1 | // License: GPL. For details, see LICENSE file.
|
---|
2 | package org.openstreetmap.josm.data.imagery;
|
---|
3 |
|
---|
4 | import java.io.InputStream;
|
---|
5 | import java.net.MalformedURLException;
|
---|
6 | import java.net.URL;
|
---|
7 | import java.util.Locale;
|
---|
8 | import java.util.function.BiFunction;
|
---|
9 |
|
---|
10 | import javax.xml.namespace.QName;
|
---|
11 | import javax.xml.stream.XMLInputFactory;
|
---|
12 | import javax.xml.stream.XMLStreamException;
|
---|
13 | import javax.xml.stream.XMLStreamReader;
|
---|
14 |
|
---|
15 | import org.openstreetmap.josm.tools.Utils;
|
---|
16 |
|
---|
17 | /**
|
---|
18 | * Helper class for handling OGC GetCapabilities documents
|
---|
19 | * @since 10993
|
---|
20 | */
|
---|
21 | public final class GetCapabilitiesParseHelper {
|
---|
22 | enum TransferMode {
|
---|
23 | KVP("KVP"),
|
---|
24 | REST("RESTful");
|
---|
25 |
|
---|
26 | private final String typeString;
|
---|
27 |
|
---|
28 | TransferMode(String urlString) {
|
---|
29 | this.typeString = urlString;
|
---|
30 | }
|
---|
31 |
|
---|
32 | private String getTypeString() {
|
---|
33 | return typeString;
|
---|
34 | }
|
---|
35 |
|
---|
36 | static TransferMode fromString(String s) {
|
---|
37 | for (TransferMode type : TransferMode.values()) {
|
---|
38 | if (type.getTypeString().equals(s)) {
|
---|
39 | return type;
|
---|
40 | }
|
---|
41 | }
|
---|
42 | return null;
|
---|
43 | }
|
---|
44 | }
|
---|
45 |
|
---|
46 | /**
|
---|
47 | * OWS namespace address
|
---|
48 | */
|
---|
49 | public static final String OWS_NS_URL = "http://www.opengis.net/ows/1.1";
|
---|
50 | /**
|
---|
51 | * XML xlink namespace address
|
---|
52 | */
|
---|
53 | public static final String XLINK_NS_URL = "http://www.w3.org/1999/xlink";
|
---|
54 |
|
---|
55 | /**
|
---|
56 | * QNames in OWS namespace
|
---|
57 | */
|
---|
58 | // CHECKSTYLE.OFF: SingleSpaceSeparator
|
---|
59 | static final QName QN_OWS_ALLOWED_VALUES = new QName(OWS_NS_URL, "AllowedValues");
|
---|
60 | static final QName QN_OWS_CONSTRAINT = new QName(OWS_NS_URL, "Constraint");
|
---|
61 | static final QName QN_OWS_DCP = new QName(OWS_NS_URL, "DCP");
|
---|
62 | static final QName QN_OWS_GET = new QName(OWS_NS_URL, "Get");
|
---|
63 | static final QName QN_OWS_HTTP = new QName(OWS_NS_URL, "HTTP");
|
---|
64 | static final QName QN_OWS_IDENTIFIER = new QName(OWS_NS_URL, "Identifier");
|
---|
65 | static final QName QN_OWS_OPERATION = new QName(OWS_NS_URL, "Operation");
|
---|
66 | static final QName QN_OWS_OPERATIONS_METADATA = new QName(OWS_NS_URL, "OperationsMetadata");
|
---|
67 | static final QName QN_OWS_SUPPORTED_CRS = new QName(OWS_NS_URL, "SupportedCRS");
|
---|
68 | static final QName QN_OWS_TITLE = new QName(OWS_NS_URL, "Title");
|
---|
69 | static final QName QN_OWS_VALUE = new QName(OWS_NS_URL, "Value");
|
---|
70 | // CHECKSTYLE.ON: SingleSpaceSeparator
|
---|
71 |
|
---|
72 | private GetCapabilitiesParseHelper() {
|
---|
73 | // Hide default constructor for utilities classes
|
---|
74 | }
|
---|
75 |
|
---|
76 | /**
|
---|
77 | * Returns reader with properties set for parsing WM(T)S documents
|
---|
78 | *
|
---|
79 | * @param in InputStream with pointing to GetCapabilities XML stream
|
---|
80 | * @return safe XMLStreamReader, that is not validating external entities, nor loads DTD's
|
---|
81 | * @throws XMLStreamException if any XML stream error occurs
|
---|
82 | */
|
---|
83 | public static XMLStreamReader getReader(InputStream in) throws XMLStreamException {
|
---|
84 | XMLInputFactory factory = XMLInputFactory.newInstance();
|
---|
85 | // do not try to load external entities, nor validate the XML
|
---|
86 | factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
|
---|
87 | factory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
|
---|
88 | factory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
|
---|
89 | return factory.createXMLStreamReader(in);
|
---|
90 | }
|
---|
91 |
|
---|
92 | /**
|
---|
93 | * Moves the reader to the closing tag of current tag.
|
---|
94 | * @param reader XMLStreamReader which should be moved
|
---|
95 | * @throws XMLStreamException when parse exception occurs
|
---|
96 | */
|
---|
97 | public static void moveReaderToEndCurrentTag(XMLStreamReader reader) throws XMLStreamException {
|
---|
98 | int level = 0;
|
---|
99 | QName tag = reader.getName();
|
---|
100 | for (int event = reader.getEventType(); reader.hasNext(); event = reader.next()) {
|
---|
101 | if (XMLStreamReader.START_ELEMENT == event) {
|
---|
102 | level += 1;
|
---|
103 | } else if (XMLStreamReader.END_ELEMENT == event) {
|
---|
104 | level -= 1;
|
---|
105 | if (level == 0 && tag.equals(reader.getName())) {
|
---|
106 | return;
|
---|
107 | }
|
---|
108 | }
|
---|
109 | if (level < 0) {
|
---|
110 | throw new IllegalStateException("WMTS Parser error - moveReaderToEndCurrentTag failed to find closing tag");
|
---|
111 | }
|
---|
112 | }
|
---|
113 | throw new IllegalStateException("WMTS Parser error - moveReaderToEndCurrentTag failed to find closing tag");
|
---|
114 | }
|
---|
115 |
|
---|
116 | /**
|
---|
117 | * Returns whole content of the element that reader is pointing at, including other XML elements within (with their tags).
|
---|
118 | *
|
---|
119 | * @param reader XMLStreamReader that should point to start of element
|
---|
120 | * @return content of current tag
|
---|
121 | * @throws XMLStreamException if any XML stream error occurs
|
---|
122 | */
|
---|
123 | public static String getElementTextWithSubtags(XMLStreamReader reader) throws XMLStreamException {
|
---|
124 | StringBuilder ret = new StringBuilder();
|
---|
125 | int level = 0;
|
---|
126 | QName tag = reader.getName();
|
---|
127 | for (int event = reader.getEventType(); reader.hasNext(); event = reader.next()) {
|
---|
128 | if (XMLStreamReader.START_ELEMENT == event) {
|
---|
129 | if (level > 0) {
|
---|
130 | ret.append('<').append(reader.getLocalName()).append('>');
|
---|
131 | }
|
---|
132 | level += 1;
|
---|
133 | } else if (XMLStreamReader.END_ELEMENT == event) {
|
---|
134 | level -= 1;
|
---|
135 | if (level == 0 && tag.equals(reader.getName())) {
|
---|
136 | return ret.toString();
|
---|
137 | }
|
---|
138 | ret.append("</").append(reader.getLocalName()).append('>');
|
---|
139 | } else if (XMLStreamReader.CHARACTERS == event) {
|
---|
140 | ret.append(reader.getText());
|
---|
141 | }
|
---|
142 | if (level < 0) {
|
---|
143 | throw new IllegalStateException("WMTS Parser error - moveReaderToEndCurrentTag failed to find closing tag");
|
---|
144 | }
|
---|
145 | }
|
---|
146 | throw new IllegalStateException("WMTS Parser error - moveReaderToEndCurrentTag failed to find closing tag");
|
---|
147 | }
|
---|
148 |
|
---|
149 |
|
---|
150 | /**
|
---|
151 | * Moves reader to first occurrence of the structure equivalent of Xpath tags[0]/tags[1]../tags[n]. If fails to find
|
---|
152 | * moves the reader to the closing tag of current tag
|
---|
153 | *
|
---|
154 | * @param tags array of tags
|
---|
155 | * @param reader XMLStreamReader which should be moved
|
---|
156 | * @return true if tag was found, false otherwise
|
---|
157 | * @throws XMLStreamException See {@link XMLStreamReader}
|
---|
158 | */
|
---|
159 | public static boolean moveReaderToTag(XMLStreamReader reader, QName... tags) throws XMLStreamException {
|
---|
160 | return moveReaderToTag(reader, QName::equals, tags);
|
---|
161 | }
|
---|
162 |
|
---|
163 | /**
|
---|
164 | * Moves reader to first occurrence of the structure equivalent of Xpath tags[0]/tags[1]../tags[n]. If fails to find
|
---|
165 | * moves the reader to the closing tag of current tag
|
---|
166 | *
|
---|
167 | * @param tags array of tags
|
---|
168 | * @param reader XMLStreamReader which should be moved
|
---|
169 | * @param equalsFunc function to check equality of the tags
|
---|
170 | * @return true if tag was found, false otherwise
|
---|
171 | * @throws XMLStreamException See {@link XMLStreamReader}
|
---|
172 | */
|
---|
173 | public static boolean moveReaderToTag(XMLStreamReader reader,
|
---|
174 | BiFunction<QName, QName, Boolean> equalsFunc, QName... tags) throws XMLStreamException {
|
---|
175 | QName stopTag = reader.getName();
|
---|
176 | int currentLevel = 0;
|
---|
177 | QName searchTag = tags[currentLevel];
|
---|
178 | QName parentTag = null;
|
---|
179 | QName skipTag = null;
|
---|
180 |
|
---|
181 | for (int event = 0; //skip current element, so we will not skip it as a whole
|
---|
182 | reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && equalsFunc.apply(stopTag, reader.getName()));
|
---|
183 | event = reader.next()) {
|
---|
184 | if (event == XMLStreamReader.END_ELEMENT && skipTag != null && equalsFunc.apply(skipTag, reader.getName())) {
|
---|
185 | skipTag = null;
|
---|
186 | }
|
---|
187 | if (skipTag == null) {
|
---|
188 | if (event == XMLStreamReader.START_ELEMENT) {
|
---|
189 | if (equalsFunc.apply(searchTag, reader.getName())) {
|
---|
190 | currentLevel += 1;
|
---|
191 | if (currentLevel >= tags.length) {
|
---|
192 | return true; // found!
|
---|
193 | }
|
---|
194 | parentTag = searchTag;
|
---|
195 | searchTag = tags[currentLevel];
|
---|
196 | } else {
|
---|
197 | skipTag = reader.getName();
|
---|
198 | }
|
---|
199 | }
|
---|
200 |
|
---|
201 | if (event == XMLStreamReader.END_ELEMENT && parentTag != null && equalsFunc.apply(parentTag, reader.getName())) {
|
---|
202 | currentLevel -= 1;
|
---|
203 | searchTag = parentTag;
|
---|
204 | if (currentLevel >= 0) {
|
---|
205 | parentTag = tags[currentLevel];
|
---|
206 | } else {
|
---|
207 | parentTag = null;
|
---|
208 | }
|
---|
209 | }
|
---|
210 | }
|
---|
211 | }
|
---|
212 | return false;
|
---|
213 | }
|
---|
214 |
|
---|
215 | /**
|
---|
216 | * Parses Operation[@name='GetTile']/DCP/HTTP/Get section. Returns when reader is on Get closing tag.
|
---|
217 | * @param reader StAX reader instance
|
---|
218 | * @return TransferMode coded in this section
|
---|
219 | * @throws XMLStreamException See {@link XMLStreamReader}
|
---|
220 | */
|
---|
221 | public static TransferMode getTransferMode(XMLStreamReader reader) throws XMLStreamException {
|
---|
222 | QName getQname = QN_OWS_GET;
|
---|
223 |
|
---|
224 | Utils.ensure(getQname.equals(reader.getName()), "WMTS Parser state invalid. Expected element %s, got %s",
|
---|
225 | getQname, reader.getName());
|
---|
226 | for (int event = reader.getEventType();
|
---|
227 | reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && getQname.equals(reader.getName()));
|
---|
228 | event = reader.next()) {
|
---|
229 | if (event == XMLStreamReader.START_ELEMENT && QN_OWS_CONSTRAINT.equals(reader.getName())
|
---|
230 | && "GetEncoding".equals(reader.getAttributeValue("", "name"))) {
|
---|
231 | moveReaderToTag(reader, QN_OWS_ALLOWED_VALUES, QN_OWS_VALUE);
|
---|
232 | return TransferMode.fromString(reader.getElementText());
|
---|
233 | }
|
---|
234 | }
|
---|
235 | return null;
|
---|
236 | }
|
---|
237 |
|
---|
238 | /**
|
---|
239 | * Normalize url
|
---|
240 | *
|
---|
241 | * @param url URL
|
---|
242 | * @return normalized URL
|
---|
243 | * @throws MalformedURLException in case of malformed URL
|
---|
244 | * @since 10993
|
---|
245 | */
|
---|
246 | public static String normalizeCapabilitiesUrl(String url) throws MalformedURLException {
|
---|
247 | URL inUrl = new URL(url);
|
---|
248 | URL ret = new URL(inUrl.getProtocol(), inUrl.getHost(), inUrl.getPort(), inUrl.getFile());
|
---|
249 | return ret.toExternalForm();
|
---|
250 | }
|
---|
251 |
|
---|
252 | /**
|
---|
253 | * Convert CRS identifier to plain code
|
---|
254 | * @param crsIdentifier CRS identifier
|
---|
255 | * @return CRS Identifier as it is used within JOSM (without prefix)
|
---|
256 | * @see <a href="https://portal.opengeospatial.org/files/?artifact_id=24045">
|
---|
257 | * Definition identifier URNs in OGC namespace, chapter 7.2: URNs for single objects</a>
|
---|
258 | */
|
---|
259 | public static String crsToCode(String crsIdentifier) {
|
---|
260 | if (crsIdentifier.startsWith("urn:ogc:def:crs:")) {
|
---|
261 | return crsIdentifier.replaceFirst("urn:ogc:def:crs:([^:]*)(?::.*)?:(.*)$", "$1:$2").toUpperCase(Locale.ENGLISH);
|
---|
262 | }
|
---|
263 | return crsIdentifier;
|
---|
264 | }
|
---|
265 | }
|
---|