    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/portal/standard/wms/control/SimplePrintListener.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.portal.standard.wms.control;
    038
    039    import java.awt.Graphics;
    040    import java.awt.geom.Rectangle2D;
    041    import java.awt.image.BufferedImage;
    042    import java.io.File;
    043    import java.net.URL;
    044    import java.util.ArrayList;
    045
    046    import javax.servlet.http.HttpServletRequest;
    047    import javax.servlet.http.HttpSession;
    048
    049    import org.deegree.datatypes.QualifiedName;
    050    import org.deegree.enterprise.control.RPCMember;
    051    import org.deegree.enterprise.control.RPCMethodCall;
    052    import org.deegree.enterprise.control.RPCParameter;
    053    import org.deegree.enterprise.control.RPCStruct;
    054    import org.deegree.enterprise.control.RPCWebEvent;
    055    import org.deegree.framework.log.ILogger;
    056    import org.deegree.framework.log.LoggerFactory;
    057    import org.deegree.framework.util.ImageUtils;
    058    import org.deegree.framework.util.StringTools;
    059    import org.deegree.i18n.Messages;
    060    import org.deegree.model.crs.CoordinateSystem;
    061    import org.deegree.model.spatialschema.Envelope;
    062    import org.deegree.model.spatialschema.GeometryFactory;
    063    import org.deegree.model.spatialschema.Point;
    064    import org.deegree.ogcbase.ImageURL;
    065    import org.deegree.ogcwebservices.OWSUtils;
    066    import org.deegree.ogcwebservices.getcapabilities.OGCCapabilities;
    067    import org.deegree.ogcwebservices.wms.capabilities.LegendURL;
    068    import org.deegree.ogcwebservices.wms.capabilities.WMSCapabilities;
    069    import org.deegree.ogcwebservices.wms.capabilities.WMSCapabilitiesDocument;
    070    import org.deegree.ogcwebservices.wms.capabilities.WMSCapabilitiesDocumentFactory;
    071    import org.deegree.owscommon_new.HTTP;
    072    import org.deegree.owscommon_new.Operation;
    073    import org.deegree.portal.Constants;
    074    import org.deegree.portal.PortalException;
    075    import org.deegree.portal.common.control.AbstractSimplePrintListener;
    076    import org.deegree.portal.context.ContextException;
    077    import org.deegree.portal.context.Format;
    078    import org.deegree.portal.context.FormatList;
    079    import org.deegree.portal.context.General;
    080    import org.deegree.portal.context.Layer;
    081    import org.deegree.portal.context.LayerList;
    082    import org.deegree.portal.context.Server;
    083    import org.deegree.portal.context.Style;
    084    import org.deegree.portal.context.StyleList;
    085    import org.deegree.portal.context.ViewContext;
    086
    087    /**
    088     * This class prints the View context. It inherits from AbstractSimplePrintListner and implement the abstract method
    089     * getViewContext
    090     *
    091     * TODO The methods changeBBox(), changeLayerList(), extractBBox() are already implemented in AbstractContextListner.
    092     * The question now is wether to inherit from AbstractContextListner instead of AbstractListner?
    093     *
    094     * @author <a href="mailto:mays@lat-lon.de">Judit Mays</a>
    095     * @author last edited by: $Author: mschneider $
    096     *
    097     * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
    098     */
    099    public class SimplePrintListener extends AbstractSimplePrintListener {
    100
    101        private static final ILogger LOG = LoggerFactory.getLogger( SimplePrintListener.class );
    102
    103        private String missingImg = null;
    104
    105        @Override
    106        protected ViewContext getViewContext( RPCWebEvent rpc ) {
    107
    108            init();
    109            RPCMethodCall mc = rpc.getRPCMethodCall();
    110            RPCParameter[] params = mc.getParameters();
    111            RPCStruct struct = (RPCStruct) params[2].getValue();
    112
    113            HttpSession session = ( (HttpServletRequest) getRequest() ).getSession();
    114            ViewContext vc = (ViewContext) session.getAttribute( Constants.CURRENTMAPCONTEXT );
    115            // change values: BBOX and Layer List
    116            Envelope bbox = extractBBox( (RPCStruct) struct.getMember( "boundingBox" ).getValue(), null );
    117            RPCMember[] layerList = ( (RPCStruct) struct.getMember( "layerList" ).getValue() ).getMembers();
    118            try {
    119                changeBBox( vc, bbox );
    120                changeLayerList( vc, layerList );
    121            } catch ( PortalException e ) {
    122                LOG.logError( "An Error occured while trying to get the Viewcontext" );
    123                return null;
    124                // TODO This method implements its abstract method in AbstractSimplePrintListner
    125                // Preferably if the abstract header could be changed to throw a adequate excpetion
    126                // since this header is already implemented by another class
    127            }
    128            return vc;
    129        }
    130
    131        private void init() {
    132
    133            String tmp = getInitParameter( "MISSING_IMAGE" );
    134            if ( tmp != null ) {
    135                missingImg = StringTools.concat( 200, getHomePath(), tmp );
    136                LOG.logDebug( "MissingLegend PATH = ", missingImg );
    137            }
    138        }
    139
    140        /**
    141         * Convenience method to extract the boundig box from an rpc fragment.
    142         *
    143         * @param bboxStruct
    144         *            the <code>RPCStruct</code> containing the bounding box. For example,
    145         *            <code>&lt;member&gt;&lt;name&gt;boundingBox&lt;/name&gt;etc...</code>.
    146         * @param crs
    147         *            a coordinate system value, may be null.
    148         * @return an envelope with the boundaries defined in the rpc structure
    149         */
    150        protected Envelope extractBBox( RPCStruct bboxStruct, CoordinateSystem crs ) {
    151
    152            Double minx = (Double) bboxStruct.getMember( Constants.RPC_BBOXMINX ).getValue();
    153            Double miny = (Double) bboxStruct.getMember( Constants.RPC_BBOXMINY ).getValue();
    154            Double maxx = (Double) bboxStruct.getMember( Constants.RPC_BBOXMAXX ).getValue();
    155            Double maxy = (Double) bboxStruct.getMember( Constants.RPC_BBOXMAXY ).getValue();
    156
    157            Envelope bbox = GeometryFactory.createEnvelope( minx.doubleValue(), miny.doubleValue(), maxx.doubleValue(),
    158                                                            maxy.doubleValue(), crs );
    159            return bbox;
    160        }
    161
    162        /**
    163         * changes the layer list of the ViewContext vc according to the information contained in the rpcLayerList
    164         *
    165         * @param vc
    166         *            The original ViewContext where the changes will be applied to
    167         * @param rpcLayerList
    168         *            the current layerlist
    169         * @throws PortalException
    170         */
    171        protected void changeLayerList( ViewContext vc, RPCMember[] rpcLayerList )
    172                                throws PortalException {
    173            LayerList layerList = vc.getLayerList();
    174            ArrayList<Layer> nLayers = new ArrayList<Layer>( rpcLayerList.length );
    175
    176            // this is needed to keep layer order
    177            // order is correct in rpc call JavaScript, but get lost in translation...
    178            LOG.logDebug( "Layerlist length: " + rpcLayerList.length );
    179            for ( int i = 0; i < rpcLayerList.length; i++ ) {
    180                String[] v = StringTools.toArray( (String) rpcLayerList[i].getValue(), "|", false );
    181                String layerName = rpcLayerList[i].getName();
    182
    183                String title = layerName;
    184                if ( v.length > 5 ) {
    185                    // See if there is a title field and use it if exists
    186                    title = v[5];
    187                }
    188                boolean isQueryable = false;
    189                if ( v.length > 6 ) {
    190                    // see if there's a isQuerzable field and use it if exists
    191                    isQueryable = v[6].equalsIgnoreCase( "true" );
    192                }
    193
    194                boolean isVisible = Boolean.valueOf( v[0] ).booleanValue();
    195                LOG.logDebug( "Service url: " + v[4] );
    196                // server address must be set!! otherwise an outdated layer might be taken.
    197                Layer l = layerList.getLayer( layerName, v[4] );
    198                if ( l != null ) {
    199                    // needed to reconstruct new layer order
    200                    // otherwise layer order is still from original context
    201                    LOG.logDebug( StringTools.concat( 100, "Layer ", layerName, " is defined in WMC" ) );
    202                    l.setHidden( !isVisible );
    203                } else {
    204                    LOG.logDebug( StringTools.concat( 100, "Layer ", layerName, " is undefined by the WMC" ) );
    205                    if ( layerList.getLayers().length == 0 ) {
    206                        // FIXME is this Exception Correct?
    207                        throw new PortalException( Messages.getMessage( "IGEO_STD_CNTXT_ERROR_EMPTY_LAYERLIST" ) );
    208                    }
    209
    210                    Layer p = layerList.getLayers()[0];
    211                    // a new layer must be created because it is not prsent in the current context.
    212                    // This is the case if the client has loaded an additional WMS
    213                    String[] tmp = StringTools.toArray( v[2], " ", false );
    214                    try {
    215                        v[4] = OWSUtils.validateHTTPGetBaseURL( v[4] );
    216                        WMSCapabilitiesDocument doc = WMSCapabilitiesDocumentFactory.getWMSCapabilitiesDocument( new URL(
    217                                                                                                                          v[4]
    218                                                                                                                                                  + "request=GetCapabilities&service=WMS" ) );
    219                        LOG.logDebug( "Service base url: " + v[4] );
    220                        OGCCapabilities capa = doc.parseCapabilities();
    221
    222                        URL url = getLegendURL( capa, layerName, null );
    223                        LOG.logDebug( "Final obtained legend url: " + url.toExternalForm() );
    224                        ImageURL tmpImg = p.getStyleList().getCurrentStyle().getLegendURL();
    225                        LegendURL legendUrl = new LegendURL( tmpImg.getWidth(), tmpImg.getHeight(), tmpImg.getFormat(), url );
    226                        Style style = new Style( "default", "", null, legendUrl, true );
    227                        StyleList styleList = new StyleList( new org.deegree.portal.context.Style[] { style } );
    228                        // end of changes
    229                        Server server = new Server( v[3], tmp[1], tmp[0], new URL( v[4] ), capa );
    230
    231                        Format[] formats = new Format[] { new Format( "image/png", true ) };
    232                        FormatList fl = new FormatList( formats );
    233                        l = new Layer( server, layerName, title, "", p.getSrs(), null, null, fl, styleList, isQueryable,
    234                                       !isVisible, null );
    235                    } catch ( Exception e ) {
    236                        throw new PortalException( StringTools.stackTraceToString( e ) );
    237                    }
    238                }
    239                nLayers.add( l );
    240            }
    241            try {
    242                nLayers.trimToSize();
    243                Layer[] ls = new Layer[nLayers.size()];
    244                ls = nLayers.toArray( ls );
    245                vc.setLayerList( new LayerList( ls ) );
    246            } catch ( ContextException e ) {
    247                throw new PortalException( Messages.getMessage( "IGEO_STD_CNTXT_ERROR_SET_LAYERLIST",
    248                                                                StringTools.stackTraceToString( e.getStackTrace() ) ) );
    249            }
    250        }
    251
    252        /**
    253         * This method is called, when no legend image could be retreived from the information given in the WebMapContext
    254         * document. The layer was probably added during runtime dynamically.
    255         *
    256         * It is attempted to get the legend url from style info in the WMS Capabilities, first with the passed style info,
    257         * second for the default style, third for the only available style. (If the WMS capabilities holds more than one
    258         * style, but no style was passed with the layer info, then one cannot know "the right style". Therefore no style is
    259         * taken from WMS capabilities, in this case.)
    260         *
    261         * Then, it is attempted to get the legend image from a GetLegendGraphics request, if the WMS capabilities state,
    262         * that this request is supported by the WMS.
    263         *
    264         * If all this fails, the image is taken from init param "missingImage".
    265         *
    266         * @param capa
    267         * @param layerName
    268         * @param styleName
    269         * @return URL of the Legend
    270         * @throws Exception
    271         */
    272        private URL getLegendURL( OGCCapabilities capa, String layerName, String styleName )
    273                                throws Exception {
    274
    275            // changes added to enable fetching the legend graphic through multiple methods
    276            WMSCapabilities wmsCapa = (WMSCapabilities) capa;
    277            org.deegree.ogcwebservices.wms.capabilities.Layer ogcLayer = wmsCapa.getLayer( layerName );
    278            URL url = null;
    279            if ( styleName == null ) {
    280                styleName = "default";
    281            }
    282            org.deegree.ogcwebservices.wms.capabilities.Style ogcStyle = ogcLayer.getStyleResource( styleName );// stylename
    283
    284            // 1. get style for layer
    285            // first try with "default" and then with "" (empty string) as style name
    286            LOG.logDebug( "Trying first method" );
    287            if ( ogcStyle == null ) {
    288                LOG.logDebug( "Layer has no valid default style definition. Obtaining default style from OGCLayer" );
    289                ogcStyle = ogcLayer.getStyleResource( "" );
    290            }
    291
    292            // try to get another style for layer (only, if there is just one style definition)
    293            if ( ogcStyle == null ) {
    294                LOG.logDebug( "Layer has no valid default style definition." );
    295                org.deegree.ogcwebservices.wms.capabilities.Style[] styles = ogcLayer.getStyles();
    296                if ( styles.length == 1 ) {
    297                    // using the only available style definition as default style definition
    298                    LOG.logDebug( "Layer has only one style definition. Assuming this as default style." );
    299                    ogcStyle = styles[0];
    300                } else {
    301                    // more than one style definition available, but non is the default style.
    302                    // Therefore no style definition can be chosen. Nothing happens here.
    303                }
    304            }
    305
    306            // 2. try to get legend url from style definition
    307            if ( ogcStyle != null ) {
    308                LOG.logDebug( "Trying second method" );
    309                LegendURL[] legendUrls = ogcStyle.getLegendURL();
    310                if ( legendUrls != null && legendUrls.length > 0 ) {
    311                    // First field of the array contains a url to the legend
    312                    url = legendUrls[0].getOnlineResource();
    313                    LOG.logDebug( "Legend url from the ogcStyle is: " + url.toExternalForm() );
    314                }
    315            }
    316
    317            // 3. try to get legend url from getLegendGraphic request
    318            if ( ogcStyle == null || url == null ) {
    319                LOG.logDebug( "Trying third method" );
    320                // either layer does not have a style info at all
    321                // or layer has a style info, but this style does not contain a legend url
    322
    323                QualifiedName name = new QualifiedName( "GetLegendGraphic" );
    324                Operation op = wmsCapa.getOperationMetadata().getOperation( name );
    325                if ( op != null ) {
    326                    HTTP http = (HTTP) op.getDCP().get( 0 );
    327                    url = http.getGetOnlineResources().get( 0 );
    328                } else {
    329                    LOG.logDebug( "GetLegendGrpahic not served by the service." );
    330                }
    331                LOG.logDebug( "LegendURLs can not be extracted from ogcStyle." );
    332            }
    333            if ( url != null ) {
    334                try {
    335                    // If no exception is thrown here, then everythis is ok, otherwise we load the missing legend image
    336                    ImageUtils.loadImage( url );
    337                } catch ( Exception e ) {
    338                    // The path is invalid. Loading the missing image from controller.xml
    339                    LOG.logDebug( StringTools.concat( 100, "The url : " + url,
    340                                                      " could not be loaded. Loading missing image" ) );
    341                    url = createMissingLegend( ogcLayer.getTitle() );
    342                }
    343            }
    344            return url;
    345        }
    346
    347        /**
    348         * Tries to create the missing image from the given path in the controller.xml If this image is invalid we try to
    349         * create our own empty legend with/without layer title, depending on the configurations
    350         *
    351         * @param layerTitle
    352         * @return Missing legend URL
    353         */
    354        private URL createMissingLegend( String layerTitle ) {
    355
    356            URL url = null;
    357            try {
    358                // using default length. width and format
    359                BufferedImage img = createEmptyLegend( layerTitle );
    360                // create the folder print in webapp if we don't have it
    361                File printDir = new File( getHomePath() + "print" );
    362                if ( !printDir.exists() ) {
    363                    printDir.mkdir();
    364                }
    365                File imgFile = findavailableName( printDir, "missingLegend", ".png" );
    366                ImageUtils.saveImage( img, imgFile, 1.0f );
    367                url = new URL( "file:///" + imgFile.getAbsolutePath() );
    368
    369            } catch ( Exception e ) {
    370                LOG.logError( e.getLocalizedMessage() );
    371            }
    372            return url;
    373        }
    374
    375        /**
    376         * Creates an empty legend in case the path to the MISSING_IMAGE parameter in the controller is invalid
    377         *
    378         * @param layerTitle
    379         * @return Empty legend BufferedImage
    380         */
    381        private BufferedImage createEmptyLegend( String layerTitle ) {
    382
    383            BufferedImage legend = new BufferedImage( 1, 1, BufferedImage.TYPE_INT_ARGB );
    384            if ( missingImg != null ) {
    385                try {
    386                    legend = ImageUtils.loadImage( missingImg );
    387                    // if MISSING_IMAGE path is valid and useLayerTitle is false, just return the missing image
    388                    return legend;
    389                } catch ( Exception e ) {
    390                    // its ok, we already have a valid legend BufferedImage
    391                }
    392            }
    393
    394            // if useLayerTitle == true,add the title to the image
    395            LOG.logDebug( "Adding title to the missing image" );
    396            BufferedImage missingLegend = new BufferedImage( 80, 15, BufferedImage.TYPE_INT_RGB );
    397            Graphics g = missingLegend.getGraphics();
    398            Rectangle2D rect = g.getFontMetrics().getStringBounds( layerTitle, g );
    399
    400            missingLegend = new BufferedImage( rect.getBounds().width + 80, legend.getHeight() + 15,
    401                                               BufferedImage.TYPE_INT_ARGB );
    402            g.dispose();
    403            g = missingLegend.getGraphics();
    404            g.drawImage( legend, 0, 0, null );
    405            g.dispose();
    406            return missingLegend;
    407        }
    408
    409        /**
    410         * Finds the first non used name in the folder name that starts with the given prefix. Ex. if prefix is print, then
    411         * it looks if print1 does not exist to use, if not then print2, if not then print3 etc..
    412         *
    413         * @param folderName
    414         * @param prefix
    415         * @param suffix
    416         *            (extension)of the generated path, ex .png or .jpg
    417         * @return Available file name
    418         */
    419        private File findavailableName( File folderName, String prefix, String suffix ) {
    420            String filename = folderName.getAbsolutePath();
    421            if ( !filename.endsWith( "/" ) && !filename.endsWith( "\\" ) ) {
    422                filename += "/";
    423            }
    424            filename += prefix;
    425            int counter = 1;
    426            while ( true ) {
    427                File file = new File( StringTools.concat( 100, filename, counter++, suffix ) );
    428                if ( !file.exists() ) {
    429                    return file;
    430                }
    431            }
    432        }
    433
    434        /**
    435         * changes the bounding box of a given view context
    436         *
    437         * @param vc
    438         *            the view context to be changed
    439         * @param bbox
    440         *            the new bounding box
    441         * @throws PortalException
    442         */
    443        public static final void changeBBox( ViewContext vc, Envelope bbox )
    444                                throws PortalException {
    445            General gen = vc.getGeneral();
    446
    447            CoordinateSystem cs = gen.getBoundingBox()[0].getCoordinateSystem();
    448            Point[] p = new Point[] { GeometryFactory.createPoint( bbox.getMin(), cs ),
    449                                     GeometryFactory.createPoint( bbox.getMax(), cs ) };
    450            try {
    451                gen.setBoundingBox( p );
    452            } catch ( ContextException e ) {
    453                LOG.logError( e.getMessage(), e );
    454                throw new PortalException( Messages.getMessage( "IGEO_STD_CNTXT_ERROR_SET_BBOX" ) );
    455            }
    456        }
    457
    458    }