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    }