Java tutorial
/* * Copyright 2010-2012 University of Toronto * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.ut.biolab.medsavant.client.plugin; import org.ut.biolab.medsavant.shared.util.RemoteFileCache; import org.ut.biolab.medsavant.shared.util.NetworkUtils; import java.io.*; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.ut.biolab.medsavant.client.settings.VersionSettings; import org.ut.biolab.medsavant.client.settings.DirectorySettings; import org.ut.biolab.medsavant.client.util.ClientIOUtils; import org.ut.biolab.medsavant.client.util.ClientMiscUtils; import org.ut.biolab.medsavant.client.util.ClientNetworkUtils; import org.ut.biolab.medsavant.client.util.Controller; import org.ut.biolab.medsavant.client.view.util.DialogUtils; /** * Plugin controller ported over from Savant. * * @author mfiume, tarkvara */ public class PluginController extends Controller { private static final Log LOG = LogFactory.getLog(PluginController.class); private static final String UNINSTALL_FILENAME = ".uninstall_plugins"; private static PluginController instance; private File uninstallFile; private List<String> pluginsToRemove = new ArrayList<String>(); private Map<String, PluginDescriptor> knownPlugins = new HashMap<String, PluginDescriptor>(); private Map<String, MedSavantPlugin> loadedPlugins = new HashMap<String, MedSavantPlugin>(); private Map<String, String> pluginErrors = new LinkedHashMap<String, String>(); private PluginLoader pluginLoader; private PluginIndex repositoryIndex = null; /** SINGLETON **/ public static synchronized PluginController getInstance() { if (instance == null) { instance = new PluginController(); } return instance; } /** * Private constructor. Should only be called by getInstance(). */ private PluginController() { try { uninstallFile = new File(DirectorySettings.getMedSavantDirectory(), UNINSTALL_FILENAME); LOG.debug(String.format("Uninstall list %s.", UNINSTALL_FILENAME)); if (uninstallFile.exists()) { deleteFileList(uninstallFile); } copyBuiltInPlugins(); } catch (Exception ex) { LOG.error("Error loading plugins.", ex); } } /** * Try to load all JAR files in the given directory. */ public void loadPlugins(File pluginsDir) { LOG.info("Loading plugins in " + pluginsDir.getAbsolutePath()); File[] files = pluginsDir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".jar"); } }); for (File f : files) { try { LOG.info("Loading plugin at " + f.getAbsolutePath()); addPlugin(f); } catch (PluginVersionException x) { LOG.warn(String.format("No compatible plugins found in %s.", f)); } } // Check to see if we have any outdated plugins. if (pluginErrors.size() > 0) { List<String> updated = new ArrayList<String>(); for (String s : pluginErrors.keySet()) { // Plugin is invalid, and we don't have a newer version. if (checkForPluginUpdate(s)) { updated.add(s); } } if (updated.size() > 0) { DialogUtils.displayMessage("Plugins Updated", String.format( "<html>The following plugins were updated to be compatible with MedSavant %s:<br><br><i>%s</i></html>", VersionSettings.getVersionString(), ClientMiscUtils.join(updated, ", "))); for (String s : updated) { pluginErrors.remove(s); } } if (pluginErrors.size() > 0) { StringBuilder errorStr = null; for (String s : pluginErrors.keySet()) { if (errorStr == null) { errorStr = new StringBuilder(); } else { errorStr.append("<br>"); } errorStr.append(s); errorStr.append(" "); errorStr.append(pluginErrors.get(s)); } if (errorStr != null) { // The following dialog will only report plugins which we can tell are faulty before calling loadPlugin(), typically // by checking the version in plugin.xml. DialogUtils.displayMessage("Plugins Not Loaded", String.format( "<html>The following plugins could not be loaded:<br><br><i>%s</i><br><br>They will not be available to MedSavant.</html>", errorStr)); } } } Set<URL> jarURLs = new HashSet<URL>(); for (PluginDescriptor desc : knownPlugins.values()) { try { if (!pluginErrors.containsKey(desc.getID())) { jarURLs.add(desc.getFile().toURI().toURL()); } } catch (MalformedURLException ignored) { } } if (jarURLs.size() > 0) { pluginLoader = new PluginLoader(jarURLs.toArray(new URL[0]), getClass().getClassLoader()); for (final PluginDescriptor desc : knownPlugins.values()) { if (!pluginErrors.containsKey(desc.getID())) { new Thread("PluginLoader-" + desc) { @Override public void run() { try { loadPlugin(desc); } catch (Throwable x) { LOG.error(String.format("Unable to load %s.", desc.getName()), x); pluginErrors.put(desc.getID(), x.getClass().getName()); fireEvent(new PluginEvent(PluginEvent.Type.ERROR, desc.getID())); } } }.start(); } } } } public List<PluginDescriptor> getDescriptors() { List<PluginDescriptor> result = new ArrayList<PluginDescriptor>(); result.addAll(knownPlugins.values()); Collections.sort(result); return result; } public List<PluginDescriptor> getDescriptorsOfType(PluginDescriptor.Type t) { List<PluginDescriptor> result = new ArrayList<PluginDescriptor>(); for (PluginDescriptor desc : knownPlugins.values()) { if (desc.getType() == t) { result.add(desc); } } Collections.sort(result); return result; } /** * @deprecated */ public void getGeneManiaData() { Runnable r = new Runnable() { @Override public void run() { String directoryPath = DirectorySettings.getCacheDirectory().getAbsolutePath(); if (!(new File(directoryPath + "/done.txt")).exists()) { URL pathToGMData = NetworkUtils .getKnownGoodURL("http://genomesavant.com/serve/data/genemania/gmdata.zip"); System.out.println("Downloding GeneMania data from " + pathToGMData.toString()); try { if (true) { throw new IOException( "Temporarily preventing gm data from downloading. Because it's so large it should only be downloaded once and on demand"); } File data = RemoteFileCache.getCacheFile(pathToGMData); System.out.println("data is" + data.getAbsolutePath()); ZipFile zipData = new ZipFile(data.getAbsolutePath()); Enumeration entries = zipData.entries(); while (entries.hasMoreElements()) { ZipEntry entry = (ZipEntry) entries.nextElement(); if (entry.isDirectory()) { (new File(directoryPath + "/" + entry.getName())).mkdirs(); continue; } //System.err.println("Extracting file: " + entry.getName()); copyInputStream(zipData.getInputStream(entry), new BufferedOutputStream( new FileOutputStream(directoryPath + "/" + entry.getName()))); } zipData.close(); FileWriter fstream = new FileWriter(directoryPath + "/done.txt"); BufferedWriter out = new BufferedWriter(fstream); out.write("This file indicates that the GeneMANIA data has finished downloading."); out.close(); } catch (IOException ex) { Logger.getLogger(PluginController.class.getName()).log(Level.SEVERE, null, ex); } } } }; Thread t = new Thread(r); t.start(); } /** * @deprecated */ private static final void copyInputStream(InputStream in, OutputStream out) throws IOException { byte[] buffer = new byte[1024]; int len; while ((len = in.read(buffer)) >= 0) { out.write(buffer, 0, len); } in.close(); out.close(); } public MedSavantPlugin getPlugin(String id) { return loadedPlugins.get(id); } public boolean queuePluginForRemoval(String id) { FileWriter fstream = null; boolean success = false; try { PluginDescriptor info = knownPlugins.get(id); LOG.info(String.format("Adding plugin %s to uninstall list %s.", info.getFile().getAbsolutePath(), uninstallFile.getPath())); if (!uninstallFile.exists()) { uninstallFile.createNewFile(); } fstream = new FileWriter(uninstallFile, true); BufferedWriter out = new BufferedWriter(fstream); out.write(info.getFile().getAbsolutePath() + "\n"); out.close(); pluginsToRemove.add(id); fireEvent(new PluginEvent(PluginEvent.Type.QUEUED_FOR_REMOVAL, id)); success = true; } catch (IOException ex) { LOG.error(String.format("Error uninstalling plugin: %s.", uninstallFile), ex); } finally { try { fstream.close(); } catch (IOException ignored) { } } return success; } public boolean isPluginQueuedForRemoval(String id) { return pluginsToRemove.contains(id); } public String getPluginStatus(String id) { if (pluginsToRemove.contains(id)) { return "Queued for removal"; } if (loadedPlugins.get(id) != null) { return "Loaded"; } String err = pluginErrors.get(id); if (err != null) { return err; } if (knownPlugins.get(id) != null) { // Plugin is valid, but hasn't shown up in the loadedPlugins map. return "Loading"; } return "Unknown"; } private void deleteFileList(File fileListFile) { BufferedReader br = null; String line = ""; try { br = new BufferedReader(new FileReader(fileListFile)); while ((line = br.readLine()) != null) { LOG.info(String.format("Uninstalling %s.", line)); if (!new File(line).delete()) { throw new IOException("Delete of " + line + " failed"); } } } catch (IOException ex) { LOG.error(String.format("Problem uninstalling %s.", line), ex); } finally { try { br.close(); } catch (IOException ex) { } } fileListFile.delete(); } private void copyBuiltInPlugins() { File destDir = DirectorySettings.getPluginsDirectory(); File srcDir = null; if (ClientMiscUtils.MAC) { srcDir = new File(com.apple.eio.FileManager.getPathToApplicationBundle() + "/Contents/Plugins"); if (srcDir.exists()) { try { ClientIOUtils.copyDir(srcDir, destDir); return; } catch (Exception ignored) { // We should expect to see this when running in the debugger. } } } try { srcDir = new File("plugins"); ClientIOUtils.copyDir(srcDir, destDir); } catch (Exception x) { LOG.error(String.format("Unable to copy builtin plugins from %s to %s.", srcDir.getAbsolutePath(), destDir), x); } } private void loadPlugin(PluginDescriptor desc) throws Throwable { LOG.debug(String.format("loadPlugin(\"%s\")", desc.getID())); Class pluginClass = pluginLoader.loadClass(desc.getClassName()); MedSavantPlugin plugin = (MedSavantPlugin) pluginClass.newInstance(); plugin.setDescriptor(desc); loadedPlugins.put(desc.getID(), plugin); LOG.debug(String.format("Firing LOADED event to %s listeners.", listeners.size())); fireEvent(new PluginEvent(PluginEvent.Type.LOADED, desc.getID())); } /** * Try to add a plugin from the given file. It is inserted into our internal * data structures, but not yet loaded. */ public PluginDescriptor addPlugin(File f) throws PluginVersionException { LOG.info(String.format("Loading plugin from %s", f.getAbsolutePath())); PluginDescriptor desc = PluginDescriptor.fromFile(f); if (desc != null) { LOG.debug(String.format("Found usable %s in %s.", desc, f.getName())); PluginDescriptor existingDesc = knownPlugins.get(desc.getID()); if (existingDesc != null && existingDesc.getVersion().compareTo(desc.getVersion()) >= 0) { LOG.debug(String.format(" Ignored %s due to presence of existing %s.", desc, existingDesc)); return null; } knownPlugins.put(desc.getID(), desc); if (desc.isCompatible()) { if (existingDesc != null) { LOG.debug(String.format(" Replaced %s.", existingDesc)); pluginErrors.remove(desc.getID()); } } else { LOG.debug(String.format("Found incompatible %s (SDK version %s) in %s.", desc, desc.getSDKVersion(), f.getName())); pluginErrors.put(desc.getID(), "Invalid SDK version (" + desc.getSDKVersion() + ")"); throw new PluginVersionException("Invalid SDK version (" + desc.getSDKVersion() + ")"); } } return desc; } /** * Copy the given file to the plugins directory, add it, and load it. * @param selectedFile */ public void installPlugin(File selectedFile) throws Throwable { File pluginFile = new File(DirectorySettings.getPluginsDirectory(), selectedFile.getName()); LOG.info("Copying file " + selectedFile.getAbsolutePath() + " to " + pluginFile.getAbsolutePath()); ClientIOUtils.copyFile(selectedFile, pluginFile); LOG.info("Getting plugin information..."); PluginDescriptor desc = addPlugin(pluginFile); LOG.info("Got plugin information"); if (desc != null) { LOG.info("Loading plugin..."); if (pluginLoader == null) { pluginLoader = new PluginLoader(new URL[] { pluginFile.toURI().toURL() }, getClass().getClassLoader()); } pluginLoader.addJar(pluginFile); loadPlugin(desc); LOG.info("Done loading plugin"); } } private boolean checkForPluginUpdate(String id) { try { if (repositoryIndex == null) { repositoryIndex = new PluginIndex(VersionSettings.PLUGIN_URL); } URL updateURL = repositoryIndex.getPluginURL(id); if (updateURL != null) { LOG.debug(String.format("Downloading updated version of %s from %s.", id, updateURL)); addPlugin( ClientNetworkUtils.downloadFile(updateURL, DirectorySettings.getPluginsDirectory(), null)); return true; } } catch (IOException x) { LOG.error(String.format("Unable to install update for %s.", id), x); } catch (PluginVersionException x) { LOG.error(String.format("Update for %s not loaded.", id)); } return false; } class PluginLoader extends URLClassLoader { PluginLoader(URL[] urls, ClassLoader parent) { super(urls, parent); } void addJar(File f) { try { addURL(f.toURI().toURL()); } catch (MalformedURLException ignored) { } } } }