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; 014import javax.xml.parsers.SAXParserFactory; 015 016import org.openstreetmap.josm.Main; 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, HashMap<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 HashMap<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 HashMap<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) throws NumberFormatException { 101 String s = get(element, attribute); 102 if (s == null) return null; 103 return Double.parseDouble(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.parseLong(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 HashMap<String,String> h = new HashMap<>(); 135 capabilities.put(element, h); 136 } 137 HashMap<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 /** 161 * Returns the max number of objects in a changeset. -1 if either the capabilities 162 * don't include this parameter or if the parameter value is illegal (not a number, 163 * a negative number) 164 * 165 * @return the max number of objects in a changeset 166 */ 167 public int getMaxChangesetSize() { 168 String v = get("changesets", "maximum_elements"); 169 if (v == null) return -1; 170 try { 171 int n = Integer.parseInt(v); 172 if (n <= 0) { 173 Main.warn(tr("Illegal value of attribute ''{0}'' of element ''{1}'' in server capabilities. Got ''{2}''", "changesets", "maximum_elements", n )); 174 return -1; 175 } 176 return n; 177 } catch (NumberFormatException e) { 178 Main.warn(tr("Illegal value of attribute ''{0}'' of element ''{1}'' in server capabilities. Got ''{2}''", "changesets", "maximum_elements", v )); 179 return -1; 180 } 181 } 182 183 /** 184 * Checks if the given URL is blacklisted by one of the of the regular expressions. 185 * @param url Imagery URL to check 186 * @return {@code true} if URL is blacklisted, {@code false} otherwise 187 */ 188 public boolean isOnImageryBlacklist(String url) { 189 if (url != null && imageryBlacklist != null) { 190 for (String blacklistRegex : imageryBlacklist) { 191 if (url.matches(blacklistRegex)) 192 return true; 193 } 194 } 195 return false; 196 } 197 198 /** 199 * Returns the full list of imagery blacklist regular expressions. 200 * @return full list of imagery blacklist regular expressions 201 */ 202 public List<String> getImageryBlacklist() { 203 return Collections.unmodifiableList(imageryBlacklist); 204 } 205 206 /** 207 * A parser for the "capabilities" response XML. 208 * @since 7473 209 */ 210 public static final class CapabilitiesParser extends DefaultHandler { 211 212 private Capabilities capabilities; 213 214 @Override 215 public void startDocument() throws SAXException { 216 capabilities = new Capabilities(); 217 } 218 219 @Override 220 public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { 221 for (int i=0; i< atts.getLength(); i++) { 222 capabilities.put(qName, atts.getQName(i), atts.getValue(i)); 223 } 224 } 225 226 /** 227 * Returns the read capabilities. 228 * @return the read capabilities 229 */ 230 public Capabilities getCapabilities() { 231 return capabilities; 232 } 233 234 /** 235 * Parses and returns capabilities from the given input source. 236 * 237 * @param inputSource The input source to read capabilities from 238 * @return the capabilities 239 * @throws SAXException if any SAX errors occur during processing 240 * @throws IOException if any I/O errors occur 241 * @throws ParserConfigurationException if a parser cannot be created 242 */ 243 public static Capabilities parse(InputSource inputSource) throws SAXException, IOException, ParserConfigurationException { 244 CapabilitiesParser parser = new CapabilitiesParser(); 245 SAXParserFactory.newInstance().newSAXParser().parse(inputSource, parser); 246 return parser.getCapabilities(); 247 } 248 } 249}