Skip to content

Commit 1de3553

Browse files
committed
GEOS-11850 Pretty-print text/mapml responses based on GeoServer global verbose setting
1 parent 55dd176 commit 1de3553

File tree

11 files changed

+432
-37
lines changed

11 files changed

+432
-37
lines changed
6.26 KB
Loading
26.2 KB
Loading

doc/en/user/source/extensions/mapml/installation.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,14 @@ For example, the UTM14WGS84Quad specified in the above selector has the followin
8080

8181
.. figure:: images/mapml_utm_gridset.png
8282

83+
Global Settings
84+
---------------
85+
86+
.. figure:: images/mapml_global_menu.png
87+
88+
The Global settings menu (above) contains a Service Response Settings section (below) which contains the Verbose XML output (pretty print) checkbox. The MapML extension respects or uses this setting when serializing text/mapml (media type) responses. Be aware that caching on both the client and server may prevent this setting from becoming immediately obvious in devtools responses. Refreshing the browser cache can request a new version of the response, but if the response is cached on the server, for example as a vector tile, it may not be possible to obtain a pretty printed version of the data, short of deleting the tile cache, which may be undesirable.
8389

90+
.. figure:: images/mapml_global_verbose_output.png
8491

8592
Styles
8693
------

src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLEncoder.java

Lines changed: 106 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,23 @@ private Marshaller createMarshaller() throws JAXBException {
5151
* @param output OutputStream
5252
*/
5353
public void encode(Mapml mapml, OutputStream output) {
54+
encode(mapml, output, false);
55+
}
56+
57+
/**
58+
* Use Marshaller to encode MapML object onto an output stream with optional pretty-printing
59+
*
60+
* @param mapml MapML object
61+
* @param output OutputStream
62+
* @param prettyPrint true to enable pretty-printing with 2-space indents, false for dense markup
63+
*/
64+
public void encode(Mapml mapml, OutputStream output, boolean prettyPrint) {
5465
try {
55-
XMLStreamWriter writer = new Wrapper(XMLOutputFactory.newInstance().createXMLStreamWriter(output));
56-
createMarshaller().marshal(mapml, writer);
57-
writer.flush();
66+
XMLOutputFactory factory = XMLOutputFactory.newInstance();
67+
Wrapper wrapper = new Wrapper(factory.createXMLStreamWriter(output));
68+
wrapper.setIndenting(prettyPrint);
69+
createMarshaller().marshal(mapml, wrapper);
70+
wrapper.flush();
5871
} catch (JAXBException | XMLStreamException e) {
5972
throw new ServiceException(e);
6073
}
@@ -81,6 +94,13 @@ static class Wrapper implements XMLStreamWriter {
8194

8295
private final XMLStreamWriter writer;
8396
private static final String NS_PREFIX = "";
97+
public static final String MAPML_INDENT_PROPERTY = "mapml.indent";
98+
private static final String INDENT = " ";
99+
100+
private boolean indenting = false;
101+
private int depth = 0;
102+
private boolean needsIndent = false;
103+
private boolean lastWasStartElement = false;
84104

85105
/**
86106
* Constructor
@@ -91,39 +111,90 @@ static class Wrapper implements XMLStreamWriter {
91111
this.writer = writer;
92112
}
93113

114+
/** Writes indentation if pretty-printing is enabled */
115+
private void writeIndent() throws XMLStreamException {
116+
if (indenting && needsIndent) {
117+
writer.writeCharacters("\n");
118+
for (int i = 0; i < depth; i++) {
119+
writer.writeCharacters(INDENT);
120+
}
121+
needsIndent = false;
122+
}
123+
}
124+
94125
@Override
95126
public void writeStartElement(String localName) throws XMLStreamException {
127+
writeIndent();
96128
writer.writeStartElement(localName);
129+
depth++;
130+
needsIndent = true;
131+
lastWasStartElement = true;
97132
}
98133

99134
@Override
100135
public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException {
136+
writeIndent();
101137
writer.writeStartElement(namespaceURI, localName);
138+
depth++;
139+
needsIndent = true;
140+
lastWasStartElement = true;
102141
}
103142

104143
@Override
105144
public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
145+
writeIndent();
106146
writer.writeStartElement(NS_PREFIX, localName, namespaceURI);
147+
depth++;
148+
needsIndent = true;
149+
lastWasStartElement = true;
107150
}
108151

109152
@Override
110153
public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException {
111-
writer.writeEmptyElement(namespaceURI, localName);
154+
// Force HTML-compatible empty elements with explicit end tags
155+
writeIndent();
156+
writer.writeStartElement(namespaceURI, localName);
157+
writer.writeEndElement();
158+
needsIndent = true;
112159
}
113160

114161
@Override
115162
public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
116-
writer.writeEmptyElement(prefix, localName, namespaceURI);
163+
// Force HTML-compatible empty elements with explicit end tags
164+
writeIndent();
165+
writer.writeStartElement(NS_PREFIX, localName, namespaceURI);
166+
writer.writeEndElement();
167+
needsIndent = true;
117168
}
118169

119170
@Override
120171
public void writeEmptyElement(String localName) throws XMLStreamException {
121-
writer.writeEmptyElement(localName);
172+
// Force HTML-compatible empty elements with explicit end tags
173+
writeIndent();
174+
writer.writeStartElement(localName);
175+
writer.writeEndElement();
176+
needsIndent = true;
122177
}
123178

124179
@Override
125180
public void writeEndElement() throws XMLStreamException {
181+
depth--;
182+
// For empty elements (start immediately followed by end), keep on same line
183+
if (needsIndent && !lastWasStartElement) {
184+
writeIndent();
185+
} else if (lastWasStartElement) {
186+
// Note: empty string typically doesn't add content but signals "non-empty" to writer
187+
writer.writeCharacters("");
188+
}
126189
writer.writeEndElement();
190+
191+
// Add two newlines after the root closing tag
192+
if (depth == 0 && indenting) {
193+
writer.writeCharacters("\n\n");
194+
}
195+
196+
needsIndent = true;
197+
lastWasStartElement = false;
127198
}
128199

129200
@Override
@@ -169,7 +240,9 @@ public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException
169240

170241
@Override
171242
public void writeComment(String data) throws XMLStreamException {
243+
writeIndent();
172244
writer.writeComment(data);
245+
needsIndent = true;
173246
}
174247

175248
@Override
@@ -214,11 +287,17 @@ public void writeStartDocument(String encoding, String version) throws XMLStream
214287

215288
@Override
216289
public void writeCharacters(String text) throws XMLStreamException {
290+
// Don't indent before text content as it would alter the actual content
291+
needsIndent = false;
292+
lastWasStartElement = false;
217293
writer.writeCharacters(text);
218294
}
219295

220296
@Override
221297
public void writeCharacters(char[] text, int start, int len) throws XMLStreamException {
298+
// Don't indent before text content as it would alter the actual content
299+
needsIndent = false;
300+
lastWasStartElement = false;
222301
writer.writeCharacters(text, start, len);
223302
}
224303

@@ -249,7 +328,28 @@ public NamespaceContext getNamespaceContext() {
249328

250329
@Override
251330
public Object getProperty(String name) throws IllegalArgumentException {
331+
if (MAPML_INDENT_PROPERTY.equals(name)) {
332+
return indenting;
333+
}
252334
return writer.getProperty(name);
253335
}
336+
337+
/**
338+
* Sets the indenting property for pretty-printing
339+
*
340+
* @param indent true to enable pretty-printing with 2-space indents, false for dense markup
341+
*/
342+
public void setIndenting(boolean indent) {
343+
this.indenting = indent;
344+
}
345+
346+
/**
347+
* Gets the current indenting state
348+
*
349+
* @return true if pretty-printing is enabled
350+
*/
351+
public boolean isIndenting() {
352+
return indenting;
353+
}
254354
}
255355
}

