001 //$HeadURL: http://svn.wald.intevation.org/svn/deegree/base/trunk/src/org/deegree/enterprise/servlet/ChartServlet.java $
002 /*----------------------------------------------------------------------------
003 This file is part of deegree, http://deegree.org/
004 Copyright (C) 2001-2009 by:
005 Department of Geography, University of Bonn
006 and
007 lat/lon GmbH
008
009 This library is free software; you can redistribute it and/or modify it under
010 the terms of the GNU Lesser General Public License as published by the Free
011 Software Foundation; either version 2.1 of the License, or (at your option)
012 any later version.
013 This library is distributed in the hope that it will be useful, but WITHOUT
014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016 details.
017 You should have received a copy of the GNU Lesser General Public License
018 along with this library; if not, write to the Free Software Foundation, Inc.,
019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020
021 Contact information:
022
023 lat/lon GmbH
024 Aennchenstr. 19, 53177 Bonn
025 Germany
026 http://lat-lon.de/
027
028 Department of Geography, University of Bonn
029 Prof. Dr. Klaus Greve
030 Postfach 1147, 53001 Bonn
031 Germany
032 http://www.geographie.uni-bonn.de/deegree/
033
034 e-mail: info@deegree.org
035 ----------------------------------------------------------------------------*/
036
037 package org.deegree.enterprise.servlet;
038
039 import java.awt.image.BufferedImage;
040 import java.io.IOException;
041 import java.net.URL;
042 import java.util.Iterator;
043 import java.util.Map;
044
045 import javax.servlet.ServletException;
046 import javax.servlet.http.HttpServlet;
047 import javax.servlet.http.HttpServletRequest;
048 import javax.servlet.http.HttpServletResponse;
049
050 import org.deegree.framework.log.ILogger;
051 import org.deegree.framework.log.LoggerFactory;
052 import org.deegree.framework.util.ImageUtils;
053 import org.deegree.framework.util.KVP2Map;
054 import org.deegree.framework.util.MimeTypeMapper;
055 import org.deegree.graphics.charts.ChartConfig;
056 import org.deegree.graphics.charts.ChartsBuilder;
057 import org.deegree.graphics.charts.IncorrectFormatException;
058 import org.deegree.graphics.charts.QueuedMap;
059 import org.deegree.i18n.Messages;
060
061 /**
062 * <code>
063 * A Servlet that gets an http GET request, with the de.latlon.charts parameters and it generates
064 * and returns a BufferedImage based on these parameters. The request should look as follows:
065 * http://www.someserver.com/servletname?
066 * chart=ChartType& ( Mandatory )
067 * title=string&
068 * legend=boolean& ( default = true )
069 * width=int& ( Mandatory )
070 * height=int& ( Mandatory )
071 * xAxis=string&
072 * yAxis=string&
073 * tooltips=boolean& ( default = false )
074 * lblType Determines in PieChart what the legend and the tool tip to be. You can
075 * either show the data key, value or both. Possible values for this parameter are:
076 * <i>Key</i>, <i>Value</i> and <i>KeyValue</i> ( default = Key )
077 * orientation=direction& ( default = vertical )
078 * format=image_format& ( Mandatory )
079 * value=value ( Mandatory )
080 *
081 * The keys are the following
082 * I) Chart Type: Available chart Types are:
083 * 1- Pie
084 * 2- Pie3D
085 * 3- Bar
086 * 4- Bar3D
087 * 5- Line
088 * 6- Line3D
089 * 7- XYBar
090 * 8- XYLine
091 *
092 * II) title: Title of the generated image
093 * II) legend: true/false to show/hide the legend of the categories/rows
094 * III) width: Width of the generated image
095 * IV) height: Height of the generated image
096 * V) xAxis Title of the
097 * X Axis Title of xAxis
098 * VI) yAxis Title of the Y Axis
099 * VII) tooltips Show/hide tool tips
100 * VIII)orientation vertical/horizontal. the orientation of the chart
101 * IX) format format of the output image, ex: image/png and image/jpeg
102 * X) type There is actually no parameter called type.
103 * It only indicates the more parameters that carry the actual values for the chart.
104 * They can have different three different formats. but only one format type shall be used
105 * per request. The values can be given as normal key value pairs as follows:
106 *
107 * Format 1: &CDU=34&SPD=36&Gruene=11 - In this format the key has only one value
108 * - This type can only be used together with the chart type: Pie, Pie3D, Bar, Line and Line3D
109 *
110 * Format 2: &CDU=23,25,21,26&SPD=42,23,33,36
111 * - In this format multiple values per keys can be given - Either comma or white spaces can be
112 * used as separators(white spaces are of course encoded in case of a Http Get request)
113 * - This type can only be used together with the chart type: Bar, Line and ine3D
114 *
115 * Format 3: &CDU=1970,23;1974,44;1978,43&SPD=1970,23;1974,44;1978,43
116 * - In this format the key has multiple value, in which each value is a tuple that contains in
117 * turn two tokens representing x and y of a cartesian point. This format is
118 * used to create cartesian de.latlon.charts
119 * - comma and white spacescan be used as separators between tokens, i.e. between x and y,
120 * while semi colon shall be used as a separator between tupels.
121 * - This type can only be used together with XYLine (and later with XYBar)
122 * </code>
123 *
124 * @author <a href="mailto:elmasry@lat-lon.de">Moataz Elmasry</a>
125 * @author last edited by: $Author: apoth $
126 *
127 * @version $Revision: 29949 $, $Date: 2011-03-09 13:08:56 +0100 (Wed, 09 Mar 2011) $
128 */
129 public class ChartServlet extends HttpServlet {
130
131 private static final ILogger LOG = LoggerFactory.getLogger( ChartServlet.class );
132
133 private String errorMsgPath = null;
134
135 private ChartConfig configs = null;
136
137 /*
138 * (non-Javadoc)
139 *
140 * @see javax.servlet.GenericServlet#init()
141 */
142 @Override
143 public void init()
144 throws ServletException {
145 errorMsgPath = this.getServletContext().getRealPath( this.getInitParameter( "errorMsg" ) );
146 try {
147 String filePath = this.getServletContext().getRealPath( this.getInitParameter( "ConfigFile" ) );
148 if ( !filePath.startsWith( "file:///" ) ) {
149 filePath = "file:///" + filePath;
150 }
151 configs = new ChartConfig( new URL( filePath ) );
152 } catch ( Exception e ) {
153 LOG.logError( e.getLocalizedMessage() );
154 LOG.logError( Messages.getMessage( "GRA_CHART_SERVLET_FAIL", new Object[] {} ) );
155 }
156 }
157
158 private static final long serialVersionUID = -374323170888642652L;
159
160 /*
161 * (non-Javadoc)
162 *
163 * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
164 * javax.servlet.http.HttpServletResponse)
165 */
166 @Override
167 protected void doGet( HttpServletRequest request, HttpServletResponse response )
168 throws ServletException, IOException {
169 performAction( request, response );
170 }
171
172 /**
173 * @param request
174 * @param response
175 */
176 protected void performAction( HttpServletRequest request, HttpServletResponse response ) {
177
178 /*
179 * chart=ChartType& title=string& legend=boolean& width=int& height=int& xAxis=string& yAxis=string&
180 * tooltips=boolean& orientation=direction& format=image_format& value=value
181 */
182
183 Map<String, String> keyedValues = null;
184 try {
185 keyedValues = initAndValidateParams( request );
186 } catch ( Exception e ) {
187 LOG.logError( e.getLocalizedMessage() );
188 goToErrorPage( response );
189 return;
190 }
191
192 /**
193 * TODO introduce key "SOSURL" to point towards a SOS. In case a SOSURL is provided, the URL will be queried and
194 * it will be assumed that a GetObservation request is returned.
195 *
196 */
197 // probably not required any more
198
199 // for ( String key : keyedValues.keySet() ) {
200 // String value = keyedValues.get( key );
201 // try {
202 // String encoding = Charset.defaultCharset().name();
203 // if ( request.getCharacterEncoding() != null ) {
204 // encoding = request.getCharacterEncoding();
205 // }
206 // String dec = URLDecoder.decode( value, encoding );
207 // keyedValues.put( key, dec );
208 // } catch ( Exception e ) {
209 // LOG.logError( e.getLocalizedMessage() );
210 // }
211 // }
212
213 String format = request.getParameter( "format" );
214 String style = null;
215 ChartConfig chartConfigs = null;
216 if ( ( style = request.getParameter( "style" ) ) != null ) {
217 try {
218 chartConfigs = new ChartConfig( new URL( style ) );
219 } catch ( Exception e ) {
220 // Its not a big problem that the chartConfig is null. In this case the default chart config will be
221 // used
222 LOG.logError( Messages.getMessage( "GRA_CHART_INVALID_URL", style ) );
223 }
224 }
225 try {
226 BufferedImage img = buildChart( request.getParameter( "chart" ), request.getParameter( "title" ),
227 request.getParameter( "legend" ), request.getParameter( "width" ),
228 request.getParameter( "height" ), request.getParameter( "xAxis" ),
229 request.getParameter( "yAxis" ), request.getParameter( "tooltips" ),
230 request.getParameter( "orientation" ), request.getParameter( "lblType" ),
231 request.getParameter( "format" ), keyedValues, chartConfigs );
232 String imgType = null;
233 if ( MimeTypeMapper.isKnownImageType( format ) ) {
234 int index = format.indexOf( "/" );
235 imgType = format.substring( index + 1, format.length() );
236 } else {
237 throw new IncorrectFormatException( "The given image type is not a supported type" );
238 }
239 try {
240 ImageUtils.saveImage( img, response.getOutputStream(), imgType, 1.0f );
241 } catch ( Exception e ) {
242 // Nothing. An exception happened here, because the GET request is called multiple times.
243 // This exception happens on the second and third time.
244 }
245 } catch ( Exception e ) {
246 LOG.logError( e.getLocalizedMessage() );
247 goToErrorPage( response );
248 }
249 }
250
251 /**
252 * Init and validate params does what its name say. It extracts the mandatory parameters from the HttpServletRequest
253 * class and checks their validity. Then it fills a queued map with all parameters, that are not defined, because
254 * these are the candidates to be the parameters for the chart
255 *
256 * @param request
257 * @return instance of the queued map with
258 * @throws IncorrectFormatException
259 *
260 */
261 @SuppressWarnings("unchecked")
262 protected QueuedMap<String, String> initAndValidateParams( HttpServletRequest request )
263 throws IncorrectFormatException {
264
265 QueuedMap<String, String> keyedValues = new QueuedMap<String, String>();
266
267 Map<String, String> param = KVP2Map.toMap( request );
268
269 String chart = param.get( "CHART" );
270 String width = param.get( "WIDTH" );
271 String height = param.get( "HEIGHT" );
272 String format = param.get( "FORMAT" );
273
274 if ( chart == null ) {
275 throw new IncorrectFormatException( Messages.getMessage( "GRA_CHART_ERROR_NULL_PARAM", "chart" ) );
276 }
277
278 if ( width == null ) {
279 throw new IncorrectFormatException( Messages.getMessage( "GRA_CHART_ERROR_NULL_PARAM", "width" ) );
280 }
281
282 if ( height == null ) {
283 throw new IncorrectFormatException( Messages.getMessage( "GRA_CHART_ERROR_NULL_PARAM", "height" ) );
284 }
285
286 if ( format == null ) {
287 throw new IncorrectFormatException( Messages.getMessage( "GRA_CHART_ERROR_NULL_PARAM", "format" ) );
288 } else if ( !MimeTypeMapper.isKnownImageType( format ) ) {
289 throw new IncorrectFormatException( Messages.getMessage( "GRA_CHART_ERROR_UNSUPPORTED_TYPE" ) );
290 }
291
292 Iterator<String> it = request.getParameterMap().keySet().iterator();
293 while ( it.hasNext() ) {
294 String key = it.next();
295 if ( !key.equals( "chart" ) && !key.equals( "title" ) && !key.equals( "legend" ) && !key.equals( "width" )
296 && !key.equals( "height" ) && !key.equals( "xAxis" ) && !key.equals( "yAxis" )
297 && !key.equals( "tooltips" ) && !key.equals( "orientation" ) && !key.equals( "format" )
298 && !key.equals( "style" ) ) {
299 keyedValues.put( key, request.getParameter( key ) );
300 }
301 }
302 return keyedValues;
303 }
304
305 /**
306 * Creates a buffered image that contains an error label. It is used to display an error instead of a chart in case
307 * an error happened
308 *
309 * @param response
310 */
311 protected void goToErrorPage( HttpServletResponse response ) {
312 if ( errorMsgPath == null ) {
313 LOG.logError( Messages.getMessage( "GRA_CHART_MISSING_ERROR_IMAGE", new Object[] {} ) );
314 return;
315 }
316 try {
317 BufferedImage img = ImageUtils.loadImage( errorMsgPath );
318 ImageUtils.saveImage( img, response.getOutputStream(), "gif", 1.0f );
319
320 } catch ( Exception e ) {
321 LOG.logError( e.getLocalizedMessage() );
322 }
323 }
324
325 /**
326 * Takes in all the needed parameters and a queued map containing the values of the chart and returns a
327 * BufferedImage with the chart
328 *
329 * @param chart
330 * @param title
331 * @param legend
332 * @param width
333 * @param height
334 * @param xAxis
335 * @param yAxis
336 * @param tooltips
337 * @param orientation
338 * @param lblType
339 * @param imagetype
340 * @param params
341 * @param chartConfigs
342 * to configure the output chart, or null to use the default ChartConfig
343 * @return BufferedImage containing the chart
344 * @throws IncorrectFormatException
345 */
346 protected BufferedImage buildChart( String chart, String title, String legend, String width, String height,
347 String xAxis, String yAxis, String tooltips, String orientation,
348 String lblType, String imagetype, Map<String, String> params,
349 ChartConfig chartConfigs )
350 throws IncorrectFormatException {
351 if ( configs == null ) {
352 throw new IncorrectFormatException( Messages.getMessage( "GRA_CHART_ERROR_SERVLET_BROKEN", new Object[] {} ) );
353 }
354
355 ChartsBuilder builder = null;
356 try {
357 builder = new ChartsBuilder( configs );
358 } catch ( Exception e ) {
359 throw new IncorrectFormatException( e.getLocalizedMessage() );
360 }
361 boolean legend2 = "true".equals( legend );
362 int width2 = Integer.parseInt( width );
363 int height2 = Integer.parseInt( height );
364 boolean tooltips2 = "true".equals( tooltips );
365 int orientation2 = "horizontal".equals( orientation ) ? ChartsBuilder.ORIENTATION_HORIZONTAL
366 : ChartsBuilder.ORIENTATION_VERTICAL;
367 QueuedMap<String, String> map = new QueuedMap<String, String>();
368 map.putAll( params );
369
370 final String THREE_D = "3D";
371 /*
372 * * Format 1: &CDU=34&SPD=36&Gruene=11 - In this format the key has only one value - This type can only be used
373 * together with the chart type: Pie, Pie3D, Bar, Line and Line3D
374 *
375 * Format 2: &CDU=23,25,21,26&SPD=42,23,33,36 - In this format multiple values per keys can be given - Either
376 * comma or white spaces can be used as separators - This type can only be used together with the chart type:
377 * Bar, Line and Line3D
378 *
379 * Format 3: &CDU=1970 23;1974,44;1978,43&SPD=1970,23;1974,44;1978,43 - In this format the key has multiple
380 * value, in which each value is a tuple that contains in turn two tokens representing x and y of a cartesian
381 * point. This format is used to create cartesian de.latlon.charts - comma and white spacescan be used as
382 * separators between tokens, i.e. between x and y, while semi colon shall be used as a separator between
383 * tupels. - This type can only be used together with XYBar and XYBar
384 */
385 if ( chart.equals( "Pie" ) || chart.equals( "Pie3D" ) ) {
386 QueuedMap<String, Double> map2 = new QueuedMap<String, Double>();
387 Iterator<String> it = map.keySet().iterator();
388 while ( it.hasNext() ) {
389 String key = it.next();
390 String value = map.get( key );
391 try {
392 if ( value != null ) {
393 map2.put( key, Double.parseDouble( value ) );
394 }
395 } catch ( Exception e ) {
396 // It's not a problem. There might be parameters, that are not values.
397 }
398 }
399 return builder.createPieChart( title, map2, width2, height2, chart.contains( THREE_D ), legend2, tooltips2,
400 lblType, imagetype, chartConfigs );
401 } else if ( chart.equals( "Line" ) || chart.equals( "Line3D" ) ) {
402 return builder.createLineChart( title, map, width2, height2, chart.contains( THREE_D ), legend2, tooltips2,
403 orientation2, imagetype, xAxis, yAxis, chartConfigs );
404 } else if ( chart.equals( "Bar" ) || chart.equals( "Bar3D" ) ) {
405 return builder.createBarChart( title, map, width2, height2, chart.contains( THREE_D ), legend2, tooltips2,
406 orientation2, imagetype, xAxis, yAxis, chartConfigs );
407 } else if ( chart.equals( "XYLine" ) ) {
408 return builder.createXYLineChart( title, map, width2, height2, legend2, tooltips2, orientation2, imagetype,
409 xAxis, yAxis, chartConfigs );
410 } else {
411 throw new IncorrectFormatException( Messages.getMessage( "GRA_CHART_UNSUPPORTED_TYPE", chart ) );
412 }
413 }
414 }