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 * &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="50000"/&gt;
034 *     &lt;timeout seconds="300"/&gt;
035 *   &lt;/api&gt;
036 *   &lt;policy&gt;
037 *     &lt;imagery&gt;
038 *       &lt;blacklist regex=".*\.google\.com/.*"/&gt;
039 *       &lt;blacklist regex=".*209\.85\.2\d\d.*"/&gt;
040 *       &lt;blacklist regex=".*209\.85\.1[3-9]\d.*"/&gt;
041 *       &lt;blacklist regex=".*209\.85\.12[89].*"/&gt;
042 *     &lt;/imagery&gt;
043 *   &lt;/policy&gt;
044 * &lt;/osm&gt;
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    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            SAXParserFactory.newInstance().newSAXParser().parse(inputSource, parser);
275            return parser.getCapabilities();
276        }
277    }
278}