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