001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/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: 19231 $, $Date: 2009-08-19 10:08:46 +0200 (Mi, 19. Aug 2009) $
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 *
174 * @param request
175 * @param response
176 * @deprecated due to a typo this method is deprecated. Use
177 * {@link #performAction(HttpServletRequest, HttpServletResponse)} instead.
178 */
179 @Deprecated
180 protected void PerformAction( HttpServletRequest request, HttpServletResponse response ) {
181 performAction( request, response );
182 }
183
184 /**
185 * @param request
186 * @param response
187 */
188 protected void performAction( HttpServletRequest request, HttpServletResponse response ) {
189
190 /*
191 * chart=ChartType& title=string& legend=boolean& width=int& height=int& xAxis=string& yAxis=string&
192 * tooltips=boolean& orientation=direction& format=image_format& value=value
193 */
194
195 Map<String, String> keyedValues = null;
196 try {
197 keyedValues = initAndValidateParams( request );
198 } catch ( Exception e ) {
199 LOG.logError( e.getLocalizedMessage() );
200 goToErrorPage( response );
201 return;
202 }
203
204 /**
205 * TODO introduce key "SOSURL" to point towards a SOS.
206 * In case a SOSURL is provided, the URL will be queried and it will be assumed that a
207 * GetObservation request is returned.
208 *
209 */
210 // probably not required any more
211
212 // for ( String key : keyedValues.keySet() ) {
213 // String value = keyedValues.get( key );
214 // try {
215 // String encoding = Charset.defaultCharset().name();
216 // if ( request.getCharacterEncoding() != null ) {
217 // encoding = request.getCharacterEncoding();
218 // }
219 // String dec = URLDecoder.decode( value, encoding );
220 // keyedValues.put( key, dec );
221 // } catch ( Exception e ) {
222 // LOG.logError( e.getLocalizedMessage() );
223 // }
224 // }
225
226 String format = request.getParameter( "format" );
227 String style = null;
228 ChartConfig chartConfigs = null;
229 if ( ( style = request.getParameter( "style" ) ) != null ) {
230 try {
231 chartConfigs = new ChartConfig( new URL( style ) );
232 } catch ( Exception e ) {
233 // Its not a big problem that the chartConfig is null. In this case the default chart config will be
234 // used
235 LOG.logError( Messages.getMessage( "GRA_CHART_INVALID_URL", style ) );
236 }
237 }
238 try {
239 BufferedImage img = buildChart( request.getParameter( "chart" ), request.getParameter( "title" ),
240 request.getParameter( "legend" ), request.getParameter( "width" ),
241 request.getParameter( "height" ), request.getParameter( "xAxis" ),
242 request.getParameter( "yAxis" ), request.getParameter( "tooltips" ),
243 request.getParameter( "orientation" ), request.getParameter( "lblType" ),
244 request.getParameter( "format" ), keyedValues, chartConfigs );
245 String imgType = null;
246 if ( MimeTypeMapper.isKnownImageType( format ) ) {
247 int index = format.indexOf( "/" );
248 imgType = format.substring( index + 1, format.length() );
249 } else {
250 throw new IncorrectFormatException( "The given image type is not a supported type" );
251 }
252 try {
253 ImageUtils.saveImage( img, response.getOutputStream(), imgType, 1.0f );
254 } catch ( Exception e ) {
255 // Nothing. An exception happened here, because the GET request is called multiple times.
256 // This exception happens on the second and third time.
257 }
258 } catch ( Exception e ) {
259 LOG.logError( e.getLocalizedMessage() );
260 goToErrorPage( response );
261 }
262 }
263
264 /**
265 *
266 * @param request
267 * @return instance of the queuedMap
268 * @throws IncorrectFormatException
269 * @deprecated due to a typo this method is deprecated. Use {@link #initAndValidateParams(HttpServletRequest)}
270 * instead.
271 *
272 */
273 @Deprecated
274 protected QueuedMap<String, String> InitNValidateParams( HttpServletRequest request )
275 throws IncorrectFormatException {
276 return initAndValidateParams( request );
277 }
278
279 /**
280 * Init and validate params does what its name say. It extracts the mandatory parameters from the HttpServletRequest
281 * class and checks their validity. Then it fills a queued map with all parameters, that are not defined, because
282 * these are the candidates to be the parameters for the chart
283 *
284 * @param request
285 * @return instance of the queued map with
286 * @throws IncorrectFormatException
287 *
288 */
289 @SuppressWarnings("unchecked")
290 protected QueuedMap<String, String> initAndValidateParams( HttpServletRequest request )
291 throws IncorrectFormatException {
292
293 QueuedMap<String, String> keyedValues = new QueuedMap<String, String>();
294
295 Map<String, String> param = KVP2Map.toMap( request );
296
297 String chart = param.get( "CHART" );
298 String width = param.get( "WIDTH" );
299 String height = param.get( "HEIGHT" );
300 String format = param.get( "FORMAT" );
301
302 if ( chart == null ) {
303 throw new IncorrectFormatException( Messages.getMessage( "GRA_CHART_ERROR_NULL_PARAM", "chart" ) );
304 }
305
306 if ( width == null ) {
307 throw new IncorrectFormatException( Messages.getMessage( "GRA_CHART_ERROR_NULL_PARAM", "width" ) );
308 }
309
310 if ( height == null ) {
311 throw new IncorrectFormatException( Messages.getMessage( "GRA_CHART_ERROR_NULL_PARAM", "height" ) );
312 }
313
314 if ( format == null ) {
315 throw new IncorrectFormatException( Messages.getMessage( "GRA_CHART_ERROR_NULL_PARAM", "format" ) );
316 } else if ( !MimeTypeMapper.isKnownImageType( format ) ) {
317 throw new IncorrectFormatException( Messages.getMessage( "GRA_CHART_ERROR_UNSUPPORTED_TYPE" ) );
318 }
319
320
321 Iterator<String> it = request.getParameterMap().keySet().iterator();
322 while ( it.hasNext() ) {
323 String key = it.next();
324 if ( !key.equals( "chart" ) && !key.equals( "title" ) && !key.equals( "legend" ) && !key.equals( "width" )
325 && !key.equals( "height" ) && !key.equals( "xAxis" ) && !key.equals( "yAxis" )
326 && !key.equals( "tooltips" ) && !key.equals( "orientation" ) && !key.equals( "format" )
327 && !key.equals( "style" ) ) {
328 keyedValues.put( key, request.getParameter( key ) );
329 }
330 }
331 return keyedValues;
332 }
333
334 /**
335 * Creates a buffered image that contains an error label. It is used to display an error instead of a chart in case
336 * an error happened
337 *
338 * @param response
339 */
340 protected void goToErrorPage( HttpServletResponse response ) {
341 if ( errorMsgPath == null ) {
342 LOG.logError( Messages.getMessage( "GRA_CHART_MISSING_ERROR_IMAGE", new Object[] {} ) );
343 return;
344 }
345 try {
346 BufferedImage img = ImageUtils.loadImage( errorMsgPath );
347 ImageUtils.saveImage( img, response.getOutputStream(), "gif", 1.0f );
348
349 } catch ( Exception e ) {
350 LOG.logError( e.getLocalizedMessage() );
351 }
352 }
353
354 /**
355 * Takes in all the needed parameters and a queued map containing the values of the chart and returns a
356 * BufferedImage with the chart
357 *
358 * @param chart
359 * @param title
360 * @param legend
361 * @param width
362 * @param height
363 * @param xAxis
364 * @param yAxis
365 * @param tooltips
366 * @param orientation
367 * @param lblType
368 * @param imagetype
369 * @param params
370 * @param chartConfigs
371 * to configure the output chart, or null to use the default ChartConfig
372 * @return BufferedImage containing the chart
373 * @throws IncorrectFormatException
374 */
375 protected BufferedImage buildChart( String chart, String title, String legend, String width, String height,
376 String xAxis, String yAxis, String tooltips, String orientation,
377 String lblType, String imagetype, Map<String, String> params,
378 ChartConfig chartConfigs )
379 throws IncorrectFormatException {
380 if ( configs == null ) {
381 throw new IncorrectFormatException( Messages.getMessage( "GRA_CHART_ERROR_SERVLET_BROKEN", new Object[]{} ) );
382 }
383
384 ChartsBuilder builder = null;
385 try {
386 builder = new ChartsBuilder( configs );
387 } catch ( Exception e ) {
388 throw new IncorrectFormatException( e.getLocalizedMessage() );
389 }
390 boolean legend2 = "true".equals( legend );
391 int width2 = Integer.parseInt( width );
392 int height2 = Integer.parseInt( height );
393 boolean tooltips2 = "true".equals( tooltips );
394 int orientation2 = "horizontal".equals( orientation ) ? ChartsBuilder.ORIENTATION_HORIZONTAL
395 : ChartsBuilder.ORIENTATION_VERTICAL;
396 QueuedMap<String, String> map = new QueuedMap<String, String>();
397 map.putAll( params );
398
399 final String THREE_D = "3D";
400 /*
401 * * Format 1: &CDU=34&SPD=36&Gruene=11 - In this format the key has only one value - This type can only be used
402 * together with the chart type: Pie, Pie3D, Bar, Line and Line3D
403 *
404 * Format 2: &CDU=23,25,21,26&SPD=42,23,33,36 - In this format multiple values per keys can be given - Either
405 * comma or white spaces can be used as separators - This type can only be used together with the chart type:
406 * Bar, Line and Line3D
407 *
408 * Format 3: &CDU=1970 23;1974,44;1978,43&SPD=1970,23;1974,44;1978,43 - In this format the key has multiple
409 * value, in which each value is a tuple that contains in turn two tokens representing x and y of a cartesian
410 * point. This format is used to create cartesian de.latlon.charts - comma and white spacescan be used as
411 * separators between tokens, i.e. between x and y, while semi colon shall be used as a separator between
412 * tupels. - This type can only be used together with XYBar and XYBar
413 */
414 if ( chart.equals( "Pie" ) || chart.equals( "Pie3D" ) ) {
415 QueuedMap<String, Double> map2 = new QueuedMap<String, Double>();
416 Iterator<String> it = map.keySet().iterator();
417 while ( it.hasNext() ) {
418 String key = it.next();
419 String value = map.get( key );
420 try {
421 if ( value != null ) {
422 map2.put( key, Double.parseDouble( value ) );
423 }
424 } catch ( Exception e ) {
425 // It's not a problem. There might be parameters, that are not values.
426 }
427 }
428 return builder.createPieChart( title, map2, width2, height2, chart.contains( THREE_D ), legend2, tooltips2,
429 lblType, imagetype, chartConfigs );
430 } else if ( chart.equals( "Line" ) || chart.equals( "Line3D" ) ) {
431 return builder.createLineChart( title, map, width2, height2, chart.contains( THREE_D ), legend2, tooltips2,
432 orientation2, imagetype, xAxis, yAxis, chartConfigs );
433 } else if ( chart.equals( "Bar" ) || chart.equals( "Bar3D" ) ) {
434 return builder.createBarChart( title, map, width2, height2, chart.contains( THREE_D ), legend2, tooltips2,
435 orientation2, imagetype, xAxis, yAxis, chartConfigs );
436 } else if ( chart.equals( "XYLine" ) ) {
437 return builder.createXYLineChart( title, map, width2, height2, legend2, tooltips2, orientation2, imagetype,
438 xAxis, yAxis, chartConfigs );
439 } else {
440 throw new IncorrectFormatException( Messages.getMessage( "GRA_CHART_UNSUPPORTED_TYPE", chart ) );
441 }
442 }
443 }