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 | * @param in InputStream with pointing to GetCapabilities XML stream
|
---|
78 | * @return safe XMLStreamReader, that is not validating external entities, nor loads DTD's
|
---|
79 | * @throws XMLStreamException if any XML stream error occurs
|
---|
80 | */
|
---|
81 | public static XMLStreamReader getReader(InputStream in) throws XMLStreamException {
|
---|
82 | XMLInputFactory factory = XMLInputFactory.newInstance();
|
---|
83 | // do not try to load external entities, nor validate the XML
|
---|
84 | factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
|
---|
85 | factory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
|
---|
86 | factory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
|
---|
87 | return factory.createXMLStreamReader(in);
|
---|
88 | }
|
---|
89 |
|
---|
90 | /**
|
---|
91 | * Moves the reader to the closing tag of current tag.
|
---|
92 | * @param reader XMLStreamReader which should be moved
|
---|
93 | * @throws XMLStreamException when parse exception occurs
|
---|
94 | */
|
---|
95 | public static void moveReaderToEndCurrentTag(XMLStreamReader reader) throws XMLStreamException {
|
---|
96 | int level = 0;
|
---|
97 | QName tag = reader.getName();
|
---|
98 | for (int event = reader.getEventType(); reader.hasNext(); event = reader.next()) {
|
---|
99 | if (XMLStreamReader.START_ELEMENT == event) {
|
---|
100 | level += 1;
|
---|
101 | } else if (XMLStreamReader.END_ELEMENT == event) {
|
---|
102 | level -= 1;
|
---|
103 | if (level == 0 && tag.equals(reader.getName())) {
|
---|
104 | return;
|
---|
105 | }
|
---|
106 | }
|
---|
107 | if (level < 0) {
|
---|
108 | throw new IllegalStateException("WMTS Parser error - moveReaderToEndCurrentTag failed to find closing tag");
|
---|
109 | }
|
---|
110 | }
|
---|
111 | throw new IllegalStateException("WMTS Parser error - moveReaderToEndCurrentTag failed to find closing tag");
|
---|
112 | }
|
---|
113 |
|
---|
114 | /**
|
---|
115 | * Returns whole content of the element that reader is pointing at, including other XML elements within (with their tags).
|
---|
116 | *
|
---|
117 | * @param reader XMLStreamReader that should point to start of element
|
---|
118 | * @return content of current tag
|
---|
119 | * @throws XMLStreamException if any XML stream error occurs
|
---|
120 | */
|
---|
121 | public static String getElementTextWithSubtags(XMLStreamReader reader) throws XMLStreamException {
|
---|
122 | StringBuilder ret = new StringBuilder();
|
---|
123 | int level = 0;
|
---|
124 | QName tag = reader.getName();
|
---|
125 | for (int event = reader.getEventType(); reader.hasNext(); event = reader.next()) {
|
---|
126 | if (XMLStreamReader.START_ELEMENT == event) {
|
---|
127 | if (level > 0) {
|
---|
128 | ret.append("<" + reader.getLocalName() +">");
|
---|
129 | }
|
---|
130 | level += 1;
|
---|
131 | } else if (XMLStreamReader.END_ELEMENT == event) {
|
---|
132 | level -= 1;
|
---|
133 | if (level == 0 && tag.equals(reader.getName())) {
|
---|
134 | return ret.toString();
|
---|
135 | }
|
---|
136 | ret.append("</" + reader.getLocalName() +">");
|
---|
137 | } else if (XMLStreamReader.CHARACTERS == event) {
|
---|
138 | ret.append(reader.getText());
|
---|
139 | }
|
---|
140 | if (level < 0) {
|
---|
141 | throw new IllegalStateException("WMTS Parser error - moveReaderToEndCurrentTag failed to find closing tag");
|
---|
142 | }
|
---|
143 | }
|
---|
144 | throw new IllegalStateException("WMTS Parser error - moveReaderToEndCurrentTag failed to find closing tag");
|
---|
145 | }
|
---|
146 |
|
---|
147 |
|
---|
148 | /**
|
---|
149 | * Moves reader to first occurrence of the structure equivalent of Xpath tags[0]/tags[1]../tags[n]. If fails to find
|
---|
150 | * moves the reader to the closing tag of current tag
|
---|
151 | *
|
---|
152 | * @param tags array of tags
|
---|
153 | * @param reader XMLStreamReader which should be moved
|
---|
154 | * @return true if tag was found, false otherwise
|
---|
155 | * @throws XMLStreamException See {@link XMLStreamReader}
|
---|
156 | */
|
---|
157 | public static boolean moveReaderToTag(XMLStreamReader reader, QName... tags) throws XMLStreamException {
|
---|
158 | return moveReaderToTag(reader, QName::equals, tags);
|
---|
159 | }
|
---|
160 |
|
---|
161 | /**
|
---|
162 | * Moves reader to first occurrence of the structure equivalent of Xpath tags[0]/tags[1]../tags[n]. If fails to find
|
---|
163 | * moves the reader to the closing tag of current tag
|
---|
164 | *
|
---|
165 | * @param tags array of tags
|
---|
166 | * @param reader XMLStreamReader which should be moved
|
---|
167 | * @param equalsFunc function to check equality of the tags
|
---|
168 | * @return true if tag was found, false otherwise
|
---|
169 | * @throws XMLStreamException See {@link XMLStreamReader}
|
---|
170 | */
|
---|
171 | public static boolean moveReaderToTag(XMLStreamReader reader,
|
---|
172 | BiFunction<QName, QName, Boolean> equalsFunc, QName... tags) throws XMLStreamException {
|
---|
173 | QName stopTag = reader.getName();
|
---|
174 | int currentLevel = 0;
|
---|
175 | QName searchTag = tags[currentLevel];
|
---|
176 | QName parentTag = null;
|
---|
177 | QName skipTag = null;
|
---|
178 |
|
---|
179 | for (int event = 0; //skip current element, so we will not skip it as a whole
|
---|
180 | reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && equalsFunc.apply(stopTag, reader.getName()));
|
---|
181 | event = reader.next()) {
|
---|
182 | if (event == XMLStreamReader.END_ELEMENT && skipTag != null && equalsFunc.apply(skipTag, reader.getName())) {
|
---|
183 | skipTag = null;
|
---|
184 | }
|
---|
185 | if (skipTag == null) {
|
---|
186 | if (event == XMLStreamReader.START_ELEMENT) {
|
---|
187 | if (equalsFunc.apply(searchTag, reader.getName())) {
|
---|
188 | currentLevel += 1;
|
---|
189 | if (currentLevel >= tags.length) {
|
---|
190 | return true; // found!
|
---|
191 | }
|
---|
192 | parentTag = searchTag;
|
---|
193 | searchTag = tags[currentLevel];
|
---|
194 | } else {
|
---|
195 | skipTag = reader.getName();
|
---|
196 | }
|
---|
197 | }
|
---|
198 |
|
---|
199 | if (event == XMLStreamReader.END_ELEMENT && parentTag != null && equalsFunc.apply(parentTag, reader.getName())) {
|
---|
200 | currentLevel -= 1;
|
---|
201 | searchTag = parentTag;
|
---|
202 | if (currentLevel >= 0) {
|
---|
203 | parentTag = tags[currentLevel];
|
---|
204 | } else {
|
---|
205 | parentTag = null;
|
---|
206 | }
|
---|
207 | }
|
---|
208 | }
|
---|
209 | }
|
---|
210 | return false;
|
---|
211 | }
|
---|
212 |
|
---|
213 | /**
|
---|
214 | * Parses Operation[@name='GetTile']/DCP/HTTP/Get section. Returns when reader is on Get closing tag.
|
---|
215 | * @param reader StAX reader instance
|
---|
216 | * @return TransferMode coded in this section
|
---|
217 | * @throws XMLStreamException See {@link XMLStreamReader}
|
---|
218 | */
|
---|
219 | public static TransferMode getTransferMode(XMLStreamReader reader) throws XMLStreamException {
|
---|
220 | QName getQname = QN_OWS_GET;
|
---|
221 |
|
---|
222 | Utils.ensure(getQname.equals(reader.getName()), "WMTS Parser state invalid. Expected element %s, got %s",
|
---|
223 | getQname, reader.getName());
|
---|
224 | for (int event = reader.getEventType();
|
---|
225 | reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && getQname.equals(reader.getName()));
|
---|
226 | event = reader.next()) {
|
---|
227 | if (event == XMLStreamReader.START_ELEMENT && QN_OWS_CONSTRAINT.equals(reader.getName())
|
---|
228 | && "GetEncoding".equals(reader.getAttributeValue("", "name"))) {
|
---|
229 | moveReaderToTag(reader, QN_OWS_ALLOWED_VALUES, QN_OWS_VALUE);
|
---|
230 | return TransferMode.fromString(reader.getElementText());
|
---|
231 | }
|
---|
232 | }
|
---|
233 | return null;
|
---|
234 | }
|
---|
235 |
|
---|
236 | /**
|
---|
237 | * @param url URL
|
---|
238 | * @return normalized URL
|
---|
239 | * @throws MalformedURLException in case of malformed URL
|
---|
240 | * @since 10993
|
---|
241 | */
|
---|
242 | public static String normalizeCapabilitiesUrl(String url) throws MalformedURLException {
|
---|
243 | URL inUrl = new URL(url);
|
---|
244 | URL ret = new URL(inUrl.getProtocol(), inUrl.getHost(), inUrl.getPort(), inUrl.getFile());
|
---|
245 | return ret.toExternalForm();
|
---|
246 | }
|
---|
247 |
|
---|
248 | /**
|
---|
249 | * Convert CRS identifier to plain code
|
---|
250 | * @param crsIdentifier CRS identifier
|
---|
251 | * @return CRS Identifier as it is used within JOSM (without prefix)
|
---|
252 | * @see <a href="https://portal.opengeospatial.org/files/?artifact_id=24045">
|
---|
253 | * Definition identifier URNs in OGC namespace, chapter 7.2: URNs for single objects</a>
|
---|
254 | */
|
---|
255 | public static String crsToCode(String crsIdentifier) {
|
---|
256 | if (crsIdentifier.startsWith("urn:ogc:def:crs:")) {
|
---|
257 | return crsIdentifier.replaceFirst("urn:ogc:def:crs:([^:]*)(?::.*)?:(.*)$", "$1:$2").toUpperCase(Locale.ENGLISH);
|
---|
258 | }
|
---|
259 | return crsIdentifier;
|
---|
260 | }
|
---|
261 | }
|
---|