88import static org .tailormap .api .util .HttpProxyUtil .setHttpBasicAuthenticationHeader ;
99
1010import java .io .InputStream ;
11+ import java .io .StringReader ;
1112import java .lang .invoke .MethodHandles ;
1213import java .net .URI ;
1314import java .net .URLEncoder ;
2425import javax .xml .XMLConstants ;
2526import javax .xml .parsers .DocumentBuilder ;
2627import javax .xml .parsers .DocumentBuilderFactory ;
28+ import javax .xml .parsers .ParserConfigurationException ;
2729import javax .xml .xpath .XPath ;
2830import javax .xml .xpath .XPathConstants ;
2931import javax .xml .xpath .XPathFactory ;
30- import org .geotools .http .HTTPClient ;
31- import org .geotools .http .SimpleHttpClient ;
32- import org .geotools .ows .wms .LayerDescription ;
33- import org .geotools .ows .wms .WMS1_1_1 ;
34- import org .geotools .ows .wms .WebMapServer ;
35- import org .geotools .ows .wms .request .DescribeLayerRequest ;
36- import org .geotools .ows .wms .response .DescribeLayerResponse ;
3732import org .slf4j .Logger ;
3833import org .slf4j .LoggerFactory ;
3934import org .springframework .util .LinkedMultiValueMap ;
4439import org .w3c .dom .Document ;
4540import org .w3c .dom .Element ;
4641import org .w3c .dom .NodeList ;
42+ import org .xml .sax .EntityResolver ;
43+ import org .xml .sax .InputSource ;
4744
4845public class SimpleWFSHelper {
4946 private static final Logger logger =
@@ -82,7 +79,7 @@ public static URI getOGCRequestURL(
8279 // We need to encode the parameters manually because UriComponentsBuilder annoyingly does not
8380 // encode '+' as used in mime types for output formats, see
8481 // https://stackoverflow.com/questions/18138011
85- parameters .replaceAll ((key , values ) -> values .stream ()
82+ parameters .replaceAll ((unusedKey , values ) -> values .stream ()
8683 .map (s -> URLEncoder .encode (s , StandardCharsets .UTF_8 ))
8784 .collect (Collectors .toList ()));
8885 params .addAll (parameters );
@@ -119,17 +116,7 @@ public static List<String> getOutputFormats(
119116 HttpResponse <InputStream > response =
120117 httpClient .send (requestBuilder .build (), HttpResponse .BodyHandlers .ofInputStream ());
121118
122- // Parse capabilities in DOM
123-
124- DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory .newInstance ();
125- documentBuilderFactory .setNamespaceAware (true );
126- documentBuilderFactory .setExpandEntityReferences (false );
127- documentBuilderFactory .setValidating (false );
128- documentBuilderFactory .setXIncludeAware (false );
129- documentBuilderFactory .setCoalescing (true );
130- documentBuilderFactory .setFeature (XMLConstants .FEATURE_SECURE_PROCESSING , true );
131- DocumentBuilder documentBuilder = documentBuilderFactory .newDocumentBuilder ();
132- Document doc = documentBuilder .parse (response .body ());
119+ Document doc = parseSecureXml (response .body ());
133120
134121 // WFS 1.1.0 and WFS 2.0.0 use different namespaces, but the same local element names
135122 boolean wfs2 = "2.0.0" .equals (doc .getDocumentElement ().getAttribute ("version" ));
@@ -189,30 +176,18 @@ public static SimpleWFSLayerDescription describeWMSLayer(
189176 public static Map <String , SimpleWFSLayerDescription > describeWMSLayers (
190177 String url , String username , String password , List <String > layers ) {
191178 try {
192- HTTPClient client = new SimpleHttpClient ();
193- client .setUser (username );
194- client .setPassword (password );
195- client .setConnectTimeout (TIMEOUT );
196- client .setReadTimeout (TIMEOUT );
197- WebMapServer wms = new WebMapServer (new URI (url ).toURL (), client );
198- // Directly create WMS 1.1.1 request. Creating it from WebMapServer errors with GeoServer
199- // about unsupported request in capabilities unless we override WebMapServer to set up
200- // specifications.
201- DescribeLayerRequest describeLayerRequest = new WMS1_1_1 ().createDescribeLayerRequest (new URI (url ).toURL ());
202- // XXX Otherwise GeoTools will send VERSION=1.1.0...
203- describeLayerRequest .setProperty ("VERSION" , "1.1.1" );
204- describeLayerRequest .setLayers (String .join ("," , layers ));
205- // GeoTools will throw a ClassCastException when a WMS ServiceException is returned
206- DescribeLayerResponse describeLayerResponse = wms .issueRequest (describeLayerRequest );
207-
208- Map <String , SimpleWFSLayerDescription > descriptions = new HashMap <>();
209- for (LayerDescription ld : describeLayerResponse .getLayerDescs ()) {
210- String wfsUrl = getWfsUrl (ld , wms );
211-
212- if (wfsUrl != null && ld .getQueries () != null && ld .getQueries ().length != 0 ) {
213- descriptions .put (ld .getName (), new SimpleWFSLayerDescription (wfsUrl , List .of (ld .getQueries ())));
214- }
215- }
179+ URI describeLayerUri = getDescribeLayerRequestUrl (url , layers );
180+ HttpClient httpClient = getDefaultHttpClient ();
181+
182+ HttpRequest .Builder requestBuilder = HttpRequest .newBuilder ().uri (describeLayerUri );
183+ setHttpBasicAuthenticationHeader (requestBuilder , username , password );
184+
185+ HttpResponse <InputStream > response =
186+ httpClient .send (requestBuilder .build (), HttpResponse .BodyHandlers .ofInputStream ());
187+
188+ Document document = parseXmlAllowingDocType (response .body ());
189+
190+ Map <String , SimpleWFSLayerDescription > descriptions = parseDescribeLayerResponse (document , url );
216191 return Collections .unmodifiableMap (descriptions );
217192 } catch (Exception e ) {
218193 String msg =
@@ -226,17 +201,81 @@ public static Map<String, SimpleWFSLayerDescription> describeWMSLayers(
226201 return Map .of ();
227202 }
228203
229- private static String getWfsUrl (LayerDescription ld , WebMapServer wms ) {
230- String wfsUrl = (ld .getWfs () != null ) ? ld .getWfs ().toString () : null ;
231- if (wfsUrl == null && "WFS" .equalsIgnoreCase (ld .getOwsType ())) {
232- wfsUrl = ld .getOwsURL ().toString ();
204+ private static URI getDescribeLayerRequestUrl (String url , List <String > layers ) {
205+ MultiValueMap <String , String > parameters = new LinkedMultiValueMap <>();
206+ parameters .add ("LAYERS" , String .join ("," , layers ));
207+ return getOGCRequestURL (url , "WMS" , "1.1.1" , "DescribeLayer" , parameters );
208+ }
209+
210+ private static Document parseSecureXml (InputStream inputStream ) throws Exception {
211+ return getDocumentBuilder ().parse (inputStream );
212+ }
213+
214+ private static DocumentBuilder getDocumentBuilder () throws ParserConfigurationException {
215+ DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory .newInstance ();
216+ documentBuilderFactory .setNamespaceAware (true );
217+ documentBuilderFactory .setExpandEntityReferences (false );
218+ documentBuilderFactory .setValidating (false );
219+ documentBuilderFactory .setXIncludeAware (false );
220+ documentBuilderFactory .setCoalescing (true );
221+ documentBuilderFactory .setFeature (XMLConstants .FEATURE_SECURE_PROCESSING , true );
222+ return documentBuilderFactory .newDocumentBuilder ();
223+ }
224+
225+ private static Document parseXmlAllowingDocType (InputStream inputStream ) throws Exception {
226+ DocumentBuilder documentBuilder = getDocumentBuilder ();
227+ EntityResolver entityResolver = (publicId , systemId ) -> new InputSource (new StringReader ("" ));
228+ documentBuilder .setEntityResolver (entityResolver );
229+
230+ return documentBuilder .parse (inputStream );
231+ }
232+
233+ private static Map <String , SimpleWFSLayerDescription > parseDescribeLayerResponse (
234+ Document document , String fallbackUrl ) {
235+ Element root = document .getDocumentElement ();
236+ if (root == null ) {
237+ return Map .of ();
238+ }
239+
240+ NodeList layerDescriptions = root .getElementsByTagNameNS ("*" , "LayerDescription" );
241+ Map <String , SimpleWFSLayerDescription > descriptions = new HashMap <>();
242+
243+ for (int i = 0 ; i < layerDescriptions .getLength (); i ++) {
244+ Element layerDescription = (Element ) layerDescriptions .item (i );
245+
246+ String layerName = layerDescription .getAttribute ("name" );
247+ String wfsUrl = getDescribeLayerWfsUrl (layerDescription , fallbackUrl );
248+
249+ List <String > typeNames = new ArrayList <>();
250+ NodeList queryNodes = layerDescription .getElementsByTagNameNS ("*" , "Query" );
251+ for (int j = 0 ; j < queryNodes .getLength (); j ++) {
252+ Element query = (Element ) queryNodes .item (j );
253+ String typeName = query .getAttribute ("typeName" );
254+ if (!typeName .isBlank ()) {
255+ typeNames .add (typeName );
256+ }
257+ }
258+
259+ if (!layerName .isBlank () && wfsUrl != null && !typeNames .isEmpty ()) {
260+ descriptions .put (layerName , new SimpleWFSLayerDescription (wfsUrl , List .copyOf (typeNames )));
261+ }
233262 }
234- // OGC 02-070 Annex B says the wfs/owsURL attributed are not required but implied. Some
235- // Deegree instance encountered has all attributes empty, and apparently the meaning is that
236- // the WFS URL is the same as the WMS URL (not explicitly defined in the spec).
237- if (wfsUrl == null ) {
238- wfsUrl = wms .getInfo ().getSource ().toString ();
263+
264+ return descriptions ;
265+ }
266+
267+ private static String getDescribeLayerWfsUrl (Element layerDescription , String fallbackUrl ) {
268+ String wfsUrl = layerDescription .getAttribute ("wfs" );
269+ if (!wfsUrl .isBlank ()) {
270+ return wfsUrl ;
239271 }
240- return wfsUrl ;
272+
273+ String owsType = layerDescription .getAttribute ("owsType" );
274+ String owsUrl = layerDescription .getAttribute ("owsURL" );
275+ if ("WFS" .equalsIgnoreCase (owsType ) && !owsUrl .isBlank ()) {
276+ return owsUrl ;
277+ }
278+
279+ return fallbackUrl ;
241280 }
242281}
0 commit comments