001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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 }