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    /**
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}