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 }