diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8b6b9f1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +target +dependencies-reduced-pom.xml \ No newline at end of file diff --git a/README.md b/README.md index 8cc3f91..3b5e963 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,65 @@ # Jmeter-local-plugins-manager -Intranet Plugin Manager for Jmeter to avoid downloading plugins from Internet +Intranet Plugin Manager for JMeter - which downloads the plugins from internet periodically. + +## Motive +Some organization will not have/provide access to internet on certain hosts - This will enable the team to create an own server to manage the plugins ## Features -1. Downloads the plugins and dependent libraries to local repository -2. Creates and exposes modified API with to get the Plugins info for Jmeter -3. Creates periodic backup before update +1. Downloads the plugins, and it's associated dependent libraries to local storage. +2. Stores all the information to SQLITE DB. +3. Enables a UI to upload custom plugin which are restricted to share outside of organization. +4. Creates and exposes modified API with to get the Plugins info for Jmeter Plugin manager +5. Easily configurable scheduler to check for the newly available plugins/versions in the market. + + +## Required Components +1. Java 8 or above + +## Architecture + + +## How to Set up + +* Download the source code and compile or Download the releases +* Create config to override configuration for application +* Run the jar (java -jar jmeter-local-plugins-manager-2.0.jar) + + +## Available APIs + +| Service | HTTP Method | URI | +|:-------------------|:-----------:|:----------------------------------------| +| App Running Status | GET | http://:\/v1/ | +| Upload Plugin | GET | http://:\/v1/upload | +| Get Plugins | GET | http://:\/v1/plugins | + + +## Uploading Custom plugin +![Custom Upload Form](/img/upload-form.jpg) + + +## How it works ? + +* Its acts as an independent server which polls plugins manager for update (which is configurable) +* It creates the required directories to store the plugins and its dependencies to the local +* It checks the permission on the local directories before storing the files +* Exposes 3 APIs in intranet + - Public plugins api + - Custom Plugins api + - Merged (Public and Custom) Plugins api + +## Tools used +- Spark java framework +- Sqlite DB + +## 💲 Support Me + +If this project help you reduce time to develop, you can give me a cup of coffee :) +[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://ko-fi.com/rollno748) +Please rate a star(:star2:) - If you like it. +Please open up a bug(:beetle:) - If you experience abnormalities. + diff --git a/img/upload-form.jpg b/img/upload-form.jpg new file mode 100644 index 0000000..142928c Binary files /dev/null and b/img/upload-form.jpg differ diff --git a/pom.xml b/pom.xml index 3d3d3b6..c787365 100644 --- a/pom.xml +++ b/pom.xml @@ -2,25 +2,75 @@ 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"> 4.0.0 - com.perftalks.jmeter.pluginsmanager + io.perfwise.local.pluginsmanager jmeter-local-plugins-manager - 1.0 + 2.0 + - 2.8.2 - LATEST - 1.9.11 + 2.9.4 + 3.42.0.0 + 1.5.0 + 4.5.14 + 20230618 + 2.8.9 + 2.12.5 + 1.16.1 + 1.2.6 + 1.5 + - org.apache.logging.log4j - log4j-slf4j-impl - ${log-4j-version} + com.sparkjava + spark-core + ${spark-java} + + + org.xerial + sqlite-jdbc + ${sqlite-version} + + + commons-cli + commons-cli + ${commons-cli-vesion} + + + org.apache.httpcomponents + httpclient + ${httpclient-version} org.json json - ${jackson-mapper-version} + ${json-version} + + + com.google.code.gson + gson + ${gson-version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson-databind-version} + + + ch.qos.logback + logback-classic + ${logback-version} + + commons-fileupload + commons-fileupload + ${commons-fileupload} + + + org.jsoup + jsoup + ${jsoup-version} + + @@ -28,26 +78,39 @@ src/main/resources - config.properties + **/* - org.apache.maven.plugins - maven-jar-plugin - 3.1.0 + maven-compiler-plugin - - - true - lib/ - com.perftalks.jmeter.app.AppRunner - - + 8 + 8 + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + + + io.perfwise.local.pluginsmanager.LocalPluginsManager + + + + + + diff --git a/src/main/java/com/perftalks/jmeter/app/AppRunner.java b/src/main/java/com/perftalks/jmeter/app/AppRunner.java deleted file mode 100644 index 1389dbd..0000000 --- a/src/main/java/com/perftalks/jmeter/app/AppRunner.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.perftalks.jmeter.app; - -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.util.Properties; - -public class AppRunner { - - private static Properties props = new Properties(); - - public static void main(String[] args) throws MalformedURLException, IOException { - try { - InputStream inputStream = PluginsManager.class.getClassLoader().getResourceAsStream("config.properties"); - props.load(inputStream); - System.out.println("Properties load :: Success"); - } catch (IOException e) { - e.printStackTrace(); - } - - if (!props.isEmpty()) { - new App(props).execute(); - } - - } - -} \ No newline at end of file diff --git a/src/main/java/com/perftalks/jmeter/app/PluginsManager.java b/src/main/java/com/perftalks/jmeter/app/PluginsManager.java deleted file mode 100644 index adba665..0000000 --- a/src/main/java/com/perftalks/jmeter/app/PluginsManager.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.perftalks.jmeter.app; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.util.Properties; -import java.util.logging.Logger; - -import org.json.JSONArray; - -import com.perftalks.jmeter.repo.rest.HTTPRequests; -import com.perftalks.jmeter.repo.utils.DirOps; -import com.perftalks.jmeter.repo.utils.LoadMap; - -public class PluginsManager { - - private static final Logger LOGGER = Logger.getLogger(PluginsManager.class.getName()); - private static LoadMap load = new LoadMap(); - - - public static void executeUpdatePlugins(Properties props) throws MalformedURLException, IOException { - - if(DirOps.createDir(props.getProperty("LOCAL_REPO_PATH"))) { - JSONArray jmeterJson = HTTPRequests.get(props.getProperty("JMETER_REPO_URL")); - if(!jmeterJson.isEmpty()) { - LOGGER.info("Checking available plugins"); - //Plugins.downloadMissingPlugins(load.ConvertToMap(jmeterJson), props); - - }else { - LOGGER.info("Failed to connect to Internet, Check your Internet settings.."); - System.exit(1); - } - } - - } - -} diff --git a/src/main/java/com/perftalks/jmeter/repo/rest/HTTPRequests.java b/src/main/java/com/perftalks/jmeter/repo/rest/HTTPRequests.java deleted file mode 100644 index 1343015..0000000 --- a/src/main/java/com/perftalks/jmeter/repo/rest/HTTPRequests.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.perftalks.jmeter.repo.rest; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; -import java.nio.charset.Charset; -import java.util.logging.Logger; - -import org.json.JSONArray; - -public class HTTPRequests { - - private static final Logger LOGGER = Logger.getLogger(HTTPRequests.class.getName()); - private static JSONArray jsonArr = null; - private static InputStream is = null; - private static File file; - - public static JSONArray get(String url) throws MalformedURLException, IOException { - try { - is = new URL(url).openStream(); - BufferedReader rd = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8"))); - String jsonText = readAll(rd); - jsonArr = new JSONArray(jsonText); - - } catch (Exception e) { - e.printStackTrace(); - } finally { - is.close(); - } - - return jsonArr; - } - - private static String readAll(Reader rd) throws IOException { - StringBuilder sb = new StringBuilder(); - int cp; - while ((cp = rd.read()) != -1) { - sb.append((char) cp); - } - return sb.toString(); - } - - - public static void Downloader(String filePath, URL url) throws MalformedURLException, IOException { - - int respCode = getResponseCode(url); - file = new File (filePath + url.toString().substring(url.toString().lastIndexOf("/"))); - - if (respCode == 200) { - - try { - InputStream input = url.openStream(); - if (file.exists()) { - if (file.isDirectory()) - throw new IOException("File '" + file + "' is a directory"); - - if (!file.canWrite()) - throw new IOException("File '" + file + "' cannot be written"); - } else { - File parent = file.getParentFile(); - if ((parent != null) && (!parent.exists()) && (!parent.mkdirs())) { - throw new IOException("File '" + file + "' could not be created"); - } - } - - FileOutputStream output = new FileOutputStream(file); - - byte[] buffer = new byte[4096]; - int n = 0; - while (-1 != (n = input.read(buffer))) { - output.write(buffer, 0, n); - } - - input.close(); - output.close(); - - LOGGER.info(file + " downloaded successfully!"); - } catch (IOException ioEx) { - ioEx.printStackTrace(); - } - } - } - - - public static int getResponseCode(URL url) throws MalformedURLException, IOException { - - HttpURLConnection huc = (HttpURLConnection) url.openConnection(); - huc.setRequestMethod("GET"); - huc.connect(); - return huc.getResponseCode(); - } - -} \ No newline at end of file diff --git a/src/main/java/com/perftalks/jmeter/repo/utils/DirOps.java b/src/main/java/com/perftalks/jmeter/repo/utils/DirOps.java deleted file mode 100644 index 7655cd8..0000000 --- a/src/main/java/com/perftalks/jmeter/repo/utils/DirOps.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.perftalks.jmeter.repo.utils; - -import java.io.File; -import java.io.IOException; -import java.util.logging.Logger; - -import com.perftalks.jmeter.app.PluginsManager; - -public class DirOps { - - private static final Logger LOGGER = Logger.getLogger(PluginsManager.class.getName()); - - public static boolean createDir(String dirPath) { - - Boolean status = false; - File file = new File(dirPath); - - if(!file.exists()) { - try { - file.mkdirs(); - if(file.exists()) { - LOGGER.info("Local Plugin directory not found, Created new directory " + file.toString()); - status=true; - } - }catch(Exception e){ - LOGGER.info("Failed to create a directory for LocalRepo, Permission denied !!"); - e.printStackTrace(); - } - }else { - if(hasWritePermission(file)) { - status=true; - }else { - LOGGER.info("Permission issue.. Try with sudo "); - status=false; - } - } - return status; - } - - private static boolean hasWritePermission(File dirPath) { - File sample = new File(dirPath, "empty.txt"); - try { - sample.createNewFile(); - sample.delete(); - return true; - } catch (IOException e) { - LOGGER.info("Write permission denied for path :" + dirPath); - return false; - } - } - -} diff --git a/src/main/java/com/perftalks/jmeter/repo/utils/LoadMap.java b/src/main/java/com/perftalks/jmeter/repo/utils/LoadMap.java deleted file mode 100644 index 7009887..0000000 --- a/src/main/java/com/perftalks/jmeter/repo/utils/LoadMap.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.perftalks.jmeter.repo.utils; - -import java.util.HashMap; -import java.util.logging.Logger; - -import org.json.JSONArray; -import org.json.JSONObject; - -public class LoadMap { - - private static final Logger LOGGER = Logger.getLogger(LoadMap.class.getName()); - private static HashMap hmap = new HashMap(); - - public HashMap ConvertToMap(JSONArray jmeterJson) { - - LOGGER.info("Found " + jmeterJson.length() + " Jmeter plugins in the market"); - - for (Object obj : jmeterJson) { - // System.out.println("JSON OBJ ::" + obj.toString()); - JSONObject jObj = new JSONObject(obj.toString()); - String id = jObj.getString("id"); - hmap.put(id, jObj); - } - return hmap; - } - -} diff --git a/src/main/java/com/perftalks/jmeter/repo/utils/Parser.java b/src/main/java/com/perftalks/jmeter/repo/utils/Parser.java deleted file mode 100644 index 34696bc..0000000 --- a/src/main/java/com/perftalks/jmeter/repo/utils/Parser.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.perftalks.jmeter.repo.utils; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; - -import org.json.JSONArray; -import org.json.JSONObject; - -public class Parser { - - static String result; - - public static String getJarName(String temp) { - - if(temp.contains(".jar")) { - result = temp.substring(temp.lastIndexOf("/")+1, temp.length()); - } - return result; - } - - - public static ArrayList getAllPluginsNames(JSONArray jmeterRepoJson) { - ArrayList jmeterRepoList = new ArrayList(); - JSONArray jsArr = new JSONArray(); - String temp = new String(); - jsArr = jmeterRepoJson; - - for (int i=0; i missingPluginsList = new ArrayList(); - private static File dir; - private static String filePath; - private static Properties props; - private static URL url; - - @SuppressWarnings("static-access") - public Plugins(Properties props) { - this.props = props; - } - - @SuppressWarnings("rawtypes") - public void downloadMissingPlugins(HashMap hmap) throws MalformedURLException, IOException { - - LOGGER.info("Retrieving Missing Plugins Information"); - - for (Map.Entry element : hmap.entrySet()) { - String key = element.getKey().toString(); - dir = new File(props.getProperty("local.repo.plugins.path") + "/" + key); - - if (!dir.exists()) { - missingPluginsList.add(key); - } - } - - if (missingPluginsList.size() > 0) { - LOGGER.info(String.format("Found %s Missing Plugins from the Market", missingPluginsList.size())); - getMissingPlugins(hmap, missingPluginsList); - - } else { - LOGGER.info("No new plugins available in the market !!"); - } - - updateAllPlugins(hmap); - - } - - public static void getMissingPlugins(HashMap hmap, ArrayList missingPluginsList) { - - for (String key : missingPluginsList) { - JSONObject jObj = hmap.get(key); - LOGGER.info("The Sampler Name is " + key + " and it has " + jObj.getJSONObject("versions").length() - + " version"); - jObj = jObj.getJSONObject("versions"); - try { - DownloadPlugins(key, jObj); - } catch (Exception ex) { - LOGGER.info("Download Failed :" + ex); - } - } - } - - public static void updateAllPlugins(HashMap hmap) throws MalformedURLException, IOException { - - LOGGER.info("Checking plugin updates"); - - for (@SuppressWarnings("rawtypes") - Map.Entry element : hmap.entrySet()) { - - JSONObject jObj = hmap.get(element.getKey().toString()); - - if (jObj.getJSONObject("versions").length() > 1) { - - jObj = jObj.getJSONObject("versions"); - - for (Object key : jObj.keySet()) { - // based on key types - String keyStr = (String) key; - dir = new File(props.getProperty("local.repo.plugins.path") + "/" + element.getKey().toString() - + "/" + keyStr); - - if (!dir.exists()) { - DirOps.createDir(dir.toString()); - JSONObject pluginObj = (JSONObject) jObj.get(keyStr); - getPlugins(dir.toString(), pluginObj); - } - } - } - } - - LOGGER.info("Plugins update completed"); - } - - - public static void DownloadPlugins(String pluginName, JSONObject jObj) throws MalformedURLException, IOException { - - //LOGGER.info(jObj.toString()); - for (Object key : jObj.keySet()) { - // based on key types - String keyStr = (String) key; - JSONObject pluginObj = (JSONObject) jObj.get(keyStr); - - filePath = props.getProperty("local.repo.plugins.path") + pluginName + "/" + keyStr; - if (!new File(filePath).exists()) { - DirOps.createDir(filePath); - } - - getPlugins(filePath, pluginObj); - - } - - } - - private static void getPlugins(String fileLocation, JSONObject pluginObj) - throws MalformedURLException, IOException { - - url = new URL(pluginObj.getString("downloadUrl")); - - try { - HTTPRequests.Downloader(fileLocation, url); - }catch(Exception e) { - LOGGER.info("ERROR"+ e); - } - - if(pluginObj.has("libs")) { - if (pluginObj.getJSONObject("libs").length() >= 1) { - - JSONObject libObj = pluginObj.getJSONObject("libs"); - - for (String libKey : libObj.keySet()) { - url = new URL(libObj.getString(libKey)); - try { - HTTPRequests.Downloader(props.getProperty("local.repo.lib.path") + libKey, url); - }catch(Exception e) { - LOGGER.info("ERROR"+ e); - } - url = null; - } - } - } - - } - - public void UpdateJsonPluginsInfo(JSONArray jmeterJson, Properties props) throws IOException { - JSONArray modifiedJsonArray = new JSONArray(); - - modifiedJsonArray = PluginsFileWriter.getModifiedJson(jmeterJson, props); - PluginsFileWriter.createJSON(modifiedJsonArray, props); - LOGGER.info("The Plugins JSON can be viewed here : "+ "http://" + props.getProperty("repo.hostname") + "/prjoects/di-repo" + "/plugins.json"); - - } - -} diff --git a/src/main/java/com/perftalks/jmeter/repo/utils/PluginsFileWriter.java b/src/main/java/com/perftalks/jmeter/repo/utils/PluginsFileWriter.java deleted file mode 100644 index 54d6991..0000000 --- a/src/main/java/com/perftalks/jmeter/repo/utils/PluginsFileWriter.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.perftalks.jmeter.repo.utils; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Iterator; -import java.util.Properties; -import java.util.logging.Logger; - -import org.json.JSONArray; -import org.json.JSONObject; - -public class PluginsFileWriter { - - private static final Logger LOGGER = Logger.getLogger(PluginsFileWriter.class.getName()); - - private static void createJSONBackup(String repoPath) { - DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd_HHmmss"); - LocalDateTime ldt = LocalDateTime.now(); - - String targetFile = repoPath + "backup/" + "plugin_" + dtf.format(ldt) + ".json"; - String backupPath = repoPath + "backup/"; - Path source = Paths.get(repoPath + "/plugins.json"); - Path target = Paths.get(targetFile); - - if (!new File(backupPath).exists()) { - DirOps.createDir(backupPath); - } - - try { - Files.copy(source, target); - Files.delete(source); - } catch (IOException excep) { - excep.printStackTrace(); - } - - } - - public static void createJSON(JSONArray modifiedJSON, Properties props) throws IOException { - - String JSONFile = props.getProperty("local.repo.path") + "/plugins.json"; - - if (new File(JSONFile).exists()) { - LOGGER.info("Creating backup of JSON"); - createJSONBackup(props.getProperty("local.repo.path")); - } - - try { - FileWriter file = new FileWriter(JSONFile, false); - modifiedJSON.write(file); - file.close(); - } catch (Exception e) { - e.getMessage(); - } - } - - public static JSONArray getModifiedJson(JSONArray jmeterJson, Properties props) - throws UnsupportedEncodingException { - - Boolean flag; - String pluginsPath = "http://" + props.getProperty("repo.hostname") + "/projects/di-repo/plugins"; - String libraryPath = "http://" + props.getProperty("repo.hostname") + "/projects/di-repo/libs"; - - JSONObject jsonObj1 = new JSONObject(); - JSONObject jsonObj2 = new JSONObject(); - String temp1 = null; - String temp2 = null; - - for (int i = 0; i < jmeterJson.length(); i++) { - flag = true; - - if (i <= jmeterJson.length()) { - jsonObj1 = jmeterJson.getJSONObject(i).getJSONObject("versions"); - Iterator keys = jsonObj1.keys(); - - while (keys.hasNext()) { - String key = keys.next(); - if (flag) { - temp1 = jsonObj1.getJSONObject(key).get("downloadUrl").toString(); - temp2 = pluginsPath + "/" + key + "/" + Parser.getJarName(temp1); - jsonObj1.getJSONObject(key).put("downloadUrl", temp2); - } - - boolean avail = jsonObj1.getJSONObject(key).has("libs"); - - if (avail) { - jsonObj2 = jsonObj1.getJSONObject(key).getJSONObject("libs"); - - for (int j = 0; j < jsonObj2.length(); j++) { - Iterator k = jsonObj2.keys(); - - while (k.hasNext()) { - String keey = k.next(); - temp1 = jsonObj2.getString(keey); - temp2 = libraryPath + "/" + Parser.encodeUrl(keey) + "/" + Parser.getJarName(temp1); - jsonObj2.put(keey, temp2); - } - - } - } - } - } - } - - return jmeterJson; - } - -} diff --git a/src/main/java/io/perfwise/local/pluginsmanager/LocalPluginsManager.java b/src/main/java/io/perfwise/local/pluginsmanager/LocalPluginsManager.java new file mode 100644 index 0000000..8c3399d --- /dev/null +++ b/src/main/java/io/perfwise/local/pluginsmanager/LocalPluginsManager.java @@ -0,0 +1,97 @@ +package io.perfwise.local.pluginsmanager; + +import io.perfwise.local.pluginsmanager.controller.RestController; +import io.perfwise.local.pluginsmanager.scheduler.ScheduledTasks; +import io.perfwise.local.pluginsmanager.utils.PreCheckValidation; +import org.apache.commons.cli.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.net.InetAddress; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.sql.SQLException; +import java.util.Properties; +import java.util.Timer; + +public class LocalPluginsManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(LocalPluginsManager.class); + private static final Properties props = new Properties(); + + public static void main(String[] args) throws IOException, SQLException { + Timer timer = new Timer(); + + Options options = new Options(); + + Option helpOpt = Option.builder("h") + .longOpt("help") + .desc("Usage Help") + .build(); + + Option configOpt = Option.builder("c") + .longOpt("config") + .desc("Config file path") + .hasArg() + .argName("config") + .required() + .build(); + + options.addOption(helpOpt); + options.addOption(configOpt); + + CommandLineParser parser = new DefaultParser(); + CommandLine cmd; + if(args.length != 0){ + try { + cmd = parser.parse(options, args); + if (cmd.hasOption("h")) { + System.out.println("h"); + System.exit(1); + } + if (cmd.hasOption("c")) { + String configFile = cmd.getOptionValue("config"); + try (InputStream inputStream = Files.newInputStream(Paths.get(configFile))) { + props.load(inputStream); + LOGGER.info("Properties load :: Success"); + } catch (IOException e) { + LOGGER.error("Error loading properties from file: " + e.getMessage()); + System.exit(1); + } + } else { + LOGGER.error("Missing command-line arguments"); + System.exit(1); + } + } catch (ParseException e) { + LOGGER.error("Error parsing command-line arguments: " + e.getMessage()); + System.exit(1); + return; + } + }else { + LOGGER.info("Missing command-line arguments - Loading properties from resources"); + try(InputStream inputStream = LocalPluginsManager.class.getClassLoader().getResourceAsStream("config.properties")){ + if (inputStream != null) { + props.load(inputStream); + LOGGER.info("################################################"); + LOGGER.info(" Properties load :: Success"); + LOGGER.info("################################################"); + } else { + LOGGER.error("Resource 'config.properties' not found"); + System.exit(1); + } + } + } + + if (!props.isEmpty()) { + if(new PreCheckValidation(props).validate()){ + new RestController(props).startRestServer(); + timer.schedule(new ScheduledTasks(props), 0, Long.parseLong(props.getProperty("scheduler.interval"))); + }else{ + throw new IOException("Failed to create directories, verify the location permissions in config file"); + } + } + } +} + diff --git a/src/main/java/io/perfwise/local/pluginsmanager/controller/RestController.java b/src/main/java/io/perfwise/local/pluginsmanager/controller/RestController.java new file mode 100644 index 0000000..15ef33b --- /dev/null +++ b/src/main/java/io/perfwise/local/pluginsmanager/controller/RestController.java @@ -0,0 +1,152 @@ +package io.perfwise.local.pluginsmanager.controller; + +import io.perfwise.local.pluginsmanager.service.PluginService; +import io.perfwise.local.pluginsmanager.service.PluginServiceImpl; +import io.perfwise.local.pluginsmanager.service.UploadService; +import io.perfwise.local.pluginsmanager.service.UploadServiceImpl; +import io.perfwise.local.pluginsmanager.sqlite.SQLiteConnectionPool; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import spark.Spark; + +import javax.servlet.MultipartConfigElement; +import java.io.File; +import java.net.InetAddress; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Properties; + +import static spark.Spark.*; + +public class RestController { + private static final Logger LOGGER = LoggerFactory.getLogger(RestController.class); + private static String uriPath; + private Path basePath; + private String pluginsPath; + private String customPluginsPath; + private String libPath; + private int serverPort; + private int MIN_THREADS; + private int MAX_THREADS; + private int TIMEOUT; + private static long startTime; + private SQLiteConnectionPool connectionPool; + + public RestController() { + } + + public enum Plugins { + DEFAULT, + PUBLIC, + CUSTOM + } + + public RestController(Properties props) { + this.serverPort = Integer.parseInt(props.getProperty("server.port")); + uriPath = props.getProperty("server.uri.path"); + this.basePath = Paths.get(props.getProperty("local.repo.path")); + this.pluginsPath = this.basePath.resolve("plugins").toString(); + this.libPath = this.basePath.resolve("libs").toString(); + this.customPluginsPath = this.basePath.resolve("custom").toString(); + this.MIN_THREADS = Integer.parseInt(props.getProperty("db.min.threads")); + this.MAX_THREADS = Integer.parseInt(props.getProperty("db.max.threads")); + this.TIMEOUT = Integer.parseInt(props.getProperty("db.timeout.secs")); + RestController.startTime = System.currentTimeMillis(); + } + + /* + * This method initialises the RestServer which exposes + * 1. HealthCheck + * 2. Upload Custom plugin API + * 3. FileServer to download plugins + * 4. Initialises connection pool manager for SQLite DB + */ + public void startRestServer() { + try { + staticFiles.location("/public"); + staticFiles.externalLocation(this.basePath.toString()); + port(serverPort); + connectionPool = SQLiteConnectionPool.getInstance(this.basePath.toString(), MIN_THREADS, MAX_THREADS, TIMEOUT); + init(); + awaitInitialization(); + loadRestApiServices(); + LOGGER.info("##############################################################################"); + LOGGER.info(String.format("Local Plugins manager - REST services started :: http://%s:%s%s/status", + InetAddress.getLocalHost().getHostAddress(), serverPort, uriPath)); + LOGGER.info("##############################################################################"); + } catch (Exception e) { + LOGGER.error("Local Plugins manager - REST services failed to start", e); + } + } + + /* + * This method creates URI Path for the Rest services + */ + public void loadRestApiServices() { + DiskFileItemFactory factory = new DiskFileItemFactory(); + factory.setRepository(new File("uploads")); + ServletFileUpload upload = new ServletFileUpload(factory); + + path(RestController.uriPath, () -> { + before("/*", (req, res) -> LOGGER.info("Received api call")); + + get("/status", (req, res) -> { + long uptimeMillis = System.currentTimeMillis() - startTime; + long uptimeSeconds = uptimeMillis / 1000; + long hours = uptimeSeconds / 3600; + long minutes = (uptimeSeconds % 3600) / 60; + long seconds = uptimeSeconds % 60; + String uptimeMessage = String.format("Uptime: %d hours, %d minutes, %d seconds", hours, minutes, seconds); + + // Create an HTML response with the uptime information in a box + return "
" + + "

