001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.imagery; 003 004import java.io.IOException; 005import java.io.InputStream; 006import java.net.MalformedURLException; 007import java.net.URL; 008 009import javax.xml.namespace.QName; 010import javax.xml.stream.XMLInputFactory; 011import javax.xml.stream.XMLStreamException; 012import javax.xml.stream.XMLStreamReader; 013 014import org.openstreetmap.josm.tools.Utils; 015 016/** 017 * Helper class for handling OGC GetCapabilities documents 018 * 019 */ 020public final class GetCapabilitiesParseHelper { 021 enum TransferMode { 022 KVP("KVP"), 023 REST("RESTful"); 024 025 private final String typeString; 026 027 TransferMode(String urlString) { 028 this.typeString = urlString; 029 } 030 031 private String getTypeString() { 032 return typeString; 033 } 034 035 static TransferMode fromString(String s) { 036 for (TransferMode type : TransferMode.values()) { 037 if (type.getTypeString().equals(s)) { 038 return type; 039 } 040 } 041 return null; 042 } 043 } 044 045 /** 046 * OWS namespace address 047 */ 048 public static final String OWS_NS_URL = "http://www.opengis.net/ows/1.1"; 049 /** 050 * XML xlink namespace address 051 */ 052 public static final String XLINK_NS_URL = "http://www.w3.org/1999/xlink"; 053 054 /** 055 * QNames in OWS namespace 056 */ 057 // CHECKSTYLE.OFF: SingleSpaceSeparator 058 static final QName QN_OWS_ALLOWED_VALUES = new QName(OWS_NS_URL, "AllowedValues"); 059 static final QName QN_OWS_CONSTRAINT = new QName(OWS_NS_URL, "Constraint"); 060 static final QName QN_OWS_DCP = new QName(OWS_NS_URL, "DCP"); 061 static final QName QN_OWS_GET = new QName(OWS_NS_URL, "Get"); 062 static final QName QN_OWS_HTTP = new QName(OWS_NS_URL, "HTTP"); 063 static final QName QN_OWS_IDENTIFIER = new QName(OWS_NS_URL, "Identifier"); 064 static final QName QN_OWS_OPERATION = new QName(OWS_NS_URL, "Operation"); 065 static final QName QN_OWS_OPERATIONS_METADATA = new QName(OWS_NS_URL, "OperationsMetadata"); 066 static final QName QN_OWS_SUPPORTED_CRS = new QName(OWS_NS_URL, "SupportedCRS"); 067 static final QName QN_OWS_VALUE = new QName(OWS_NS_URL, "Value"); 068 // CHECKSTYLE.ON: SingleSpaceSeparator 069 070 private GetCapabilitiesParseHelper() { 071 // Hide default constructor for utilities classes 072 } 073 074 /** 075 * @param in InputStream with pointing to GetCapabilities XML stream 076 * @return safe XMLStreamReader, that is not validating external entities, nor loads DTD's 077 * @throws IOException if any I/O error occurs 078 * @throws XMLStreamException if any XML stream error occurs 079 */ 080 public static XMLStreamReader getReader(InputStream in) throws IOException, XMLStreamException { 081 XMLInputFactory factory = XMLInputFactory.newFactory(); 082 // do not try to load external entities, nor validate the XML 083 factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE); 084 factory.setProperty(XMLInputFactory.IS_VALIDATING, Boolean.FALSE); 085 factory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE); 086 return factory.createXMLStreamReader(in); 087 } 088 089 /** 090 * Moves the reader to the closing tag of current tag. 091 * @param reader XMLStreamReader which should be moved 092 * @throws XMLStreamException when parse exception occurs 093 */ 094 public static void moveReaderToEndCurrentTag(XMLStreamReader reader) throws XMLStreamException { 095 int level = 0; 096 QName tag = reader.getName(); 097 for (int event = reader.getEventType(); reader.hasNext(); event = reader.next()) { 098 if (XMLStreamReader.START_ELEMENT == event) { 099 level += 1; 100 } else if (XMLStreamReader.END_ELEMENT == event) { 101 level -= 1; 102 if (level == 0 && tag.equals(reader.getName())) { 103 return; 104 } 105 } 106 if (level < 0) { 107 throw new IllegalStateException("WMTS Parser error - moveReaderToEndCurrentTag failed to find closing tag"); 108 } 109 } 110 throw new IllegalStateException("WMTS Parser error - moveReaderToEndCurrentTag failed to find closing tag"); 111 } 112 113 /** 114 * Moves reader to first occurrence of the structure equivalent of Xpath tags[0]/tags[1]../tags[n]. If fails to find 115 * moves the reader to the closing tag of current tag 116 * 117 * @param tags array of tags 118 * @param reader XMLStreamReader which should be moved 119 * @return true if tag was found, false otherwise 120 * @throws XMLStreamException See {@link XMLStreamReader} 121 */ 122 public static boolean moveReaderToTag(XMLStreamReader reader, QName ... tags) throws XMLStreamException { 123 QName stopTag = reader.getName(); 124 int currentLevel = 0; 125 QName searchTag = tags[currentLevel]; 126 QName parentTag = null; 127 QName skipTag = null; 128 129 for (int event = 0; //skip current element, so we will not skip it as a whole 130 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && stopTag.equals(reader.getName())); 131 event = reader.next()) { 132 if (event == XMLStreamReader.END_ELEMENT && skipTag != null && skipTag.equals(reader.getName())) { 133 skipTag = null; 134 } 135 if (skipTag == null) { 136 if (event == XMLStreamReader.START_ELEMENT) { 137 if (searchTag.equals(reader.getName())) { 138 currentLevel += 1; 139 if (currentLevel >= tags.length) { 140 return true; // found! 141 } 142 parentTag = searchTag; 143 searchTag = tags[currentLevel]; 144 } else { 145 skipTag = reader.getName(); 146 } 147 } 148 149 if (event == XMLStreamReader.END_ELEMENT && parentTag != null && parentTag.equals(reader.getName())) { 150 currentLevel -= 1; 151 searchTag = parentTag; 152 if (currentLevel >= 0) { 153 parentTag = tags[currentLevel]; 154 } else { 155 parentTag = null; 156 } 157 } 158 } 159 } 160 return false; 161 } 162 163 /** 164 * Parses Operation[@name='GetTile']/DCP/HTTP/Get section. Returns when reader is on Get closing tag. 165 * @param reader StAX reader instance 166 * @return TransferMode coded in this section 167 * @throws XMLStreamException See {@link XMLStreamReader} 168 */ 169 public static TransferMode getTransferMode(XMLStreamReader reader) throws XMLStreamException { 170 QName getQname = QN_OWS_GET; 171 172 Utils.ensure(getQname.equals(reader.getName()), "WMTS Parser state invalid. Expected element %s, got %s", 173 getQname, reader.getName()); 174 for (int event = reader.getEventType(); 175 reader.hasNext() && !(event == XMLStreamReader.END_ELEMENT && getQname.equals(reader.getName())); 176 event = reader.next()) { 177 if (event == XMLStreamReader.START_ELEMENT && QN_OWS_CONSTRAINT.equals(reader.getName()) 178 && "GetEncoding".equals(reader.getAttributeValue("", "name"))) { 179 moveReaderToTag(reader, new QName[]{ 180 QN_OWS_ALLOWED_VALUES, 181 QN_OWS_VALUE 182 }); 183 return TransferMode.fromString(reader.getElementText()); 184 } 185 } 186 return null; 187 } 188 189 /** 190 * @param url URL 191 * @return normalized URL 192 * @throws MalformedURLException in case of malformed URL 193 * @since 10993 194 */ 195 public static String normalizeCapabilitiesUrl(String url) throws MalformedURLException { 196 URL inUrl = new URL(url); 197 URL ret = new URL(inUrl.getProtocol(), inUrl.getHost(), inUrl.getPort(), inUrl.getFile()); 198 return ret.toExternalForm(); 199 } 200 201 /** 202 * Convert CRS identifier to plain code 203 * @param crsIdentifier CRS identifier 204 * @return CRS Identifier as it is used within JOSM (without prefix) 205 */ 206 public static String crsToCode(String crsIdentifier) { 207 if (crsIdentifier.startsWith("urn:ogc:def:crs:")) { 208 return crsIdentifier.replaceFirst("urn:ogc:def:crs:([^:]*):.*:(.*)$", "$1:$2"); 209 } 210 return crsIdentifier; 211 } 212 213}