    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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    }