Application is up and running

" + + "

" + uptimeMessage + "

" + + "
"; + }); + + get("/upload", (req, res) -> { + res.type("text/html"); + res.redirect("/"); + return null; + }); + + get("/plugins", (req, res) -> { + res.type("application/json"); + String type = req.queryParams("type"); + PluginService pluginService = new PluginServiceImpl(); + + if (type == null || type.isEmpty()) { + return pluginService.getAllPlugins(); + } else if (type.equalsIgnoreCase(Plugins.PUBLIC.toString())) { + return pluginService.getPublicPlugins(); + } else if (type.equalsIgnoreCase(Plugins.CUSTOM.toString())) { + return pluginService.getCustomPlugins(); + } else { + return "Invalid plugin type."; + } + }); + + post("/upload", (req, res) -> { + req.attribute("org.eclipse.jetty.multipartConfig", new MultipartConfigElement("/temp")); + UploadService uploadService = new UploadServiceImpl(this.customPluginsPath, this.libPath); + String resp = uploadService.customPluginUpload(req, upload); + if(Integer.parseInt(resp) >= 500){ + return "Something went wrong !"; + }else{ + return "File Uploaded Successfully !"; + } + }); + + after((req, res) -> res.header("Content-Encoding", "gzip")); + + }); + } + + public static void stopRestServer() { + Spark.stop(); + } + +} diff --git a/src/main/java/io/perfwise/local/pluginsmanager/model/MetadataModel.java b/src/main/java/io/perfwise/local/pluginsmanager/model/MetadataModel.java new file mode 100644 index 0000000..54037fe --- /dev/null +++ b/src/main/java/io/perfwise/local/pluginsmanager/model/MetadataModel.java @@ -0,0 +1,50 @@ +package io.perfwise.local.pluginsmanager.model; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.json.JSONObject; + +public class MetadataModel { + @JsonProperty("id") + private String id; + @JsonProperty("version") + private String version; + @JsonProperty("downloadUrl") + private String downloadUrl; + @JsonProperty("libs") + private String libs; + +// Getters and Setters + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getLibs() { + return libs; + } + + public void setLibs(String libs) { + this.libs = libs; + } +} diff --git a/src/main/java/io/perfwise/local/pluginsmanager/model/Plugin.java b/src/main/java/io/perfwise/local/pluginsmanager/model/Plugin.java new file mode 100644 index 0000000..987ed67 --- /dev/null +++ b/src/main/java/io/perfwise/local/pluginsmanager/model/Plugin.java @@ -0,0 +1,97 @@ +package io.perfwise.local.pluginsmanager.model; + +public class Plugin { + private String id; + private String name; + private String description; + private String helpUrl; + private String screenshotUlr; + private String vendor ; + private String version; + private String pluginPath; + private String libPath; + + public Plugin(String id, String name, String description, String helpUrl, String screenshotUlr, String vendor, String version, String pluginPath, String libPath) { + this.id = id; + this.name = name; + this.description = description; + this.helpUrl = helpUrl; + this.screenshotUlr = screenshotUlr; + this.vendor = vendor; + this.version = version; + this.pluginPath = pluginPath; + this.libPath = libPath; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getHelpUrl() { + return helpUrl; + } + + public void setHelpUrl(String helpUrl) { + this.helpUrl = helpUrl; + } + + public String getScreenshotUlr() { + return screenshotUlr; + } + + public void setScreenshotUlr(String screenshotUlr) { + this.screenshotUlr = screenshotUlr; + } + + public String getVendor() { + return vendor; + } + + public void setVendor(String vendor) { + this.vendor = vendor; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getPluginPath() { + return pluginPath; + } + + public void setPluginPath(String pluginPath) { + this.pluginPath = pluginPath; + } + + public String getLibPath() { + return libPath; + } + + public void setLibPath(String libPath) { + this.libPath = libPath; + } +} diff --git a/src/main/java/io/perfwise/local/pluginsmanager/model/PluginModel.java b/src/main/java/io/perfwise/local/pluginsmanager/model/PluginModel.java new file mode 100644 index 0000000..3261758 --- /dev/null +++ b/src/main/java/io/perfwise/local/pluginsmanager/model/PluginModel.java @@ -0,0 +1,104 @@ +package io.perfwise.local.pluginsmanager.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class PluginModel { + + @JsonProperty("description") + private String description; + + @JsonProperty("helpUrl") + private String helpUrl; + + @JsonProperty("id") + private String id; + + @JsonProperty("type") + private String type; + + @JsonProperty("markerClass") + private String markerClass; + + @JsonProperty("name") + private String name; + + @JsonProperty("screenshotUrl") + private String screenshotUrl; + + @JsonProperty("vendor") + private String vendor; + + @JsonProperty("versions_count") + private int versions_count; + + + // Getter and Setter methods + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + + public String getHelpUrl() { + return helpUrl; + } + + public void setHelpUrl(String helpUrl) { + this.helpUrl = helpUrl; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getMarkerClass() { + return markerClass; + } + + public void setMarkerClass(String markerClass) { + this.markerClass = markerClass; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getScreenshotUrl() { + return screenshotUrl; + } + + public void setScreenshotUrl(String screenshotUrl) { + this.screenshotUrl = screenshotUrl; + } + public String getVendor() { + return vendor; + } + public void setVendor(String vendor) { + this.vendor = vendor; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public int getVersions_count() { + return versions_count; + } + + public void setVersions_count(int versions_count) { + this.versions_count = versions_count; + } +} \ No newline at end of file diff --git a/src/main/java/io/perfwise/local/pluginsmanager/model/UploadModel.java b/src/main/java/io/perfwise/local/pluginsmanager/model/UploadModel.java new file mode 100644 index 0000000..0b2982c --- /dev/null +++ b/src/main/java/io/perfwise/local/pluginsmanager/model/UploadModel.java @@ -0,0 +1,97 @@ +package io.perfwise.local.pluginsmanager.model; + +import java.util.List; + +public class UploadModel { + private String id; + private String name; + private String description; + private String helpUrl; + private String markerClass; + private String screenshotUrl; + private String vendor; + private String version; + private String plugin; + private List dependency; + + //Getters and setters + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getHelpUrl() { + return helpUrl; + } + + public void setHelpUrl(String helpUrl) { + this.helpUrl = helpUrl; + } + + public String getMarkerClass() { + return markerClass; + } + + public void setMarkerClass(String markerClass) { + this.markerClass = markerClass; + } + + public String getScreenshotUrl() { + return screenshotUrl; + } + + public void setScreenshotUrl(String screenshotUrl) { + this.screenshotUrl = screenshotUrl; + } + + public String getVendor() { + return vendor; + } + + public void setVendor(String vendor) { + this.vendor = vendor; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getPlugin() { + return plugin; + } + + public void setPlugin(String plugin) { + this.plugin = plugin; + } + + public List getDependency() { + return dependency; + } + + public void setDependency(List dependency) { + this.dependency = dependency; + } +} diff --git a/src/main/java/io/perfwise/local/pluginsmanager/scheduler/ScheduledTasks.java b/src/main/java/io/perfwise/local/pluginsmanager/scheduler/ScheduledTasks.java new file mode 100644 index 0000000..26998a8 --- /dev/null +++ b/src/main/java/io/perfwise/local/pluginsmanager/scheduler/ScheduledTasks.java @@ -0,0 +1,66 @@ +package io.perfwise.local.pluginsmanager.scheduler; + +import io.perfwise.local.pluginsmanager.scheduler.http.HttpRequest; +import io.perfwise.local.pluginsmanager.scheduler.parser.Parse; +import org.json.JSONArray; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.sql.SQLException; +import java.util.Properties; +import java.util.TimerTask; + +public class ScheduledTasks extends TimerTask { + + private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTasks.class); + private final Properties props; + private Parse parser; + private int localDBPluginsCount; + + public ScheduledTasks(Properties props) { + this.props = props; + this.parser = new Parse(props); + } + + @Override + public void run() { + try { + JSONArray pluginsArray = HttpRequest.get(props.getProperty("jmeter.plugins.url")); + + if(getAvailablePluginsCount(pluginsArray) == 0){ + LOGGER.info("Initializing plugin download for the fresh setup : {} New Plugin(s) found to download.. ", pluginsArray.length()); + Parse.downloadAllPlugins(pluginsArray); + }else{ + JSONArray missingPluginsList = Parse.getMissingPluginsNames(pluginsArray); + if(missingPluginsList.length() > 0){ + LOGGER.info("{} New Plugin(s) found to download.. ", missingPluginsList.length()); + parser.downloadMissingPlugins(missingPluginsList); + }else{ + LOGGER.info("Skipping Downloader - No new plugins available"); + } + } + } catch (IOException | SQLException | InterruptedException e) { + LOGGER.error("Exception occurred while checking with plugins manager"); + throw new RuntimeException(e); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + LOGGER.debug("Checking for plugin updates from Plugins manager"); + } + + private int getAvailablePluginsCount(JSONArray pluginsArray) throws SQLException, InterruptedException{ + int localStoreCount = Parse.getLocalPluginCount(pluginsArray); + setLocalDBPluginsCount(localStoreCount); + return localStoreCount; + } + + public int getLocalDBPluginsCount() { + return localDBPluginsCount; + } + + public void setLocalDBPluginsCount(int localDBPluginsCount) { + this.localDBPluginsCount = localDBPluginsCount; + } +} diff --git a/src/main/java/io/perfwise/local/pluginsmanager/scheduler/http/HttpRequest.java b/src/main/java/io/perfwise/local/pluginsmanager/scheduler/http/HttpRequest.java new file mode 100644 index 0000000..1a6616c --- /dev/null +++ b/src/main/java/io/perfwise/local/pluginsmanager/scheduler/http/HttpRequest.java @@ -0,0 +1,408 @@ +package io.perfwise.local.pluginsmanager.scheduler.http; + +import com.google.gson.Gson; +import io.perfwise.local.pluginsmanager.model.MetadataModel; +import io.perfwise.local.pluginsmanager.model.PluginModel; +import io.perfwise.local.pluginsmanager.sqlite.SQLiteConnectionPool; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.json.JSONArray; +import org.json.JSONObject; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.*; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class HttpRequest { + + private static final Logger LOGGER = LoggerFactory.getLogger(HttpRequest.class); + private Properties props; + private final Path basePath; + private final String pluginsPath; + private final String dependenciesPath; + private final String customPluginsPath; + private Connection conn; + private static final HttpClient HTTP_CLIENT = HttpClients.createDefault(); + private static final int MAX_RETRIES = 10; + private static final long RETRY_INTERVAL_MS = 60000; + private static final String INSERT_METADATA_INFO = "INSERT INTO metadata (id, version, downloadUrl, libs) VALUES (?, ?, ?, ?)"; + private static final String INSERT_PLUGIN_INFO = "INSERT INTO plugins (id, name, type, description, helpUrl, markerClass, screenshotUrl, vendor, versions_count) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; + private static final String SELECT_PLUGIN_VERSION_METADATA = "SELECT COUNT(*) AS COUNT FROM METADATA WHERE ID = ? AND VERSION = ?"; + private static final String SELECT_PLUGINS = "SELECT ID, NAME, DESCRIPTION, HELPURL, MARKERCLASS, SCREENSHOTURL, VENDOR FROM plugins"; + private static final String SELECT_PLUGINS_WITH_FILTER = "SELECT ID, NAME, DESCRIPTION, HELPURL, MARKERCLASS, SCREENSHOTURL, VENDOR FROM plugins WHERE type = ?"; + private static final String SELECT_METADATA_BY_ID = "SELECT ID, VERSION, DOWNLOADURL, LIBS FROM metadata WHERE ID = ?"; + + + public HttpRequest(Properties props){ + this.props = props; + this.basePath = Paths.get(props.getProperty("local.repo.path")); + this.pluginsPath = this.basePath.resolve("plugins").toString(); + this.dependenciesPath = this.basePath.resolve("libs").toString(); + this.customPluginsPath = this.basePath.resolve("custom").toString(); + } + + public static JSONArray get(String url) throws IOException { + int retries = 0; + while (retries < MAX_RETRIES) { + try { + HttpGet httpGet = new HttpGet(url); + HttpResponse response = HTTP_CLIENT.execute(httpGet); + HttpEntity entity = response.getEntity(); + JSONArray jsonArray = new JSONArray(EntityUtils.toString(entity)); + EntityUtils.consume(entity); + return jsonArray; + } catch (IOException e) { + LOGGER.error("Attempt " + (retries + 1) + " failed. Retrying in " + (RETRY_INTERVAL_MS / 1000) + " seconds..."); + retries++; + try { + Thread.sleep(RETRY_INTERVAL_MS); + } catch (InterruptedException interruptedException) { + // Ignore interruption during sleep + } + } + } + throw new IOException("Failed to execute HTTP GET request after " + MAX_RETRIES + " retries."); + } + + public static void fileDownloader(String filePath, URL url) throws IOException { + try{ + int respCode = getResponseCode(url); + if (respCode == 200) { + File file = new File(filePath + url.toString().substring(url.toString().lastIndexOf("/"))); + + try (InputStream input = url.openStream(); + FileOutputStream output = new FileOutputStream(file)) { + byte[] buffer = new byte[4096]; + int n; + while ((n = input.read(buffer)) != -1) { + output.write(buffer, 0, n); + } + } catch (IOException ioEx) { + ioEx.printStackTrace(); + } + + LOGGER.debug("{} downloaded successfully!", file); + } + }catch(UnknownHostException uhe){ + LOGGER.info(String.format("Unable to resolve hostname %s \nException trace %s", url, uhe)); + } + } + + public static int getResponseCode(URL url) throws IOException { + HttpURLConnection huc = (HttpURLConnection) url.openConnection(); + huc.setRequestMethod("GET"); + huc.connect(); + return huc.getResponseCode(); + } + + public void downloadMissingPlugins(JSONObject pluginObject) throws URISyntaxException, IOException { + JSONObject metaDataObj = new JSONObject(); + metaDataObj.put("id", pluginObject.getString("id")); + JSONObject versionObj = pluginObject.getJSONObject("versions"); + for (String version : versionObj.keySet()) { + if(!isPluginVersionExist(pluginObject.getString("id"), version)){ + metaDataObj.put("version", version); + JSONObject verObj = versionObj.getJSONObject(version); + if(!verObj.isNull("downloadUrl")){ + String downloadUrl = verObj.getString("downloadUrl"); + if(!downloadUrl.contains("%1$s.jar")){ + metaDataObj.put("downloadUrl", downloadUrl.substring(downloadUrl.lastIndexOf('/') + 1)); + fileDownloader(this.pluginsPath, new URI(downloadUrl).toURL()); + }else{ + List availableVersions = this.getAvailableLibraryVersions(downloadUrl); + for (String ver : availableVersions){ + String url = downloadUrl.replace("%1$s", ver); + metaDataObj.put("downloadUrl", url.substring(url.lastIndexOf('/') + 1)); + fileDownloader(this.pluginsPath, new URI(url).toURL()); + } + } + if(verObj.has("libs")){ + JSONObject libsObject = verObj.getJSONObject("libs"); + metaDataObj.put("libs", libsObject.toString()); + for (String lib : libsObject.keySet()) { + String libUrl = libsObject.getString(lib); + fileDownloader(this.dependenciesPath, new URI(libUrl).toURL()); + } + } + } + this.updatePluginMetadataInfo(metaDataObj); + LOGGER.info("Downloaded {} plugin - version {}", pluginObject.getString("id"), version); + } + } + this.updatePluginInfoInDB(pluginObject, "public"); + } + + private List getAvailableLibraryVersions(String downloadUrl) { + String libName = null; + Pattern pattern = Pattern.compile("jmeter/(?[^/]+)/%\\d[^/]*"); + Matcher matcher = pattern.matcher(downloadUrl); + if(matcher.find()) { + libName = matcher.group("libName"); + } + return fetchAvailableVersions(libName); + } + + public JSONArray fetchPluginsFromLocalDB(String query, String type){ + JSONArray jsonArray = new JSONArray(); + try{ + if(conn == null || conn.isClosed()){ + conn = SQLiteConnectionPool.getConnection(); + } + PreparedStatement preparedStatement = conn.prepareStatement(query); + if(type != null){ + preparedStatement.setString(1, type); + } + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + JSONObject pluginObject = new JSONObject(); + JSONObject libraryObj; + + pluginObject.put("id", rs.getString("id")); + pluginObject.put("name", rs.getString("name")); + pluginObject.put("description", rs.getString("description")); + pluginObject.put("helpUrl", rs.getString("helpUrl")); + pluginObject.put("markerClass", rs.getString("markerClass")); + pluginObject.put("screenshotUrl", rs.getString("screenshotUrl")); + pluginObject.put("vendor", rs.getString("vendor")); + libraryObj = this.getDependentLibraryObj(rs.getString("id"), type); + pluginObject.put("versions", libraryObj); + jsonArray.put(pluginObject); + } + preparedStatement.close(); + }catch(SQLException | InterruptedException e){ + LOGGER.error("Exception occurred while fetching plugins information"); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } finally { + SQLiteConnectionPool.releaseConnection(conn); + } + return jsonArray; + } + + public JSONArray getAllPlugins() { +// JSONArray pluginsArray = getCombinedPlugins(); + JSONArray publicPluginArray = getPublicPlugins(); + JSONArray customPluginArray = getCustomPlugins(); + + for(Object obj: customPluginArray){ + publicPluginArray.put((JSONObject) obj); + } + return publicPluginArray; + } + + private JSONArray getCombinedPlugins() { + return fetchPluginsFromLocalDB(SELECT_PLUGINS, "all"); + } + + public JSONArray getPublicPlugins() { + return fetchPluginsFromLocalDB(SELECT_PLUGINS_WITH_FILTER, "public"); + } + + public JSONArray getCustomPlugins() { + return fetchPluginsFromLocalDB(SELECT_PLUGINS_WITH_FILTER, "custom"); + } + + private JSONObject getDependentLibraryObj(String id, String type) throws UnknownHostException { + + String host = InetAddress.getLocalHost().getHostAddress(); + String libUrl = String.format("http://%s:%s/%s/", host, props.getProperty("server.port"), "libs"); + String pluginUrl = null; + if(type.equals("public")){ + pluginUrl = String.format("http://%s:%s/%s/", host, props.getProperty("server.port"), "plugins"); + }else{ + pluginUrl = String.format("http://%s:%s/%s/", host, props.getProperty("server.port"), "custom"); + } + + JSONObject libraryObj = new JSONObject(); + try{ + if(conn == null || conn.isClosed()){ + conn = SQLiteConnectionPool.getConnection(); + } + PreparedStatement preparedStatement = conn.prepareStatement(SELECT_METADATA_BY_ID); + preparedStatement.setString(1, id); + ResultSet rs = preparedStatement.executeQuery(); + + while (rs.next()) { + JSONObject versionsObj = new JSONObject(); + versionsObj.put("downloadUrl", pluginUrl + rs.getString("downloadUrl")); + if (rs.getString("libs") != null){ + versionsObj.put("libs", processLibs(rs.getString("libs"), libUrl)); + } + libraryObj.put(rs.getString("version"), versionsObj); + } + preparedStatement.close(); + }catch(SQLException | InterruptedException e){ + LOGGER.error("Exception occurred while fetching plugins information"); + } + return libraryObj; + } + + private JSONObject processLibs(String libs, String libUrl) { + JSONObject libraryObject = new JSONObject(); + JSONObject jsonObject = new JSONObject(libs); + for (String key : jsonObject.keySet()) { + String jVal = (String) jsonObject.get(key); + String val = libUrl + jVal.substring(jVal.lastIndexOf('/') + 1); + libraryObject.put(key, val); + } + return libraryObject; + } + + private List fetchAvailableVersions(String libName) { + Document doc = null; + List versions = new ArrayList<>(); + try{ + doc = Jsoup.connect("https://repo1.maven.org/maven2/org/apache/jmeter/" + libName).get(); + Elements links = doc.select("a[href]"); + for(Element link : links) { + String href = link.attr("href"); + if(href.contains(libName + "-")) { + String version = href.substring(href.lastIndexOf("-") + 1, href.lastIndexOf(".jar")); + versions.add(version); + } + } + }catch (IOException e){ + e.printStackTrace(); + } + return versions; + } + + public void updatePluginMetadataInfo(JSONObject metaDataObj) { + MetadataModel metadataModel = new Gson().fromJson(String.valueOf(metaDataObj), MetadataModel.class); + try{ + if(conn == null || conn.isClosed()){ + conn = SQLiteConnectionPool.getConnection(); + } + + PreparedStatement preparedStatement = conn.prepareStatement(INSERT_METADATA_INFO); + preparedStatement.setString(1, metadataModel.getId()); + preparedStatement.setString(2, metadataModel.getVersion()); + preparedStatement.setString(3, metadataModel.getDownloadUrl()); + preparedStatement.setString(4, metadataModel.getLibs()); + + int rowsInserted = preparedStatement.executeUpdate(); + if (rowsInserted > 0) { + LOGGER.debug("Data inserted for {} - successfully", metadataModel.getId()); + } else { + LOGGER.debug("Failed to insert data for {}", metadataModel.getId()); + } + preparedStatement.close(); + }catch (SQLException | InterruptedException e){ + e.printStackTrace(); + } + } + + public boolean isPluginVersionExist(String id, String version){ + int result = 0; + try{ + if(conn == null || conn.isClosed()){ + conn = SQLiteConnectionPool.getConnection(); + } + PreparedStatement preparedStatement = conn.prepareStatement(SELECT_PLUGIN_VERSION_METADATA); + preparedStatement.setString(1, id); + preparedStatement.setString(2, version); + ResultSet rs = preparedStatement.executeQuery(); + result = rs.getInt("COUNT"); + preparedStatement.close(); + }catch (SQLException | InterruptedException e){ + e.printStackTrace(); + } + + return result > 0; + } + + public void updateCustomPluginInfoInDB(JSONObject pluginModel) { + try{ + if(conn == null || conn.isClosed()){ + conn = SQLiteConnectionPool.getConnection(); + } + + PreparedStatement preparedStatement = conn.prepareStatement(INSERT_PLUGIN_INFO); + preparedStatement.setString(1, pluginModel.getString("id")); + preparedStatement.setString(2, pluginModel.getString("name")); + preparedStatement.setString(3, "custom"); + preparedStatement.setString(4, pluginModel.getString("description")); + preparedStatement.setString(5, pluginModel.getString("helpUrl")); + preparedStatement.setString(6, pluginModel.getString("markerClass")); + preparedStatement.setString(7, pluginModel.getString("screenshotUrl")); + preparedStatement.setString(8, pluginModel.getString("vendor")); + preparedStatement.setDouble(9, pluginModel.getDouble("version_count")); + + int rowsInserted = preparedStatement.executeUpdate(); + if (rowsInserted > 0) { + LOGGER.debug("Data inserted for {} - successfully", pluginModel.getString("id")); + } else { + LOGGER.debug("Failed to insert data for {}", pluginModel.getString("id")); + } + preparedStatement.close(); + }catch (SQLException | InterruptedException e){ + e.printStackTrace(); + } + } + + private void updatePluginInfoInDB(JSONObject pluginObject, String type) { + int versionsCount = pluginObject.getJSONObject("versions").length(); + pluginObject.put("versions_count", versionsCount); + PluginModel pluginModel = new Gson().fromJson(String.valueOf(pluginObject), PluginModel.class); + + try{ + if(conn == null || conn.isClosed()){ + conn = SQLiteConnectionPool.getConnection(); + } + + PreparedStatement preparedStatement = conn.prepareStatement(INSERT_PLUGIN_INFO); + preparedStatement.setString(1, pluginModel.getId()); + preparedStatement.setString(2, pluginModel.getName()); + preparedStatement.setString(3, type); + preparedStatement.setString(4, pluginModel.getDescription()); + preparedStatement.setString(5, pluginModel.getHelpUrl()); + preparedStatement.setString(6, pluginModel.getMarkerClass()); + preparedStatement.setString(7, pluginModel.getScreenshotUrl()); + preparedStatement.setString(8, pluginModel.getVendor()); + preparedStatement.setInt(9, pluginModel.getVersions_count()); + + int rowsInserted = preparedStatement.executeUpdate(); + if (rowsInserted > 0) { + LOGGER.debug("Data inserted for {} - successfully", pluginModel.getId()); + } else { + LOGGER.debug("Failed to insert data for {}", pluginModel.getId()); + } + preparedStatement.close(); + }catch (SQLException | InterruptedException e){ + e.printStackTrace(); + } + } + + public Properties getProps() { + return props; + } + + public void setProps(Properties props) { + this.props = props; + } + +} diff --git a/src/main/java/io/perfwise/local/pluginsmanager/scheduler/parser/Parse.java b/src/main/java/io/perfwise/local/pluginsmanager/scheduler/parser/Parse.java new file mode 100644 index 0000000..155b460 --- /dev/null +++ b/src/main/java/io/perfwise/local/pluginsmanager/scheduler/parser/Parse.java @@ -0,0 +1,136 @@ +package io.perfwise.local.pluginsmanager.scheduler.parser; + +import io.perfwise.local.pluginsmanager.scheduler.http.HttpRequest; +import io.perfwise.local.pluginsmanager.sqlite.SQLiteConnectionPool; +import org.json.JSONArray; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Properties; + +public class Parse { + private static final Logger LOGGER = LoggerFactory.getLogger(Parse.class); + private static Connection conn; + private static HttpRequest httpRequest = null; + private static final String PLUGINS_COUNT = "SELECT COUNT(ID) AS COUNT FROM PLUGINS"; + private static final String PLUGINS_METADATA_BY_ID = "SELECT COUNT(*) AS COUNT FROM METADATA WHERE ID = ?"; + + public Parse() { + try { + Parse.conn = SQLiteConnectionPool.getConnection(); + } catch (InterruptedException | SQLException e) { + throw new RuntimeException(e); + } + } + + public Parse(Properties props) { + httpRequest = new HttpRequest(props); + try { + Parse.conn = SQLiteConnectionPool.getConnection(); + } catch (InterruptedException | SQLException e) { + throw new RuntimeException(e); + } + } + + public static JSONArray getMissingPluginsNames(JSONArray jmeterRepoJson) { + JSONArray missingPluginArray = new JSONArray(); + for (Object plugin : jmeterRepoJson) { + JSONObject pluginObj = (JSONObject) plugin; + String id = pluginObj.getString("id") ; + JSONObject versions= pluginObj.getJSONObject("versions"); + + for (String key : versions.keySet()) { + if(!httpRequest.isPluginVersionExist(id, key)){ + missingPluginArray.put(pluginObj); + } + } + } + return missingPluginArray; + } + + public static void downloadAllPlugins(JSONArray jmeterRepoJson) throws URISyntaxException, IOException { + for (int i = 0; i < jmeterRepoJson.length(); i++) { + httpRequest.downloadMissingPlugins(jmeterRepoJson.getJSONObject(i)); + } + } + + public static int getLocalPluginCount(JSONArray pluginsArray) throws SQLException, InterruptedException { + int localStoreCount = 0; + conn = SQLiteConnectionPool.getConnection(); + + if(!conn.isClosed()){ + try{ + ResultSet rs = conn.createStatement().executeQuery(PLUGINS_COUNT); + localStoreCount = rs.getInt("COUNT"); + } catch (SQLException e) { + LOGGER.error("Exception occurred while executing SQL statement"); + throw new RuntimeException(e); + }finally{ + SQLiteConnectionPool.releaseConnection(conn); + } + } + return localStoreCount; + } + + public static void addPluginDataToDB(JSONObject pluginObj) throws SQLException, InterruptedException { + String type = "custom"; + httpRequest.updateCustomPluginInfoInDB(pluginObj); + } + + public static void addMetaDataToDB(JSONObject metaDataObj) { + httpRequest.updatePluginMetadataInfo(metaDataObj); + } + + public static int availablePluginsCount(String id) throws SQLException, InterruptedException{ + int availableCount = 0; + conn = SQLiteConnectionPool.getConnection(); + + if(!conn.isClosed()){ + try{ + PreparedStatement preparedStatement = conn.prepareStatement(PLUGINS_METADATA_BY_ID); + preparedStatement.setString(1, id); + ResultSet rs = preparedStatement.executeQuery(); + availableCount = rs.getInt("COUNT"); + } catch (SQLException e) { + LOGGER.error("Exception occurred while executing SQL statement"); + throw new RuntimeException(e); + }finally{ + SQLiteConnectionPool.releaseConnection(conn); + } + } + return availableCount; + } + + public static JSONArray getAllPlugins() { + return httpRequest.getAllPlugins(); + } + + public static JSONArray getPublicPlugins() { + return httpRequest.getPublicPlugins(); + } + + public static JSONArray getCustomPlugins() { + return httpRequest.getCustomPlugins(); + } + + public void downloadMissingPlugins(JSONArray missingPluginsList) throws URISyntaxException, IOException { + for (int i = 0; i < missingPluginsList.length(); i++) { + httpRequest.downloadMissingPlugins(missingPluginsList.getJSONObject(i)); + } + } + + public static HttpRequest getHttpRequest() { + return httpRequest; + } + + public static void setHttpRequest(HttpRequest httpRequest) { + Parse.httpRequest = httpRequest; + } +} diff --git a/src/main/java/io/perfwise/local/pluginsmanager/service/PluginService.java b/src/main/java/io/perfwise/local/pluginsmanager/service/PluginService.java new file mode 100644 index 0000000..69f6b5c --- /dev/null +++ b/src/main/java/io/perfwise/local/pluginsmanager/service/PluginService.java @@ -0,0 +1,9 @@ +package io.perfwise.local.pluginsmanager.service; + +import org.json.JSONArray; + +public interface PluginService { + JSONArray getAllPlugins(); + JSONArray getPublicPlugins(); + JSONArray getCustomPlugins(); +} diff --git a/src/main/java/io/perfwise/local/pluginsmanager/service/PluginServiceImpl.java b/src/main/java/io/perfwise/local/pluginsmanager/service/PluginServiceImpl.java new file mode 100644 index 0000000..645f5a7 --- /dev/null +++ b/src/main/java/io/perfwise/local/pluginsmanager/service/PluginServiceImpl.java @@ -0,0 +1,22 @@ +package io.perfwise.local.pluginsmanager.service; + +import io.perfwise.local.pluginsmanager.scheduler.parser.Parse; +import org.json.JSONArray; + +public class PluginServiceImpl implements PluginService { + + @Override + public JSONArray getAllPlugins() { + return Parse.getAllPlugins(); + } + + @Override + public JSONArray getPublicPlugins() { + return Parse.getPublicPlugins(); + } + + @Override + public JSONArray getCustomPlugins() { + return Parse.getCustomPlugins(); + } +} diff --git a/src/main/java/io/perfwise/local/pluginsmanager/service/UploadService.java b/src/main/java/io/perfwise/local/pluginsmanager/service/UploadService.java new file mode 100644 index 0000000..fe5c252 --- /dev/null +++ b/src/main/java/io/perfwise/local/pluginsmanager/service/UploadService.java @@ -0,0 +1,19 @@ +package io.perfwise.local.pluginsmanager.service; + +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.FileUploadException; +import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.json.JSONObject; +import spark.Request; + +import javax.servlet.http.Part; +import java.io.IOException; +import java.sql.SQLException; +import java.util.Collection; +import java.util.List; + +public interface UploadService { + String customPluginUpload(Request req, ServletFileUpload servletFileUpload) throws FileUploadException, SQLException, InterruptedException, IOException; + boolean handleFileUpload(Collection uploadedFiles, JSONObject metadataObject) throws IOException; + +} diff --git a/src/main/java/io/perfwise/local/pluginsmanager/service/UploadServiceImpl.java b/src/main/java/io/perfwise/local/pluginsmanager/service/UploadServiceImpl.java new file mode 100644 index 0000000..9023fe5 --- /dev/null +++ b/src/main/java/io/perfwise/local/pluginsmanager/service/UploadServiceImpl.java @@ -0,0 +1,117 @@ +package io.perfwise.local.pluginsmanager.service; + +import io.perfwise.local.pluginsmanager.scheduler.http.HttpRequest; +import io.perfwise.local.pluginsmanager.scheduler.parser.Parse; +import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import spark.Request; + +import javax.servlet.ServletException; +import javax.servlet.http.Part; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.sql.SQLException; +import java.util.Collection; + +public class UploadServiceImpl implements UploadService{ + private static final Logger LOGGER = LoggerFactory.getLogger(UploadServiceImpl.class); + private String customPluginPath; + private String libPath; + + public UploadServiceImpl(String customPluginPath, String libPath) { + this.customPluginPath = customPluginPath; + this.libPath = libPath; + } + + @Override + public String customPluginUpload(Request req, ServletFileUpload servletFileUpload) throws SQLException, InterruptedException, IOException { + String result = null; + Collection dependencyJars = null; + JSONObject pluginObj = new JSONObject(); + JSONObject metaDataObj = new JSONObject(); + HttpRequest httpRequest = Parse.getHttpRequest(); + + String id = req.queryMap("id").values()[0]; + String version = req.queryMap("version").values()[0]; + + int versionCount = Parse.availablePluginsCount(id); + + pluginObj.put("id", id); + pluginObj.put("name", req.queryMap("name").values()[0]); + pluginObj.put("type", "custom"); + pluginObj.put("description", req.queryMap("description").values()[0]); + pluginObj.put("helpUrl", req.queryMap("helpUrl").values()[0]); + pluginObj.put("markerClass", req.queryMap("markerClass").values()[0]); + pluginObj.put("screenshotUrl", req.queryMap("screenshotUrl").values()[0]); + pluginObj.put("vendor", req.queryMap("vendor").values()[0]); + + if(httpRequest.isPluginVersionExist(id, version)){ + pluginObj.put("version_count", versionCount); + }else{ + pluginObj.put("version_count", versionCount + 1); + } + Parse.addPluginDataToDB(pluginObj); + + metaDataObj.put("id", id); + metaDataObj.put("version", version); + + try{ + dependencyJars = req.raw().getParts(); + } catch (ServletException | IOException e) { + throw new RuntimeException(e); + } + + boolean isUploadSuccess = this.handleFileUpload(dependencyJars, metaDataObj); + if(isUploadSuccess){ + result = "200"; + }else{ + result = "500"; + } + return result; + } + @Override + public boolean handleFileUpload(Collection uploadedFiles, JSONObject metaDataObj) throws IOException { + String url = "http://dummy.com/"; + JSONObject depsObj = new JSONObject(); + try{ + for (Part uploadedFile : uploadedFiles) { + String fieldName = uploadedFile.getName(); + if(fieldName != null && fieldName.equals("pluginJar")){ + copyFileItemToDirectory(uploadedFile, this.customPluginPath); + metaDataObj.put("downloadUrl", uploadedFile.getSubmittedFileName()); + } else if (fieldName != null && fieldName.equals("dependencyJars")) { + String fileName = uploadedFile.getSubmittedFileName(); + String strippedName = fileName.substring(0, fileName.length() - 4); + copyFileItemToDirectory(uploadedFile, this.libPath); + depsObj.put(strippedName, url + fileName); + } + } + if(depsObj.length()>0) + metaDataObj.put("libs", depsObj.toString()); + + Parse.addMetaDataToDB(metaDataObj); + return true; + }catch(Exception e){ + LOGGER.error("Exception occurred while Uploading custom plugins: "); + e.printStackTrace(); + return false; + } + } + + private void copyFileItemToDirectory(Part uploadedItem, String path) { + String filename = uploadedItem.getSubmittedFileName(); + String destination = path + File.separator + filename; + try (InputStream fileInputStream = uploadedItem.getInputStream()) { + Files.copy(fileInputStream, Paths.get(destination), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/io/perfwise/local/pluginsmanager/sqlite/SQLiteConnectionPool.java b/src/main/java/io/perfwise/local/pluginsmanager/sqlite/SQLiteConnectionPool.java new file mode 100644 index 0000000..9af5cd7 --- /dev/null +++ b/src/main/java/io/perfwise/local/pluginsmanager/sqlite/SQLiteConnectionPool.java @@ -0,0 +1,175 @@ +package io.perfwise.local.pluginsmanager.sqlite; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.*; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; + +public class SQLiteConnectionPool { + + private static final Logger LOGGER = LoggerFactory.getLogger(SQLiteConnectionPool.class); + private static final int DEFAULT_MIN_POOL_SIZE = 1; + private static final int DEFAULT_MAX_POOL_SIZE = 5; + private static final int DEFAULT_TIMEOUT_SECONDS = 5; + private static final Object lock = new Object(); + private static SQLiteConnectionPool instance; + private static String DB_FILE_PATH; + private static final String DBFILENAME = "plugins.db"; + private static final String PLUGINS_METADATA = "CREATE TABLE metadata (id TEXT, version TEXT, downloadUrl TEXT, libs TEXT(1000000))"; + private static final String PLUGINS_INFO = "CREATE TABLE plugins (id TEXT PRIMARY KEY, name TEXT, type TEXT, description TEXT, helpUrl TEXT, markerClass TEXT, screenshotUrl TEXT, vendor TEXT, versions_count INTEGER)"; + private static int minPoolSize; + private static int maxPoolSize; + private static int timeoutSeconds; + private static BlockingQueue connections; + + private SQLiteConnectionPool(String dbPath) { + this(dbPath, DEFAULT_MIN_POOL_SIZE, DEFAULT_MAX_POOL_SIZE, DEFAULT_TIMEOUT_SECONDS); + } + + private SQLiteConnectionPool(String dbPath, int minPoolSize, int maxPoolSize, int timeoutSeconds) { + String pathSeparator = System.getProperty("file.separator"); + if (!dbPath.endsWith(pathSeparator)) { + dbPath += pathSeparator + DBFILENAME; + }else{ + dbPath += DBFILENAME; + } + + setMinPoolSize(minPoolSize); + setMaxPoolSize(maxPoolSize); + setTimeoutSeconds(timeoutSeconds); + setDB_FILE_PATH(dbPath); + connections = new ArrayBlockingQueue<>(maxPoolSize); + + for (int i = 0; i < maxPoolSize; i++) { + try { + Connection connection = createConnection(SQLiteConnectionPool.getDB_FILE_PATH()); + connections.add(connection); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + } + + private static Connection createConnection(String dbPath) throws SQLException { + return DriverManager.getConnection("jdbc:sqlite:" + dbPath); + } + + public static synchronized SQLiteConnectionPool getInstance(String dbPath, int minPoolSize, int maxPoolSize, int timeoutSeconds) { + if (instance == null) { + instance = new SQLiteConnectionPool(dbPath, minPoolSize, maxPoolSize, timeoutSeconds); + } + return instance; + } + + public static Connection getConnection() throws InterruptedException, SQLException { + Connection connection = connections.poll(getTimeoutSeconds(), TimeUnit.SECONDS); + if (connection == null) { + if (connections.size() < getMaxPoolSize()) { + connection = createConnection(getDB_FILE_PATH()); + } else { + throw new RuntimeException("Connection pool timeout"); + } + } + return connection; + } + + public static void releaseConnection(Connection connection) { + if (connection != null) { + synchronized (lock) { // Synchronize on a separate lock object + try { + connection.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + } + } + + public static boolean createLocalDatabase(String dbFilePath) { + String pathSeparator = System.getProperty("file.separator"); + boolean result = false; + if (!dbFilePath.endsWith(pathSeparator)) { + dbFilePath += pathSeparator + DBFILENAME; + }else{ + dbFilePath += DBFILENAME; + } + String dbUrl = "jdbc:sqlite:" + dbFilePath; + try { + Connection conn = DriverManager.getConnection(dbUrl); + if (conn != null) { + validateDatabase(conn); + } + result = true; + } catch (SQLException e) { + LOGGER.error("Exception occurred while creating database: {}", e.getMessage()); + } + return result; + } + + public void close() { + for (Connection connection : connections) { + try { + connection.close(); + } catch (SQLException e) { + // Ignore + } + } + connections.clear(); + } + + private static void validateDatabase(Connection conn) { + String[] tableNames = {"metadata", "plugins"}; + try { + DatabaseMetaData metaData = conn.getMetaData(); + ResultSet metadata_table = metaData.getTables(null, null, tableNames[0], null); + ResultSet plugin_table = metaData.getTables(null, null, tableNames[1], null); + + if (metadata_table.isBeforeFirst() && plugin_table.isBeforeFirst()) { + LOGGER.info("Table Present - Skipping Tables creation"); + } else { + LOGGER.info("Table not Present - creating Tables"); + conn.createStatement().execute(PLUGINS_METADATA); + conn.createStatement().execute(PLUGINS_INFO); + } + } catch (SQLException e) { + LOGGER.error("Exception occurred while fetching metadata from db file", e); + } finally { + releaseConnection(conn); + } + } + + public static String getDB_FILE_PATH() { + return DB_FILE_PATH; + } + + public static void setDB_FILE_PATH(String DB_FILE_PATH) { + SQLiteConnectionPool.DB_FILE_PATH = DB_FILE_PATH; + } + + public static int getMinPoolSize() { + return minPoolSize; + } + + public static void setMinPoolSize(int minPoolSize) { + SQLiteConnectionPool.minPoolSize = minPoolSize; + } + + public static int getMaxPoolSize() { + return maxPoolSize; + } + + public static void setMaxPoolSize(int maxPoolSize) { + SQLiteConnectionPool.maxPoolSize = maxPoolSize; + } + + public static int getTimeoutSeconds() { + return timeoutSeconds; + } + + public static void setTimeoutSeconds(int timeoutSeconds) { + SQLiteConnectionPool.timeoutSeconds = timeoutSeconds; + } +} diff --git a/src/main/java/io/perfwise/local/pluginsmanager/utils/DirectoryOps.java b/src/main/java/io/perfwise/local/pluginsmanager/utils/DirectoryOps.java new file mode 100644 index 0000000..d0bf365 --- /dev/null +++ b/src/main/java/io/perfwise/local/pluginsmanager/utils/DirectoryOps.java @@ -0,0 +1,51 @@ +package io.perfwise.local.pluginsmanager.utils; + +import io.perfwise.local.pluginsmanager.sqlite.SQLiteConnectionPool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; + +public class DirectoryOps { + + private static final Logger LOGGER = LoggerFactory.getLogger(SQLiteConnectionPool.class); + + public boolean createDirectory(String dirPath){ + boolean status = false; + File file = new File(dirPath); + + if(!file.exists()) { + try { + status = file.mkdirs(); + if(file.exists()) { + LOGGER.info("Local Plugin directory not found, Created new directory " + file.toString()); + status=true; + } + }catch(Exception e){ + LOGGER.info("Failed to create a directory for LocalRepo, Permission denied !!"); + e.printStackTrace(); + } + }else { + if(hasWritePermission(file)) { + status=true; + }else { + LOGGER.info("Permission issue.. Try with sudo "); + } + } + return status; + } + + private static boolean hasWritePermission(File dirPath) { + File sample = new File(dirPath, "permission.txt"); + boolean status; + try { + status = sample.createNewFile(); + status = sample.delete(); + } catch (IOException e) { + LOGGER.info("Write permission denied for path :" + dirPath); + status = false; + } + return status; + } +} diff --git a/src/main/java/io/perfwise/local/pluginsmanager/utils/PreCheckValidation.java b/src/main/java/io/perfwise/local/pluginsmanager/utils/PreCheckValidation.java new file mode 100644 index 0000000..aeb5f1a --- /dev/null +++ b/src/main/java/io/perfwise/local/pluginsmanager/utils/PreCheckValidation.java @@ -0,0 +1,55 @@ +package io.perfwise.local.pluginsmanager.utils; + +import io.perfwise.local.pluginsmanager.sqlite.SQLiteConnectionPool; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.SQLException; +import java.util.Properties; + +public class PreCheckValidation { + private static final Logger LOGGER = LoggerFactory.getLogger(PreCheckValidation.class); + private final Path basePath; + private final DirectoryOps directoryOps; + + public PreCheckValidation(Properties props) { + this.basePath = Paths.get(props.getProperty("local.repo.path")); + this.directoryOps = new DirectoryOps(); + } + + public boolean validateDirectoryPresence() { + if(basePath == null || basePath.toString().isEmpty()) { + LOGGER.error("Plugins directory path is invalid: {}", basePath); + return false; + } + + String pluginsPath = this.basePath.resolve("plugins").toString(); + String dependenciesPath = this.basePath.resolve("libs").toString(); + String customPluginsPath = this.basePath.resolve("custom").toString(); + + if (directoryOps.createDirectory(pluginsPath) && directoryOps.createDirectory(dependenciesPath) && directoryOps.createDirectory(customPluginsPath)){ + LOGGER.info("Created local plugins directory successfully !"); + return true; + }else{ + LOGGER.error("Failed to create local plugins directory"); + return false; + } + } + + public boolean validate() throws SQLException { + boolean result = false; + if (validateDirectoryPresence()){ + result = createLocalDatabaseIfNotPresent(); + }else { + result = createLocalDatabaseIfNotPresent(); + } + return result; + } + + private boolean createLocalDatabaseIfNotPresent() { + return SQLiteConnectionPool.createLocalDatabase(this.basePath.toString()); + } + +} diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties index 9057f6c..5b8eb10 100644 --- a/src/main/resources/config.properties +++ b/src/main/resources/config.properties @@ -1,11 +1,18 @@ -#JMETER REPO -jmeter.repo.url=https://jmeter-plugins.org/repo/ +server.port=2222 +server.uri.path=/v1 -#Directory Properties -local.repo.path=/app/performance/jmeter/repo/ -local.repo.plugins.path=/app/performance/jmeter/repo/plugins -local.repo.lib.path=/app/performance/jmeter/repo/libs/ +#Scheduler interval (in ms) +scheduler.interval=86400000 -#Repo hostname info -repo.hostname=localhost -#repo.hostname.path=/projects/repo \ No newline at end of file +#DB Connection Pool configuration +db.min.threads=2 +db.max.threads=10 +db.timeout.secs=300 + +#External APIs +jmeter.plugins.url=https://jmeter-plugins.org/repo/ +mvn.repo.url=https://mvnrepository.com/search?q= + +#Directory Configs +#local.repo.path=/app/plugins-manager/ +local.repo.path=C:\\Temp\\plugins-manager\\ diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..b1f9bfe --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,11 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/src/main/resources/public/index.html b/src/main/resources/public/index.html new file mode 100644 index 0000000..3ce40ed --- /dev/null +++ b/src/main/resources/public/index.html @@ -0,0 +1,50 @@ + + + + Custom Plugin Form + + + + + Fork me on GitHub + +
+ +
+
+

Custom Plugin Upload Form

+ + + + + + + + + + + + + + +
+
+
+ +
+ Success + File upload successfully! +
+
+ +
+ + + diff --git a/src/main/resources/public/script.js b/src/main/resources/public/script.js new file mode 100644 index 0000000..037328c --- /dev/null +++ b/src/main/resources/public/script.js @@ -0,0 +1,81 @@ +// This function is called when the form is submitted. +function submitForm() { + const fields = { + id: document.getElementById("id"), + name: document.getElementById("name"), + description: document.getElementById("description"), + helpUrl: document.getElementById("helpUrl"), + markerClass: document.getElementById("markerClass"), + screenshotUrl: document.getElementById("screenshotUrl"), + vendor: document.getElementById("vendor"), + version: document.getElementById("version"), + pluginJar: document.getElementById("pluginJar"), + dependencyJars: document.getElementById("dependencyJars") + } + + const requiredFields = ['id', 'name', 'description', 'helpUrl', 'markerClass', 'screenshotUrl', 'vendor', 'version']; + // Get the form data. + var id = document.getElementById("id").value; + var name = document.getElementById("name").value; + var description = document.getElementById("description").value; + var helpUrl = document.getElementById("helpUrl").value; + var markerClass = document.getElementById("markerClass").value; + var screenshotUrl = document.getElementById("screenshotUrl").value; + var vendor = document.getElementById("vendor").value; + var version = document.getElementById("version").value; + var pluginJar = document.getElementById("pluginJar").files[0]; + var dependencyJars = document.getElementById("dependencyJars").files; + + var isFormFieldsEmpty; + + for (let field of requiredFields) { + if (!fields[field].value) { + isFormFieldsEmpty = true; + break; + }else{ + isFormFieldsEmpty = false; + } + } + + if (isFormFieldsEmpty === true){ + alert("All Fields are required"); + }else { + var formData = new FormData(); + formData.append("id", id); + formData.append("name", name); + formData.append("description", description); + formData.append("helpUrl", helpUrl); + formData.append("markerClass", markerClass); + formData.append("screenshotUrl", screenshotUrl); + formData.append("vendor", vendor) + formData.append("version", version); + formData.append("pluginJar", pluginJar); + + for (var i = 0; i < dependencyJars.length; i++) { + formData.append("dependencyJars", dependencyJars[i]); + } + + var xhr = new XMLHttpRequest(); + xhr.open("POST", "/v1/upload", true); + xhr.onreadystatechange = function() { + if (xhr.readyState === 4) { + if (xhr.status === 200) { + alert("Upload successful!"); + fields.id.value = ''; + fields.name.value = ''; + fields.description.value = ''; + fields.helpUrl.value = ''; + fields.markerClass.value = ''; + fields.screenshotUrl.value = ''; + fields.vendor.value = ''; + fields.version.value = ''; + fields.pluginJar.value = ''; + fields.dependencyJars.value = ''; + } else { + alert("Upload failed."); + } + } + }; + xhr.send(formData); + } +} diff --git a/src/main/resources/public/styles.css b/src/main/resources/public/styles.css new file mode 100644 index 0000000..d242574 --- /dev/null +++ b/src/main/resources/public/styles.css @@ -0,0 +1,195 @@ +body { + font-family: sans-serif; + margin: 0; + padding: 0; + background-image: linear-gradient(45deg, #b763b7, #232828, #999981); + background-repeat: no-repeat; + background-size: cover; +} + + +.form { + width: 600px; + margin: 75px auto; + padding: 20px; + background-color: #ffffff; + border-radius: 8px; + border: 1px solid #ccc; + background-image: linear-gradient(to bottom, #ab96a1, #907da5); + background-repeat: no-repeat; + background-size: cover; +} + +input { + width: 95%; + padding: 10px; + margin-bottom: 20px; + border: 1px solid #ccc; + border-radius: 4px; +} + +button { + background-color: #000; + color: #fff; + padding: 10px; + border: none; + cursor: pointer; + border-radius: 4px; +} + +select { + width: 100%; + padding: 10px; + margin-bottom: 10px; + border: 1px solid #ccc; + border-radius: 4px; +} + +h1{ + text-align: center; +} + +.form h1 { + color: #000; + font-size: 24px; + margin-bottom: 20px; +} + +.form input, .form select, .form button { + color: #000; +} + +.form input:focus, .form select:focus, .form button:focus { + outline: none; + border-color: #000; +} + +.form button { + background-color: #000; + color: #fff; + margin-left: 45% +} + +.upload-button { + padding: 0.6em 2em; + border: none; + outline: none; + color: rgb(255, 255, 255); + background: #111; + cursor: pointer; + position: relative; + z-index: 0; + border-radius: 10px; + user-select: none; + -webkit-user-select: none; + touch-action: manipulation; +} + +.upload-button:before { + content: ""; + background: linear-gradient( + 45deg, + #ff0000, + #ff7300, + #fffb00, + #48ff00, + #00ffd5, + #002bff, + #7a00ff, + #ff00c8, + #ff0000 + ); + position: absolute; + top: -2px; + left: -2px; + background-size: 400%; + z-index: -1; + filter: blur(5px); + -webkit-filter: blur(5px); + width: calc(100% + 4px); + height: calc(100% + 4px); + animation: glowing-upload-button 20s linear infinite; + transition: opacity 0.3s ease-in-out; + border-radius: 10px; +} + +@keyframes glowing-upload-button { + 0% { + background-position: 0 0; + } + 50% { + background-position: 400% 0; + } + 100% { + background-position: 0 0; + } +} + +.upload-button:after { + z-index: -1; + content: ""; + position: absolute; + width: 100%; + height: 100%; + background: #222; + left: 0; + top: 0; + border-radius: 10px; +} + +.notification .success { + display: flex; + align-items: center; +} + +.toast-content .check { + display: flex; + align-items: center; + justify-content: center; + height: 35px; + min-width: 35px; + background-color: #4070f4; + color: #fff; + font-size: 20px; + border-radius: 50%; +} + +.toast-content .message { + display: flex; + flex-direction: column; + margin: 0 20px; +} + +.message .text { + font-size: 16px; + font-weight: 400; + color: #666666; +} + + +.message .text.text-1 { + font-weight: 600; + color: #333; +} + +.toast .close { + position: absolute; + top: 10px; + right: 15px; + padding: 5px; + cursor: pointer; + opacity: 0.7; +} + +.toast .close:hover { + opacity: 1; +} + +.toast .progress { + position: absolute; + bottom: 0; + left: 0; + height: 3px; + width: 100%; + +} \ No newline at end of file diff --git a/src/test/java/com/perftalks/jmeter/UpdatePluginsTest.java b/src/test/java/com/perftalks/jmeter/UpdatePluginsTest.java index 8730293..08568d2 100644 --- a/src/test/java/com/perftalks/jmeter/UpdatePluginsTest.java +++ b/src/test/java/com/perftalks/jmeter/UpdatePluginsTest.java @@ -1,14 +1,9 @@ package com.perftalks.jmeter; import java.io.IOException; -import java.io.InputStream; import java.net.MalformedURLException; import java.util.Properties; -//import org.junit.Test; -import com.perftalks.jmeter.app.App; -import com.perftalks.jmeter.app.PluginsManager; - public final class UpdatePluginsTest { private static Properties props = new Properties(); @@ -16,18 +11,5 @@ public final class UpdatePluginsTest { //@Test public void executePluginsManager() throws MalformedURLException, IOException { - try { - InputStream inputStream = PluginsManager.class.getClassLoader().getResourceAsStream("config.properties"); - props.load(inputStream); - System.out.println("Loading Properties completed"); - } catch (IOException e) { - e.printStackTrace(); - } - - if (!props.isEmpty()) { - System.out.println("Starting Local Plugins Manager"); - new App(props).execute(); - - } } }