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 }