001 /* 002 * $Id: ResourceManager.java,v 1.15 2012/02/19 17:35:53 davep Exp $ 003 * 004 * This file is part of McIDAS-V 005 * 006 * Copyright 2007-2012 007 * Space Science and Engineering Center (SSEC) 008 * University of Wisconsin - Madison 009 * 1225 W. Dayton Street, Madison, WI 53706, USA 010 * https://www.ssec.wisc.edu/mcidas 011 * 012 * All Rights Reserved 013 * 014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and 015 * some McIDAS-V source code is based on IDV and VisAD source code. 016 * 017 * McIDAS-V is free software; you can redistribute it and/or modify 018 * it under the terms of the GNU Lesser Public License as published by 019 * the Free Software Foundation; either version 3 of the License, or 020 * (at your option) any later version. 021 * 022 * McIDAS-V is distributed in the hope that it will be useful, 023 * but WITHOUT ANY WARRANTY; without even the implied warranty of 024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 025 * GNU Lesser Public License for more details. 026 * 027 * You should have received a copy of the GNU Lesser Public License 028 * along with this program. If not, see http://www.gnu.org/licenses. 029 */ 030 031 package edu.wisc.ssec.mcidasv; 032 033 import static ucar.unidata.xml.XmlUtil.getAttribute; 034 import static ucar.unidata.xml.XmlUtil.getChildText; 035 import static ucar.unidata.xml.XmlUtil.getElements; 036 037 import java.io.File; 038 import java.io.IOException; 039 import java.io.InputStream; 040 import java.util.ArrayList; 041 import java.util.Hashtable; 042 import java.util.LinkedHashMap; 043 import java.util.List; 044 import java.util.Map; 045 046 import org.w3c.dom.Attr; 047 import org.w3c.dom.Element; 048 import org.w3c.dom.NamedNodeMap; 049 import org.w3c.dom.NodeList; 050 051 import ucar.unidata.idv.IdvResourceManager; 052 import ucar.unidata.idv.IntegratedDataViewer; 053 import ucar.unidata.idv.StateManager; 054 import ucar.unidata.util.IOUtil; 055 import ucar.unidata.util.ResourceCollection; 056 import ucar.unidata.util.ResourceCollection.Resource; 057 import ucar.unidata.util.StringUtil; 058 059 /** 060 * @author McIDAS-V Team 061 * @version $Id: ResourceManager.java,v 1.15 2012/02/19 17:35:53 davep Exp $ 062 */ 063 public class ResourceManager extends IdvResourceManager { 064 065 /** Points to the adde image defaults */ 066 public static final XmlIdvResource RSC_PARAMETERSETS = 067 new XmlIdvResource("idv.resource.parametersets", 068 "Chooser Parameter Sets", "parametersets\\.xml$"); 069 070 public static final IdvResource RSC_SITESERVERS = 071 new XmlIdvResource("mcv.resource.siteservers", 072 "Site-specific Servers", "siteservers\\.xml$"); 073 074 public static final IdvResource RSC_NEW_USERSERVERS = 075 new XmlIdvResource("mcv.resource.newuserservers", 076 "New style user servers", "persistedservers\\.xml$"); 077 078 public static final IdvResource RSC_OLD_USERSERVERS = 079 new XmlIdvResource("mcv.resource.olduserservers", 080 "Old style user servers", "addeservers\\.xml$"); 081 082 public ResourceManager(IntegratedDataViewer idv) { 083 super(idv); 084 checkMoveOutdatedDefaultBundle(); 085 } 086 087 /** 088 * Overridden so that McIDAS-V can attempt to verify {@literal "critical"} 089 * resources without causing crashes. 090 * 091 * <p>Currently doesn't do a whole lot. 092 * 093 * @see #verifyResources() 094 */ 095 @Override protected void init(List rbiFiles) { 096 super.init(rbiFiles); 097 // verifyResources(); 098 } 099 100 /** 101 * Loops through all of the {@link ResourceCollection}s that the IDV knows 102 * about. 103 * 104 * <p>I realize that this could balloon into a really tedious thing... 105 * there could potentially be verification steps for each type of resource 106 * collection! the better approach is probably to identify a few key collections 107 * (like the (default?) maps). 108 */ 109 protected void verifyResources() { 110 List<IdvResource> resources = new ArrayList<IdvResource>(getResources()); 111 for (IdvResource resource : resources) { 112 ResourceCollection rc = getResources(resource); 113 System.err.println("Resource ID:"+resource); 114 for (int i = 0; i < rc.size(); i++) { 115 String path = (String)rc.get(i); 116 System.err.println(" path="+path+" exists:"+isPathValid(path)); 117 } 118 } 119 } 120 121 /** 122 * Pretty much relies upon {@link IOUtil#getInputStream(String, Class)} 123 * to determine if {@code path} exists. 124 * 125 * @param path Path to an arbitrary file. It can be a remote URL, normal 126 * file on disk, or a file included in a JAR. Just so long as it's not 127 * {@code null}! 128 * 129 * @return {@code true} <i>iff</i> there were no problems. {@code false} 130 * otherwise. 131 */ 132 private boolean isPathValid(final String path) { 133 try { 134 InputStream s = IOUtil.getInputStream(path, getClass()); 135 if (s == null) 136 return false; 137 } catch (IOException e) { 138 return false; 139 } 140 return true; 141 } 142 143 /** 144 * Adds support for McIDAS-V macros. Specifically: 145 * <ul> 146 * <li>{@link Constants#MACRO_VERSION}</li> 147 * </ul> 148 * 149 * @param path Path that contains a macro to be translated. 150 * 151 * @return Resource with our macros applied. 152 * 153 * @see IdvResourceManager#getResourcePath(String) 154 */ 155 @Override public String getResourcePath(String path) { 156 String retPath = path; 157 if (path.contains(Constants.MACRO_VERSION)) { 158 retPath = StringUtil.replace( 159 path, 160 Constants.MACRO_VERSION, 161 ((edu.wisc.ssec.mcidasv.StateManager)getStateManager()).getMcIdasVersion()); 162 } else { 163 retPath = super.getResourcePath(path); 164 } 165 return retPath; 166 } 167 168 /** 169 * Look for existing "default.mcv" and "default.xidv" bundles in root userpath 170 * If they exist, move them to the "bundles" directory, preferring "default.mcv" 171 */ 172 private void checkMoveOutdatedDefaultBundle() { 173 String userDirectory = getIdv().getObjectStore().getUserDirectory().toString(); 174 175 File defaultDir; 176 File defaultNew; 177 File defaultIdv; 178 File defaultMcv; 179 180 String os = System.getProperty("os.name"); 181 if (os == null) 182 throw new RuntimeException(); 183 if (os.startsWith("Windows")) { 184 defaultDir = new File(userDirectory + "\\bundles\\General"); 185 defaultNew = new File(defaultDir.toString() + "\\Default.mcv"); 186 defaultIdv = new File(userDirectory + "\\default.xidv"); 187 defaultMcv = new File(userDirectory + "\\default.mcv"); 188 } 189 else { 190 defaultDir = new File(userDirectory + "/bundles/General"); 191 defaultNew = new File(defaultDir.toString() + "/Default.mcv"); 192 defaultIdv = new File(userDirectory + "/default.xidv"); 193 defaultMcv = new File(userDirectory + "/default.mcv"); 194 } 195 196 // If no Alpha default bundles exist, bail quickly 197 if (!defaultIdv.exists() && !defaultMcv.exists()) return; 198 199 // If the destination directory does not exist, create it. 200 if (!defaultDir.exists()) { 201 if (!defaultDir.mkdirs()) { 202 System.err.println("Cannot create directory " + defaultDir.toString() + " for default bundle"); 203 return; 204 } 205 } 206 207 // If the destination already exists, print lame error message and bail. 208 // This whole check should only happen with Alphas so no biggie right? 209 if (defaultNew.exists()) { 210 System.err.println("Cannot copy current default bundle... " + defaultNew.toString() + " already exists"); 211 return; 212 } 213 214 // If only default.xidv exists, try to rename it. 215 // If both exist, delete the default.xidv file. It was being ignored anyway. 216 if (defaultIdv.exists()) { 217 if (defaultMcv.exists()) { 218 defaultIdv.delete(); 219 } 220 else { 221 if (!defaultIdv.renameTo(defaultNew)) { 222 System.out.println("Cannot copy current default bundle... error renaming " + defaultIdv.toString()); 223 } 224 } 225 } 226 227 // If only default.mcv exists, try to rename it. 228 if (defaultMcv.exists()) { 229 if (!defaultMcv.renameTo(defaultNew)) { 230 System.out.println("Cannot copy current default bundle... error renaming " + defaultMcv.toString()); 231 } 232 } 233 } 234 235 /** 236 * Checks an individual map resource (typically from {@code RSC_MAPS}) to 237 * verify that all of the specified maps exist? 238 * 239 * <p>Currently a no-op. The intention is to return a {@code List} so that the 240 * set of missing resources can eventually be sent off in a support 241 * request... 242 * 243 * <p>We could also decide to allow the user to search the list of plugins 244 * or ignore any missing resources (simply remove the bad stuff from the list of available xml). 245 * 246 * @param path Path to a map resource. URLs are allowed, but {@code null} is not. 247 * 248 * @return List of map paths that could not be read. If there were no 249 * errors the list is merely empty. 250 * 251 * @see IdvResourceManager#RSC_MAPS 252 */ 253 private List<String> getInvalidMapsInResource(final String path) { 254 List<String> invalidMaps = new ArrayList<String>(); 255 return invalidMaps; 256 } 257 258 /** 259 * Returns either a {@literal "normal"} {@link ResourceCollection} or a 260 * {@link XmlResourceCollection}, based upon {@code rsrc}. 261 * 262 * @param rsrc XML representation of a resource collection. Should not be 263 * {@code null}. 264 * @param name The {@literal "name"} to associate with the returned 265 * {@code ResourceCollection}. Should not be {@code null}. 266 */ 267 private ResourceCollection getCollection(final Element rsrc, final String name) { 268 ResourceCollection rc = getResources(name); 269 if (rc != null) 270 return rc; 271 272 if (getAttribute(rsrc, ATTR_RESOURCETYPE, "text").equals("text")) 273 return createResourceCollection(name); 274 else 275 return createXmlResourceCollection(name); 276 } 277 278 /** 279 * {@literal "Resource"} elements within a RBI file are allowed to have an 280 * arbitrary number of {@literal "property"} child elements (or none at 281 * all). The property elements must have {@literal "name"} and 282 * {@literal "value"} attributes. 283 * 284 * <p>This method iterates through any property elements and creates a {@link Map} 285 * of {@code name:value} pairs. 286 * 287 * @param resourceNode The {@literal "resource"} element to examine. Should 288 * not be {@code null}. Resources without {@code property}s are permitted. 289 * 290 * @return Either a {@code Map} of {@code name:value} pairs or an empty 291 * {@code Map}. 292 */ 293 private Map<String, String> getNodeProperties(final Element resourceNode) { 294 Map<String, String> nodeProperties = new LinkedHashMap<String, String>(); 295 NodeList propertyList = getElements(resourceNode, TAG_PROPERTY); 296 for (int propIdx = 0; propIdx < propertyList.getLength(); propIdx++) { 297 Element propNode = (Element)propertyList.item(propIdx); 298 String propName = getAttribute(propNode, ATTR_NAME); 299 String propValue = getAttribute(propNode, ATTR_VALUE, (String)null); 300 if (propValue == null) 301 propValue = getChildText(propNode); 302 nodeProperties.put(propName, propValue); 303 } 304 nodeProperties.putAll(getNodeAttributes(resourceNode)); 305 return nodeProperties; 306 } 307 308 /** 309 * Builds an {@code attribute:value} {@link Map} based upon the contents of 310 * {@code resourceNode}. 311 * 312 * <p><b>Be aware</b> that {@literal "location"} and {@literal "id"} attributes 313 * are ignored, as the IDV apparently considers them to be special. 314 * 315 * @param resourceNode The XML element to examine. Should not be 316 * {@code null}. 317 * 318 * @return Either a {@code Map} of {@code attribute:value} pairs or an 319 * empty {@code Map}. 320 */ 321 private Map<String, String> getNodeAttributes(final Element resourceNode) { 322 Map<String, String> nodeProperties = new LinkedHashMap<String, String>(); 323 NamedNodeMap nnm = resourceNode.getAttributes(); 324 if (nnm != null) { 325 for (int attrIdx = 0; attrIdx < nnm.getLength(); attrIdx++) { 326 Attr attr = (Attr)nnm.item(attrIdx); 327 String name = attr.getNodeName(); 328 if (!name.equals(ATTR_LOCATION) && !name.equals(ATTR_ID)) 329 nodeProperties.put(name, attr.getNodeValue()); 330 } 331 } 332 return nodeProperties; 333 } 334 335 /** 336 * Expands {@code origPath} (if needed) and builds a {@link List} of paths. 337 * Paths beginning with {@literal "index:"} or {@literal "http:"} may be in 338 * need of expansion. 339 * 340 * <p>{@literal "Index"} files contain a list of paths. These paths should 341 * be used instead of {@code origPath}. 342 * 343 * <p>Files that reside on a webserver (these begin with {@literal "http:"}) 344 * may be inaccessible for a variety of reasons. McIDAS-V allows a RBI file 345 * to specify a {@literal "property"} named {@literal "default"} whose {@literal "value"} 346 * is a path to use as a backup. For example:<br/> 347 * <pre> 348 * <resources name="idv.resource.pluginindex"> 349 * <resource label="Plugin Index" location="https://www.ssec.wisc.edu/mcidas/software/v/resources/plugins/plugins.xml"> 350 * <property name="default" value="%APPPATH%/plugins.xml"/> 351 * </resource> 352 * </resources> 353 * </pre> 354 * The {@code origPath} parameter will be the value of the {@literal "location"} 355 * attribute. If {@code origPath} is inaccessible, then the path given by 356 * the {@literal "default"} property will be used. 357 * 358 * @param origPath Typically the value of the {@literal "location"} 359 * attribute associated with a given resource. Cannot be {@code null}. 360 * @param props Contains the property {@code name:value} pairs associated with 361 * the resource whose path is being examined. Cannot be {@code null}. 362 * 363 * @return {@code List} of paths associated with a given resource. 364 * 365 * @see #isPathValid(String) 366 */ 367 private List<String> getPaths(final String origPath, 368 final Map<String, String> props) 369 { 370 List<String> paths = new ArrayList<String>(); 371 if (origPath.startsWith("index:")) { 372 String path = origPath.substring(6); 373 String index = IOUtil.readContents(path, (String)null); 374 if (index != null) { 375 List<String> lines = StringUtil.split(index, "\n", true, true); 376 for (int lineIdx = 0; lineIdx < lines.size(); lineIdx++) { 377 String line = lines.get(lineIdx); 378 if (line.startsWith("#")) 379 continue; 380 paths.add(getResourcePath(line)); 381 } 382 } 383 } else if (origPath.startsWith("http:")) { 384 String tmpPath = origPath; 385 if (!isPathValid(tmpPath) && props.containsKey("default")) 386 tmpPath = getResourcePath(props.get("default")); 387 paths.add(tmpPath); 388 } else { 389 paths.add(origPath); 390 } 391 return paths; 392 } 393 394 /** 395 * Utility method that calls {@link StateManager#fixIds(String)}. 396 */ 397 private static String fixId(final Element resource) { 398 return StateManager.fixIds(getAttribute(resource, ATTR_NAME)); 399 } 400 401 /** 402 * Processes the top-level {@literal "root"} of a RBI XML file. Overridden 403 * in McIDAS-V so that remote resources can have a backup location. 404 * 405 * @param root The {@literal "root"} element. Should not be {@code null}. 406 * @param observeLoadMore Whether or not processing should continue if a 407 * {@literal "loadmore"} tag is encountered. 408 * 409 * @see #getPaths(String, Map) 410 */ 411 @Override protected void processRbi(final Element root, 412 final boolean observeLoadMore) 413 { 414 NodeList children = getElements(root, TAG_RESOURCES); 415 416 for (int i = 0; i < children.getLength(); i++) { 417 Element rsrc = (Element)children.item(i); 418 419 ResourceCollection rc = getCollection(rsrc, fixId(rsrc)); 420 if (getAttribute(rsrc, ATTR_REMOVEPREVIOUS, false)) 421 rc.removeAll(); 422 423 if (observeLoadMore && !rc.getCanLoadMore()) 424 continue; 425 426 boolean loadMore = getAttribute(rsrc, ATTR_LOADMORE, true); 427 if (!loadMore) 428 rc.setCanLoadMore(false); 429 430 List<Resource> locationList = new ArrayList<Resource>(); 431 NodeList resources = getElements(rsrc, TAG_RESOURCE); 432 for (int idx = 0; idx < resources.getLength(); idx++) { 433 Element node = (Element)resources.item(idx); 434 String path = getResourcePath(getAttribute(node, ATTR_LOCATION)); 435 if ((path == null) || (path.length() == 0)) 436 continue; 437 438 String label = getAttribute(node, ATTR_LABEL, (String)null); 439 String id = getAttribute(node, ATTR_ID, (String)null); 440 441 Map<String, String> nodeProperties = getNodeProperties(node); 442 443 for (String p : getPaths(path, nodeProperties)) { 444 if (id != null) 445 rc.setIdForPath(id, p); 446 locationList.add(new Resource(p, label, new Hashtable<String, String>(nodeProperties))); 447 } 448 } 449 rc.addResources(locationList); 450 } 451 452 } 453 }