src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLGetFeatureInfoOutputFormat.java

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import java.io.IOException;
99
import java.io.OutputStream;
10-
import java.io.OutputStreamWriter;
1110
import java.util.HashMap;
1211
import java.util.Iterator;
1312
import java.util.List;
@@ -16,8 +15,6 @@
1615
import java.util.function.Function;
1716
import java.util.logging.Level;
1817
import java.util.logging.Logger;
19-
import javax.xml.transform.Result;
20-
import javax.xml.transform.stream.StreamResult;
2118
import net.opengis.wfs.FeatureCollectionType;
2219
import org.geoserver.catalog.Catalog;
2320
import org.geoserver.catalog.FeatureTypeInfo;
@@ -47,7 +44,6 @@
4744
import org.geotools.feature.FeatureCollection;
4845
import org.geotools.util.logging.Logging;
4946
import org.springframework.beans.factory.annotation.Autowired;
50-
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
5147

5248
/**
5349
* @author Chris Hodgson
@@ -57,7 +53,7 @@ public class MapMLGetFeatureInfoOutputFormat extends GetFeatureInfoOutputFormat
5753
private static final Logger LOGGER = Logging.getLogger("org.geoserver.mapml");
5854

5955
@Autowired
60-
private Jaxb2Marshaller mapmlMarshaller;
56+
private MapMLEncoder mapMLEncoder;
6157

6258
private WMS wms;
6359

@@ -157,10 +153,9 @@ public void write(FeatureCollectionType results, GetFeatureInfoRequest request,
157153
}
158154
}
159155

160-
OutputStreamWriter osw = new OutputStreamWriter(out, wms.getCharSet());
161-
Result result = new StreamResult(osw);
162-
mapmlMarshaller.marshal(mapml, result);
163-
osw.flush();
156+
// write to output based on global verbose setting
157+
boolean verbose = wms.getGeoServer().getGlobal().getSettings().isVerbose();
158+
mapMLEncoder.encode(mapml, out, verbose);
164159
}
165160

166161
@Override

src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLGetFeatureOutputFormat.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,11 @@
77

88
import java.io.IOException;
99
import java.io.OutputStream;
10-
import java.io.OutputStreamWriter;
1110
import java.net.URI;
1211
import java.util.ArrayList;
1312
import java.util.List;
1413
import java.util.Map;
1514
import java.util.logging.Logger;
16-
import javax.xml.transform.Result;
17-
import javax.xml.transform.stream.StreamResult;
1815
import net.opengis.wfs.impl.GetFeatureTypeImpl;
1916
import net.opengis.wfs.impl.QueryTypeImpl;
2017
import org.geoserver.catalog.LayerInfo;
@@ -32,7 +29,6 @@
3229
import org.geotools.referencing.CRS;
3330
import org.geotools.util.logging.Logging;
3431
import org.springframework.beans.factory.annotation.Autowired;
35-
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
3632

3733
/**
3834
* @author Chris Hodgson
@@ -42,7 +38,7 @@ public class MapMLGetFeatureOutputFormat extends WFSGetFeatureOutputFormat {
4238
private static final Logger LOGGER = Logging.getLogger(MapMLGetFeatureOutputFormat.class);
4339

4440
@Autowired
45-
private Jaxb2Marshaller mapmlMarshaller;
41+
private MapMLEncoder mapMLEncoder;
4642

4743
private String base;
4844
private String path;
@@ -97,11 +93,9 @@ protected void write(FeatureCollectionResponse featureCollectionResponse, Output
9793
null,
9894
null);
9995

100-
// write to output
101-
OutputStreamWriter osw = new OutputStreamWriter(out, gs.getSettings().getCharset());
102-
Result result = new StreamResult(osw);
103-
mapmlMarshaller.marshal(mapml, result);
104-
osw.flush();
96+
// write to output based on global verbose setting
97+
boolean verbose = gs.getGlobal().getSettings().isVerbose();
98+
mapMLEncoder.encode(mapml, out, verbose);
10599
}
106100

107101
/**

src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLMapOutputFormat.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ public WebMap produceMap(WMSMapContent mapContent) throws ServiceException, IOEx
7373
mapMLDocument = mapMLDocumentBuilder.getMapMLDocument();
7474
}
7575
ByteArrayOutputStream bos = new ByteArrayOutputStream();
76-
encoder.encode(mapMLDocument, bos);
76+
// write to output based on global verbose setting
77+
boolean verbose = geoServer.getGlobal().getSettings().isVerbose();
78+
encoder.encode(mapMLDocument, bos, verbose);
7779
return new RawMap(mapContent, bos, MapMLConstants.MAPML_MIME_TYPE);
7880
}
7981

src/extension/mapml/src/main/java/org/geoserver/mapml/MapMLMessageConverter.java

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,12 @@
55
package org.geoserver.mapml;
66

77
import java.io.IOException;
8-
import java.io.OutputStreamWriter;
98
import java.io.UnsupportedEncodingException;
10-
import javax.xml.transform.Result;
11-
import javax.xml.transform.stream.StreamResult;
129
import org.geoserver.rest.converters.BaseMessageConverter;
1310
import org.springframework.beans.factory.annotation.Autowired;
1411
import org.springframework.http.HttpOutputMessage;
1512
import org.springframework.http.MediaType;
1613
import org.springframework.lang.Nullable;
17-
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
1814

1915
/**
2016
* @author Chris Hodgson
@@ -23,7 +19,7 @@
2319
public class MapMLMessageConverter extends BaseMessageConverter<Object> {
2420

2521
@Autowired
26-
private Jaxb2Marshaller mapmlMarshaller;
22+
private MapMLEncoder mapMLEncoder;
2723

2824
/** */
2925
public MapMLMessageConverter() {
@@ -47,7 +43,7 @@ public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
4743
*/
4844
@Override
4945
public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
50-
return canWrite(mediaType) && mapmlMarshaller.supports(clazz);
46+
return canWrite(mediaType) && org.geoserver.mapml.xml.Mapml.class.isAssignableFrom(clazz);
5147
}
5248

5349
/**
@@ -69,11 +65,12 @@ protected boolean supports(Class<?> clazz) {
6965
@Override
7066
protected void writeInternal(Object o, HttpOutputMessage outputMessage)
7167
throws UnsupportedEncodingException, IOException {
72-
try (OutputStreamWriter osw = new OutputStreamWriter(
73-
outputMessage.getBody(), geoServer.getSettings().getCharset())) {
74-
Result result = new StreamResult(osw);
75-
mapmlMarshaller.marshal(o, result);
76-
osw.flush();
68+
if (o instanceof org.geoserver.mapml.xml.Mapml) {
69+
// write to output based on global verbose setting
70+
boolean verbose = geoServer.getGlobal().getSettings().isVerbose();
71+
mapMLEncoder.encode((org.geoserver.mapml.xml.Mapml) o, outputMessage.getBody(), verbose);
72+
} else {
73+
throw new IllegalArgumentException("Can only write Mapml objects, got: " + o.getClass());
7774
}
7875
}
7976
}

0 commit comments

Comments
 (0)