001/** 002 * Copyright (C) 2014 Universidade de Aveiro, DETI/IEETA, Bioinformatics Group - http://bioinformatics.ua.pt/ 003 * 004 * This file is part of Dicoogle/dicoogle. 005 * 006 * Dicoogle/dicoogle is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Dicoogle/dicoogle is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Dicoogle. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package pt.ua.dicoogle.plugins.webui; 020 021import java.io.BufferedReader; 022import java.io.File; 023import java.io.FileInputStream; 024import java.io.FileReader; 025import java.io.IOException; 026import java.io.InputStream; 027import java.io.InputStreamReader; 028import java.io.Reader; 029import java.util.Collection; 030import java.util.Collections; 031import java.util.Enumeration; 032import java.util.HashMap; 033import java.util.HashSet; 034import java.util.Map; 035import java.util.Set; 036import java.util.regex.Pattern; 037import java.util.zip.ZipEntry; 038import java.util.zip.ZipFile; 039import net.sf.json.JSONObject; 040import net.sf.json.JSONSerializer; 041import org.slf4j.LoggerFactory; 042import org.slf4j.Logger; 043 044/** A class type for managing web UI plugins. 045 * 046 * @author Eduardo Pinho <eduardopinho@ua.pt> 047 */ 048public class WebUIPluginManager { 049 050 private static final Logger logger = LoggerFactory.getLogger(WebUIPluginManager.class); 051 052 private static class WebUIEntry { 053 public final WebUIPlugin plugin; 054 public final String directory; 055 public final String zipPath; 056 057 public WebUIEntry(WebUIPlugin plugin, String directory, String zipPath) { 058 assert plugin != null; 059 assert directory != null; 060 this.plugin = plugin; 061 this.directory = directory; 062 this.zipPath = zipPath; 063 } 064 public WebUIEntry(WebUIPlugin plugin, String directory) { 065 this(plugin, directory, null); 066 } 067 public boolean isZipped() { 068 return this.zipPath != null; 069 } 070 public InputStream readFile(String file) throws IOException { 071 if (this.isZipped()) { 072 ZipFile zip = new ZipFile(this.zipPath); 073 return zip.getInputStream(zip.getEntry(this.directory + File.separatorChar + file)); 074 } else { 075 File f = new File(this.directory + File.separatorChar + file); 076 return new FileInputStream(f); 077 } 078 } 079 080 } 081 082 private final Map<String, WebUIEntry> plugins; 083 private final Set<WebUIPlugin> justPlugins; 084 085 public WebUIPluginManager() { 086 this.plugins = new HashMap<>(); 087 this.justPlugins = new HashSet<>(); 088 } 089 090 public void loadAll(File directory) { 091 assert directory != null; 092 if (!directory.exists()) { 093 logger.debug("No web plugins directory, ignoring"); 094 return; 095 } 096 if (!directory.isDirectory()) { 097 logger.warn("Can't load web UI plugins, file {} is not a directory", directory); 098 return; 099 } 100 101 for (File f : directory.listFiles()) { 102 if (!f.isDirectory()) continue; 103 try { 104 WebUIPlugin plugin = this.load(f); 105 logger.info("Loaded web plugin: {}", plugin.getName()); 106 } catch (IOException ex) { 107 logger.error("Attempt to load plugin at {} failed", f.getName(), ex); 108 } catch (PluginFormatException ex) { 109 logger.warn("Could not load plugin at {} failed", f.getName(), ex); 110 } 111 } 112 } 113 114 public WebUIPlugin load(File directory) throws IOException, PluginFormatException { 115 assert directory != null; 116 assert directory.isDirectory(); 117 final String dirname = directory.getCanonicalPath(); 118 File packageJSON = new File(dirname + File.separatorChar + "package.json"); 119 try (BufferedReader reader = new BufferedReader(new FileReader(packageJSON))) { 120 String acc = ""; 121 String line; 122 while ((line = reader.readLine()) != null) { 123 acc += line; 124 } 125 WebUIPlugin plugin = WebUIPlugin.fromPackageJSON((JSONObject)JSONSerializer.toJSON(acc)); 126 File moduleFile = new File(directory.getAbsolutePath() + File.separatorChar + plugin.getModuleFile()); 127 if (!moduleFile.canRead()) { 128 throw new IOException("Module file " + moduleFile.getName() + " cannot be read"); 129 } 130 this.plugins.put(plugin.getName(), new WebUIEntry(plugin, dirname)); 131 this.justPlugins.add(plugin); 132 return plugin; 133 } 134 } 135 136 /** Load all web plugins from a zip or jar file. 137 * @param pluginZip the zip file containing the plugins 138 * @throws java.io.IOException if the zip file can not be read 139 */ 140 public void loadAllFromZip(ZipFile pluginZip) throws IOException { 141 assert pluginZip != null; 142 logger.trace("Discovering web UI plugins in {} ...", pluginZip.getName()); 143 Pattern pckDescrMatcher = Pattern.compile("WebPlugins\\" + File.separatorChar + "(\\p{Alnum}|\\-|\\_)+\\" + File.separatorChar + "package.json"); 144 Enumeration<? extends ZipEntry> entries = pluginZip.entries(); 145 final int DIRNAME_TAIL = "/package.json".length(); 146 while (entries.hasMoreElements()) { 147 ZipEntry e = entries.nextElement(); 148 if (pckDescrMatcher.matcher(e.getName()).matches()) { 149 String dirname = e.getName().substring(0, e.getName().length() - DIRNAME_TAIL); 150 logger.info("Found web UI plugin in {} at \"{}\"", pluginZip.getName(), dirname); 151 try (BufferedReader reader = new BufferedReader(new InputStreamReader(pluginZip.getInputStream(e)))) { 152 String acc = ""; 153 String line; 154 while ((line = reader.readLine()) != null) { 155 acc += line; 156 } 157 WebUIPlugin plugin = WebUIPlugin.fromPackageJSON((JSONObject)JSONSerializer.toJSON(acc)); 158 this.plugins.put(plugin.getName(), new WebUIEntry(plugin, dirname, pluginZip.getName())); 159 this.justPlugins.add(plugin); 160 } catch (PluginFormatException ex) { 161 logger.warn("Failed to load plugin at \"{}\": {}", e.getName(), ex.getMessage()); 162 } 163 } 164 } 165 } 166 167 /** Get the descriptor of a web UI plugin. 168 * @param name the name of the plugin 169 * @return a copy of the installed web UI plugin 170 */ 171 public WebUIPlugin get(String name) { 172 WebUIPlugin plugin = this.plugins.get(name).plugin; 173 return plugin == null ? null : plugin.copy(); 174 } 175 176 /** Retrieve and return the original JSON object of the plugin. 177 * @param name the name of the plugin 178 * @return a JSON object of the original "package.json" 179 * @throws IOException on error reading "package.json" 180 */ 181 public JSONObject retrieveJSON(String name) throws IOException { 182 return retrieveJSON(readFile(name, "package.json")); 183 } 184 185 /** Retrieve and return the original JSON object of the plugin. 186 * @param reader a reader providing the JSON object 187 * @return a JSON object of the original "package.json" 188 * @throws IOException on error reading "package.json" 189 */ 190 private JSONObject retrieveJSON(Reader reader) throws IOException { 191 try (BufferedReader bufreader = new BufferedReader(reader)) { 192 String acc = ""; 193 String line; 194 while ((line = bufreader.readLine()) != null) { 195 acc += line; 196 } 197 try { 198 return (JSONObject)JSONSerializer.toJSON(acc); 199 } catch (ClassCastException ex) { 200 throw new IOException("Not a JSON object", ex); 201 } 202 } 203 } 204 205 /** Retrieve and return the original JSON object of the plugin. 206 * @param istream an input stream providing the JSON object 207 * @return a JSON object of the original "package.json" 208 * @throws IOException on error reading "package.json" 209 */ 210 private JSONObject retrieveJSON(InputStream istream) throws IOException { 211 return retrieveJSON(new InputStreamReader(istream)); 212 } 213 214 public String retrieveModuleJS(String name) throws IOException { 215 String moduleFile = this.plugins.get(name).plugin.getModuleFile(); 216 try (BufferedReader reader = new BufferedReader(new InputStreamReader(readFile(name, moduleFile)))) { 217 String acc = ""; 218 String line; 219 while ((line = reader.readLine()) != null) { 220 acc += line + '\n'; 221 } 222 return acc; 223 } 224 } 225 226 public Collection<WebUIPlugin> pluginSet() { 227 return Collections.unmodifiableSet(justPlugins); 228 } 229 230 public void loadSettings(File settingsFolder) { 231 for (WebUIPlugin plugin : pluginSet()) { 232 try { 233 File pluginSettingsFile = new File(settingsFolder.getPath() + File.separatorChar + plugin.getName() + ".json"); 234 if (!pluginSettingsFile.exists()) { 235 logger.info("Web plugin {} has no settings file", plugin.getName()); 236 continue; 237 } 238 BufferedReader reader = new BufferedReader(new FileReader(pluginSettingsFile)); 239 String acc = ""; 240 String line; 241 while ((line = reader.readLine()) != null) { 242 acc += line; 243 } 244 JSONObject settings = (JSONObject) JSONSerializer.toJSON(acc); 245 plugin.setSettings(settings); 246 } catch (IOException ex) { 247 logger.error("Failed to load web plugin settings", ex); 248 } 249 } 250 } 251 252 /** Read a file from an installed plugin's directory. 253 * 254 * @param pluginName the name of the plugin 255 * @param filename the relative path of the file 256 * @return an input stream with the file's content 257 * @throws IOException if the file does not exist or can not be read 258 */ 259 private InputStream readFile(String pluginName, String filename) throws IOException { 260 WebUIEntry e = this.plugins.get(pluginName); 261 if (e == null) { 262 throw new IllegalArgumentException("No such web UI plugin"); 263 } 264 return e.readFile(filename); 265 } 266}