View Javadoc
1   /*
2    * Copyright (c) 2012-2014, Dienst Landelijk Gebied - Ministerie van Economische Zaken
3    *
4    * Gepubliceerd onder de BSD 2-clause licentie,
5    * zie https://github.com/MinELenI/CBSviewer/blob/master/LICENSE.md voor de volledige licentie.
6    */
7   package nl.mineleni.cbsviewer.servlet.wms;
8   
9   import java.io.BufferedReader;
10  import java.io.IOException;
11  import java.io.InputStream;
12  import java.io.InputStreamReader;
13  import java.io.Reader;
14  import java.io.StringWriter;
15  import java.io.Writer;
16  import java.util.Iterator;
17  
18  import javax.xml.parsers.ParserConfigurationException;
19  
20  import nl.mineleni.cbsviewer.util.LabelsBundle;
21  import nl.mineleni.cbsviewer.util.xml.LayerDescriptor;
22  
23  import org.geotools.GML;
24  import org.geotools.GML.Version;
25  import org.geotools.data.simple.SimpleFeatureCollection;
26  import org.geotools.data.simple.SimpleFeatureIterator;
27  import org.geotools.feature.DefaultFeatureCollection;
28  import org.geotools.feature.simple.SimpleFeatureBuilder;
29  import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
30  import org.jsoup.Jsoup;
31  import org.jsoup.nodes.Element;
32  import org.opengis.feature.simple.SimpleFeature;
33  import org.opengis.feature.simple.SimpleFeatureType;
34  import org.opengis.geometry.primitive.Point;
35  import org.opengis.referencing.crs.CoordinateReferenceSystem;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  import org.xml.sax.SAXException;
39  
40  /**
41   * Utility klasse FeatureInfoResponseConverter kan gebruikt worden om
42   * FeatureInfo responses te parsen en te converteren naar een andere vorm.
43   *
44   * @author mprins
45   * @since 1.7
46   *
47   * @has 1 - 1 AttributesNamesFilter
48   * @has 1 - 1 AttributeValuesFilter
49   */
50  public final class FeatureInfoResponseConverter {
51  
52  	/**
53  	 * ondersteunde typen voor conversie.
54  	 */
55  	public enum CONVERTER_TYPE {
56  
57  		/** The gmltype. */
58  		GMLTYPE("application/vnd.ogc.gml"),
59  		/** The htmltype. */
60  		HTMLTYPE("text/html"),
61  		/** xml type. (UNSUPPORTED) */
62  		XMLTYPE("application/vnd.ogc.wms_xml");
63  
64  		/** het conversie type. */
65  		private final String type;
66  
67  		/**
68  		 * enum constructor.
69  		 *
70  		 * @param type
71  		 *            het conversie type
72  		 */
73  		CONVERTER_TYPE(final String type) {
74  			this.type = type;
75  		}
76  
77  		/**
78  		 * String value van dit object.
79  		 *
80  		 * @return de waarde van dit object als string
81  		 * @see java.lang.Enum#toString()
82  		 * @see java.lang.Number#toString()
83  		 */
84  		@Override
85  		public String toString() {
86  			return this.type;
87  		}
88  	}
89  
90  	/** byte array buffer size, 1KB. */
91  	private static final int BUFFERSIZE = 1024;
92  
93  	/** logger. */
94  	private static final Logger LOGGER = LoggerFactory
95  	        .getLogger(FeatureInfoResponseConverter.class);
96  
97  	/** attribuut namen filter. */
98  	private static final AttributesNamesFilter NAMESFILTER = new AttributesNamesFilter();
99  
100 	/** resource bundle. */
101 	private static final LabelsBundle RESOURCES = new LabelsBundle();
102 
103 	/** attribuut waarden filter. */
104 	private static final AttributeValuesFilter VALUESFILTER = new AttributeValuesFilter();
105 
106 	/**
107 	 * private constructor.
108 	 */
109 	private FeatureInfoResponseConverter() {
110 		// private constructor voor utility klasse
111 	}
112 
113 	/**
114 	 * Cleanup html.
115 	 *
116 	 * @param htmlStream
117 	 *            input HTML stream, bijvoorbeeld uit een GetFeatureInfo
118 	 *            request.
119 	 * @param layer
120 	 *            De laag waarvoor deze functie wordt uitgevoerd
121 	 * @return opgeschoonde html tabel
122 	 * @throws IOException
123 	 *             Signals that an I/O exception has occurred.
124 	 */
125 	private static String cleanupHTML(final InputStream htmlStream,
126 	        final LayerDescriptor layer) throws IOException {
127 		final Element table = Jsoup.parse(convertStreamToString(htmlStream))
128 		        .select("table").first();
129 		if (table == null) {
130 			LOGGER.debug("Geen attribuut info voor deze locatie/zoomnivo.");
131 			return RESOURCES.getString("KEY_INFO_GEEN_FEATURES");
132 		}
133 
134 		final DefaultFeatureCollection featureCollection = new DefaultFeatureCollection(
135 		        "internal");
136 		final SimpleFeatureTypeBuilder b = new SimpleFeatureTypeBuilder();
137 
138 		// 1e rij zijn headers, andere rijen zijn data, geheel converteren naar
139 		// een SimpleFeatureCollection
140 		final Element headers = table.select("tr:first-child").first();
141 		if (headers != null) {
142 			final Iterator<Element> iterTHs = headers.select("th").iterator();
143 			while (iterTHs.hasNext()) {
144 				b.add(iterTHs.next().text(), String.class);
145 			}
146 		}
147 		b.setDefaultGeometry("point");
148 		b.setCRS(null);
149 		b.nillable(true).add("point", Point.class,
150 		        (CoordinateReferenceSystem) null);
151 		b.setName("tablerow");
152 		final SimpleFeatureType type = b.buildFeatureType();
153 		final SimpleFeatureBuilder builder = new SimpleFeatureBuilder(type);
154 		final Iterator<Element> rows = table.select("tr:not(:first-child)")
155 		        .iterator();
156 		while (rows.hasNext()) {
157 			final Iterator<Element> iterTH = headers.select("th").iterator();
158 			final Iterator<Element> iterTDs = rows.next().select("td")
159 			        .iterator();
160 			final SimpleFeature f = builder.buildFeature(null);
161 			while (iterTDs.hasNext()) {
162 				f.setAttribute(iterTH.next().text(), iterTDs.next().text());
163 			}
164 			f.setDefaultGeometry(null);
165 			featureCollection.add(f);
166 		}
167 
168 		return featureCollectionConverter(featureCollection, layer);
169 	}
170 
171 	/**
172 	 * Converteer gml imputstream naar html tabel of een lege string.
173 	 *
174 	 * @param gmlStream
175 	 *            input GML stream, bijvoorbeeld uit een GetFeatureInfo request.
176 	 * @param layer
177 	 *            De laag waarvoor deze functie wordt uitgevoerd
178 	 * @return een html tabel
179 	 * @throws IOException
180 	 *             Signals that an I/O exception has occurred.
181 	 */
182 	private static String convertGML(final InputStream gmlStream,
183 	        final LayerDescriptor layer) throws IOException {
184 		try {
185 			final GML gml = new GML(Version.WFS1_0);
186 			return featureCollectionConverter(
187 			        gml.decodeFeatureCollection(gmlStream), layer);
188 		} catch (ParserConfigurationException | SAXException e) {
189 			LOGGER.error("Fout tijdens parsen van GML. ", e);
190 			return "";
191 		} finally {
192 			gmlStream.close();
193 		}
194 
195 	}
196 
197 	/**
198 	 * Converteert een stream naar een string.
199 	 *
200 	 * @param is
201 	 *            de InputStream met data
202 	 * @return de data als string
203 	 * @throws IOException
204 	 *             Signals that an I/O exception has occurred.
205 	 */
206 	private static String convertStreamToString(final InputStream is)
207 	        throws IOException {
208 		final Writer writer = new StringWriter();
209 		if (is != null) {
210 
211 			final char[] buffer = new char[BUFFERSIZE];
212 			try {
213 				final Reader reader = new BufferedReader(new InputStreamReader(
214 				        is, "UTF-8"));
215 				int n;
216 				while ((n = reader.read(buffer)) != -1) {
217 					writer.write(buffer, 0, n);
218 				}
219 			} finally {
220 				is.close();
221 			}
222 		}
223 		return writer.toString();
224 	}
225 
226 	/**
227 	 * Converteer de input naar een html tabel.
228 	 *
229 	 * @param input
230 	 *            inputstream met de featureinfo response.
231 	 * @param type
232 	 *            het type conversie, ondersteund zijn {@code "GMLTYPE"} en
233 	 *            {@code "HTMLTYPE"}
234 	 * @param layer
235 	 *            De laag waarvoor deze functie wordt uitgevoerd
236 	 * @return een html tabel
237 	 * @throws IOException
238 	 *             Signals that an I/O exception has occurred.
239 	 */
240 	public static String convertToHTMLTable(final InputStream input,
241 	        final CONVERTER_TYPE type, final LayerDescriptor layer)
242 	        throws IOException {
243 		switch (type) {
244 		case GMLTYPE:
245 			return convertGML(input, layer);
246 		case HTMLTYPE:
247 			return cleanupHTML(input, layer);
248 		case XMLTYPE:
249 			throw new IOException(CONVERTER_TYPE.XMLTYPE
250 			        + " (XMLTYPE) wordt niet ondersteund.");
251 		default:
252 			return convertStreamToString(input);
253 		}
254 	}
255 
256 	/**
257 	 * Feature collection converter.
258 	 *
259 	 * @param features
260 	 *            verzameling features die wordt omgezet
261 	 * @param layer
262 	 *            De laag waarvoor deze functie wordt uitgevoerd
263 	 * @return the string
264 	 */
265 	private static String featureCollectionConverter(
266 	        final SimpleFeatureCollection features, final LayerDescriptor layer) {
267 
268 		final StringBuilder sb = new StringBuilder();
269 		final String[] fieldNames = layer.getAttributes().split(",\\s*");
270 
271 		if ((features != null) && (features.size() > 0)) {
272 			// tabel maken
273 			sb.append("<table id=\"attribuutTabel\" class=\"attribuutTabel\">");
274 			sb.append("<caption>");
275 			sb.append(RESOURCES.getString("KEY_INFO_TABEL_CAPTION"));
276 			sb.append("</caption><thead><tr>");
277 			for (final String n : fieldNames) {
278 				sb.append("<th scope=\"col\">");
279 				sb.append(NAMESFILTER.filterValue(n, layer.getId()));
280 				sb.append("</th>");
281 			}
282 			sb.append("</tr></thead><tbody>");
283 			int i = 0;
284 			SimpleFeatureIterator iter = features.features();
285 			while (iter.hasNext()) {
286 				sb.append("<tr class=\"")
287 				        .append(((i++ % 2) == 0) ? "odd" : "even")
288 				        .append("\">");
289 				final SimpleFeature f = iter.next();
290 				for (final String n : fieldNames) {
291 					if (VALUESFILTER.hasFilters()) {
292 						sb.append("<td>")
293 						        .append(VALUESFILTER.filterValue(f
294 						                .getAttribute(n))).append("</td>");
295 					} else {
296 						sb.append("<td>").append(f.getAttribute(n))
297 						        .append("</td>");
298 					}
299 				}
300 				sb.append("</tr>");
301 			}
302 			sb.append("</tbody></table>");
303 			iter.close();
304 			iter = null;
305 			LOGGER.debug("Gemaakte HTML tabel: " + sb);
306 			return sb.toString();
307 		} else {
308 			LOGGER.debug("Geen attribuut info voor deze locatie/zoomnivo.");
309 			return RESOURCES.getString("KEY_INFO_GEEN_FEATURES");
310 		}
311 	}
312 }