/** * Copyright (c) 2014 Marc Fiume <mfiume@cs.toronto.edu> * Unauthorized use of this file is strictly prohibited. * * All rights reserved. No warranty, explicit or implicit, provided. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ package org.ut.biolab.medsavant.client.app; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamReader; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.ut.biolab.medsavant.client.plugin.AppDescriptor; import org.ut.biolab.medsavant.shared.util.VersionSettings; import org.ut.biolab.medsavant.shared.util.WebResources; import org.ut.biolab.medsavant.client.app.api.AppInfoFetcher; /** * * @author mfiume */ public class MedSavantAppFetcher implements AppInfoFetcher { //This could also be dynamic, and editable by the user via some kind of settings dialog. private static Log LOG = LogFactory.getLog(MedSavantAppFetcher.class); private static final String[] REQUIRED_PAIRS = new String[]{ "url", "name", "version", "sdk-version", "category", "author", "description" }; private List<AppInfo> appInfo; private enum XMLElement { PLUGINS, PLUGIN, AUTHOR, WEBPAGE, DESCRIPTION, SHORTDESCRIPTION, NEWINVERSION, ERROR; } private enum XMLPluginAttribute { URL, NAME, VERSION, SDK_VERSION { @Override public String toString() { return "sdk-version"; } }, CATEGORY; } private static MedSavantAppFetcher.XMLElement readElement(XMLStreamReader reader) { try { String elemName = reader.getLocalName().toUpperCase(); return Enum.valueOf(XMLElement.class, elemName); } catch (IllegalArgumentException ignored) { // Any elements not in our enum will just be ignored. return XMLElement.ERROR; } } private AppInfo getAppInfo(Map<String, String> pluginMap, String repositoryURL) throws MalformedURLException, IOException { //check that all required name/value pairs are set: for (String key : REQUIRED_PAIRS) { if (pluginMap.get(key) == null) { String n = pluginMap.get("name"); if (n == null) { n = "<Unknown Plugin>"; } throw new IOException("Invalid plugin repository xml at " + repositoryURL + ", plugin " + n + " is missing value for '" + key + "'"); } } String name = pluginMap.get("name"); String version = pluginMap.get("version"); String compatibleWith = pluginMap.get("sdk-version"); String category = pluginMap.get("category"); if (category != null) { category = Enum.valueOf(AppDescriptor.Category.class, category.toUpperCase()).toString(); } String description = pluginMap.get("description"); String shortdesc = pluginMap.get("shortdescription"); String newinversion = pluginMap.get("newinversion"); String author = pluginMap.get("author"); String web = pluginMap.get("webpage"); URL downloadURL = new URL(pluginMap.get("url")); LOG.info("Located plugin " + name + " in App repository"); return new AppInfo(name, version, category, compatibleWith, shortdesc, newinversion, description, author, web, downloadURL); } private void refreshAppInfo(String[] pluginRepositoryUrls) throws Exception { appInfo = new LinkedList<AppInfo>(); //query the server for XML file describing the apps. for (String repositoryURL : pluginRepositoryUrls) { XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(new URL(repositoryURL).openConnection().getInputStream()); Map<String, String> pluginMap = new HashMap<String, String>(); XMLElement currentElement = null; String currentText = ""; do { switch (reader.next()) { case XMLStreamConstants.START_ELEMENT: currentElement = readElement(reader); currentText = ""; switch (currentElement) { case PLUGINS: case PLUGIN: pluginMap.clear(); for (XMLPluginAttribute a : XMLPluginAttribute.values()) { String attrName = a.toString().toLowerCase(); String attrVal = reader.getAttributeValue(null, attrName); pluginMap.put(attrName, attrVal); } break; case AUTHOR: case WEBPAGE: case DESCRIPTION: case SHORTDESCRIPTION: case NEWINVERSION: String val = reader.getAttributeValue(null, "value"); if (val != null) { pluginMap.put(currentElement.toString().toLowerCase(), val); } break; default: throw new IOException("Invalid plugin repository xml at " + repositoryURL); } break; case XMLStreamConstants.END_ELEMENT: if (reader.getName().toString().equalsIgnoreCase(XMLElement.PLUGIN.toString())) { AppInfo ai = getAppInfo(pluginMap, repositoryURL); appInfo.add(ai); } else if (currentElement != null && currentText.length() > 0) { pluginMap.put(currentElement.toString().toLowerCase(), currentText); } break; case XMLStreamConstants.CHARACTERS: currentText += reader.getText().trim().replace("\t", ""); break; case XMLStreamConstants.END_DOCUMENT: reader.close(); reader = null; break; } } while (reader != null); } LOG.info("Found " + appInfo.size() + " App descriptions"); } @Override public List<AppInfo> fetchApplicationInformation(String search) throws Exception { if (appInfo == null) { refreshAppInfo(WebResources.PLUGIN_REPOSITORY_URLS); } if (search == null || search.length() < 1) { return appInfo; } List<AppInfo> results = new LinkedList<AppInfo>(); //search names first - those hits will be listed first. for (AppInfo ai : appInfo) { if (ai.getName().contains(search)) { if (VersionSettings.isAppSDKCompatibleWithClient(ai.getSDKVersion(),VersionSettings.getVersionString())) { results.add(ai); } } } //Search description, shortdescription, and newinversion next. for (AppInfo ai : appInfo) { if (ai.getDescription().contains(search) || ai.getShortDescription().contains(search) || ai.getNewInVersion().contains(search)) { if (VersionSettings.isAppSDKCompatibleWithClient(ai.getSDKVersion(),VersionSettings.getVersionString())) { results.add(ai); } } } return results; } public static void main(String[] args) { if (args.length < 1) { System.err.println("USAGE: MedSavantAppFetcher <URLs to test>"); System.err.println("Local files can be specified using file:// (e.g. file:///tmp/pluginDirectory.xml)"); } try { MedSavantAppFetcher m = new MedSavantAppFetcher(); m.refreshAppInfo(args); } catch (Exception ex) { System.err.println("Exception caught " + ex); ex.printStackTrace(); System.exit(1); } System.exit(0); } }