001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.io; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.io.IOException; 007import java.util.ArrayList; 008import java.util.Collections; 009import java.util.HashMap; 010import java.util.List; 011import java.util.Map; 012 013import javax.xml.parsers.ParserConfigurationException; 014 015import org.openstreetmap.josm.Main; 016import org.openstreetmap.josm.tools.Utils; 017import org.xml.sax.Attributes; 018import org.xml.sax.InputSource; 019import org.xml.sax.SAXException; 020import org.xml.sax.helpers.DefaultHandler; 021 022/** 023 * Represents the OSM API server capabilities. 024 * 025 * Example capabilites document: 026 * <pre> 027 * <osm version="0.6" generator="OpenStreetMap server"> 028 * <api> 029 * <version minimum="0.6" maximum="0.6"/> 030 * <area maximum="0.25"/> 031 * <tracepoints per_page="5000"/> 032 * <waynodes maximum="2000"/> 033 * <changesets maximum_elements="50000"/> 034 * <timeout seconds="300"/> 035 * </api> 036 * <policy> 037 * <imagery> 038 * <blacklist regex=".*\.google\.com/.*"/> 039 * <blacklist regex=".*209\.85\.2\d\d.*"/> 040 * <blacklist regex=".*209\.85\.1[3-9]\d.*"/> 041 * <blacklist regex=".*209\.85\.12[89].*"/> 042 * </imagery> 043 * </policy> 044 * </osm> 045 * </pre> 046 * This class is used in conjunction with a very primitive parser 047 * and simply stuffs the each tag and its attributes into a hash 048 * of hashes, with the exception of the "blacklist" tag which gets 049 * a list of its own. The DOM hierarchy is disregarded. 050 */ 051public class Capabilities { 052 053 private final Map<String, Map<String, String>> capabilities; 054 private final List<String> imageryBlacklist; 055 056 /** 057 * Constructs new {@code Capabilities}. 058 */ 059 public Capabilities() { 060 capabilities = new HashMap<>(); 061 imageryBlacklist = new ArrayList<>(); 062 } 063 064 /** 065 * Determines if given element and attribute are defined. 066 * 067 * @param element the name of the element 068 * @param attribute the name of the attribute 069 * @return {@code true} if defined, {@code false} otherwise 070 */ 071 public boolean isDefined(String element, String attribute) { 072 if (!capabilities.containsKey(element)) return false; 073 Map<String, String> e = capabilities.get(element); 074 if (e == null) return false; 075 return e.get(attribute) != null; 076 } 077 078 /** 079 * Returns the value of configuration item in the capabilities as string value. 080 * 081 * @param element the name of the element 082 * @param attribute the name of the attribute 083 * @return the value; {@code null}, if the respective configuration item does not exist 084 */ 085 public String get(String element, String attribute) { 086 if (!capabilities.containsKey(element)) return null; 087 Map<String, String> e = capabilities.get(element); 088 if (e == null) return null; 089 return e.get(attribute); 090 } 091 092 /** 093 * Returns the value of configuration item in the capabilities as double value. 094 * 095 * @param element the name of the element 096 * @param attribute the name of the attribute 097 * @return the value; {@code null}, if the respective configuration item does not exist 098 * @throws NumberFormatException if the value is not a valid double 099 */ 100 public Double getDouble(String element, String attribute) { 101 String s = get(element, attribute); 102 if (s == null) return null; 103 return Double.valueOf(s); 104 } 105 106 /** 107 * Returns the value of configuration item in the capabilities as long value. 108 * 109 * @param element the name of the element 110 * @param attribute the name of the attribute 111 * @return the value; {@code null}, if the respective configuration item does not exist 112 * @throws NumberFormatException if the value is not a valid long 113 */ 114 public Long getLong(String element, String attribute) { 115 String s = get(element, attribute); 116 if (s == null) return null; 117 return Long.valueOf(s); 118 } 119 120 /** 121 * Adds a new configuration item. 122 * 123 * @param element the name of the element 124 * @param attribute the name of the attribute 125 * @param value the value as string 126 */ 127 public void put(String element, String attribute, String value) { 128 if ("blacklist".equals(element)) { 129 if ("regex".equals(attribute)) { 130 imageryBlacklist.add(value); 131 } 132 } else { 133 if (!capabilities.containsKey(element)) { 134 Map<String, String> h = new HashMap<>(); 135 capabilities.put(element, h); 136 } 137 Map<String, String> e = capabilities.get(element); 138 e.put(attribute, value); 139 } 140 } 141 142 /** 143 * Clears the API capabilities. 144 */ 145 public final void clear() { 146 capabilities.clear(); 147 imageryBlacklist.clear(); 148 } 149 150 /** 151 * Determines if a given API version is supported. 152 * @param version The API version to check 153 * @return {@code true} is version is between the minimum supported version and the maximum one, {@code false} otherwise 154 */ 155 public boolean supportsVersion(String version) { 156 return get("version", "minimum").compareTo(version) <= 0 157 && get("version", "maximum").compareTo(version) >= 0; 158 } 159 160 private static void warnIllegalValue(String attr, String elem, Object val) { 161 Main.warn(tr("Illegal value of attribute ''{0}'' of element ''{1}'' in server capabilities. Got ''{2}''", attr, elem, val)); 162 } 163 164 /** 165 * Returns the max number of objects in a changeset. -1 if either the capabilities 166 * don't include this parameter or if the parameter value is illegal (not a number, 167 * a negative number) 168 * 169 * @return the max number of objects in a changeset 170 */ 171 public int getMaxChangesetSize() { 172 String v = get("changesets", "maximum_elements"); 173 if (v != null) { 174 try { 175 int n = Integer.parseInt(v); 176 if (n <= 0) { 177 warnIllegalValue("changesets", "maximum_elements", n); 178 } else { 179 return n; 180 } 181 } catch (NumberFormatException e) { 182 warnIllegalValue("changesets", "maximum_elements", v); 183 } 184 } 185 return -1; 186 } 187 188 /** 189 * Returns the max number of nodes in a way. -1 if either the capabilities 190 * don't include this parameter or if the parameter value is illegal (not a number, 191 * a negative number) 192 * 193 * @return the max number of nodes in a way 194 */ 195 public long getMaxWayNodes() { 196 String v = get("waynodes", "maximum"); 197 if (v != null) { 198 try { 199 long n = Long.parseLong(v); 200 if (n <= 0) { 201 warnIllegalValue("waynodes", "maximum", n); 202 } else { 203 return n; 204 } 205 } catch (NumberFormatException e) { 206 warnIllegalValue("waynodes", "maximum", v); 207 } 208 } 209 return -1; 210 } 211 212 /** 213 * Checks if the given URL is blacklisted by one of the of the regular expressions. 214 * @param url Imagery URL to check 215 * @return {@code true} if URL is blacklisted, {@code false} otherwise 216 */ 217 public boolean isOnImageryBlacklist(String url) { 218 if (url != null && imageryBlacklist != null) { 219 for (String blacklistRegex : imageryBlacklist) { 220 if (url.matches(blacklistRegex)) 221 return true; 222 } 223 } 224 return false; 225 } 226 227 /** 228 * Returns the full list of imagery blacklist regular expressions. 229 * @return full list of imagery blacklist regular expressions 230 */ 231 public List<String> getImageryBlacklist() { 232 return Collections.unmodifiableList(imageryBlacklist); 233 } 234 235 /** 236 * A parser for the "capabilities" response XML. 237 * @since 7473 238 */ 239 public static final class CapabilitiesParser extends DefaultHandler { 240 241 private Capabilities capabilities; 242 243 @Override 244 public void startDocument() throws SAXException { 245 capabilities = new Capabilities(); 246 } 247 248 @Override 249 public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { 250 for (int i = 0; i < atts.getLength(); i++) { 251 capabilities.put(qName, atts.getQName(i), atts.getValue(i)); 252 } 253 } 254 255 /** 256 * Returns the read capabilities. 257 * @return the read capabilities 258 */ 259 public Capabilities getCapabilities() { 260 return capabilities; 261 } 262 263 /** 264 * Parses and returns capabilities from the given input source. 265 * 266 * @param inputSource The input source to read capabilities from 267 * @return the capabilities 268 * @throws SAXException if any SAX errors occur during processing 269 * @throws IOException if any I/O errors occur 270 * @throws ParserConfigurationException if a parser cannot be created 271 */ 272 public static Capabilities parse(InputSource inputSource) throws SAXException, IOException, ParserConfigurationException { 273 CapabilitiesParser parser = new CapabilitiesParser(); 274 Utils.parseSafeSAX(inputSource, parser); 275 return parser.getCapabilities(); 276 } 277 } 278}