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><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 }