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