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.XmlUtils;
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 * &lt;osm version="0.6" generator="OpenStreetMap server"&gt;
028 *   &lt;api&gt;
029 *     &lt;version minimum="0.6" maximum="0.6"/&gt;
030 *     &lt;area maximum="0.25"/&gt;
031 *     &lt;tracepoints per_page="5000"/&gt;
032 *     &lt;waynodes maximum="2000"/&gt;
033 *     &lt;changesets maximum_elements="10000"/&gt;
034 *     &lt;timeout seconds="300"/&gt;
035 *     &lt;status database="online" api="online" gpx="online"/&gt;
036 *   &lt;/api&gt;
037 *   &lt;policy&gt;
038 *     &lt;imagery&gt;
039 *       &lt;blacklist regex=".*\.google\.com/.*"/&gt;
040 *       &lt;blacklist regex=".*209\.85\.2\d\d.*"/&gt;
041 *       &lt;blacklist regex=".*209\.85\.1[3-9]\d.*"/&gt;
042 *       &lt;blacklist regex=".*209\.85\.12[89].*"/&gt;
043 *     &lt;/imagery&gt;
044 *   &lt;/policy&gt;
045 * &lt;/osm&gt;
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            XmlUtils.parseSafeSAX(inputSource, parser);
277            return parser.getCapabilities();
278        }
279    }
280}