001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/portal/common/control/AbstractSimplePrintListener.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 package org.deegree.portal.common.control; 037 038 import java.awt.Color; 039 import java.awt.Graphics; 040 import java.awt.Image; 041 import java.awt.image.BufferedImage; 042 import java.io.File; 043 import java.io.FileOutputStream; 044 import java.io.IOException; 045 import java.io.RandomAccessFile; 046 import java.net.URL; 047 import java.text.SimpleDateFormat; 048 import java.util.ArrayList; 049 import java.util.GregorianCalendar; 050 import java.util.HashMap; 051 import java.util.List; 052 import java.util.Locale; 053 import java.util.Map; 054 import java.util.UUID; 055 056 import javax.servlet.ServletContext; 057 import javax.servlet.http.HttpServletRequest; 058 059 import net.sf.jasperreports.engine.JRDataSource; 060 import net.sf.jasperreports.engine.JREmptyDataSource; 061 import net.sf.jasperreports.engine.JRException; 062 import net.sf.jasperreports.engine.JasperFillManager; 063 import net.sf.jasperreports.engine.JasperPrint; 064 import net.sf.jasperreports.engine.JasperPrintManager; 065 import net.sf.jasperreports.engine.JasperRunManager; 066 067 import org.deegree.enterprise.control.AbstractListener; 068 import org.deegree.enterprise.control.FormEvent; 069 import org.deegree.enterprise.control.RPCMember; 070 import org.deegree.enterprise.control.RPCStruct; 071 import org.deegree.enterprise.control.RPCWebEvent; 072 import org.deegree.framework.log.ILogger; 073 import org.deegree.framework.log.LoggerFactory; 074 import org.deegree.framework.util.CharsetUtils; 075 import org.deegree.framework.util.ImageUtils; 076 import org.deegree.framework.util.KVP2Map; 077 import org.deegree.framework.util.StringTools; 078 import org.deegree.framework.xml.NamespaceContext; 079 import org.deegree.framework.xml.XMLFragment; 080 import org.deegree.framework.xml.XMLParsingException; 081 import org.deegree.framework.xml.XMLTools; 082 import org.deegree.model.spatialschema.Point; 083 import org.deegree.ogcbase.CommonNamespaces; 084 import org.deegree.ogcwebservices.InconsistentRequestException; 085 import org.deegree.ogcwebservices.wms.operation.GetMap; 086 import org.deegree.portal.PortalException; 087 import org.deegree.portal.PortalUtils; 088 import org.deegree.portal.context.Layer; 089 import org.deegree.portal.context.Style; 090 import org.deegree.portal.context.ViewContext; 091 import org.deegree.security.drm.model.User; 092 import org.xml.sax.SAXException; 093 094 /** 095 * performs a print request/event by creating a PDF document from the current map. For this JASPER is used. Well known 096 * parameters that can be passed to a jaser report are:<br> 097 * <ul> 098 * <li>MAP</li> 099 * <li>LEGEND</li> 100 * <li>DATE</li> 101 * <li>MAPSCALE</li> 102 * </ul> 103 * <br> 104 * Additionaly parameters named 'TA:XXXX' can be used. deegree will create a k-v-p for each TA:XXXX passed as part of 105 * RPC. 106 * 107 * 108 * @version $Revision: 18283 $ 109 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 110 * @author last edited by: $Author: apoth $ 111 * 112 * @version $Revision: 18283 $, $Date: 2009-06-30 09:23:23 +0200 (Di, 30. Jun 2009) $ 113 */ 114 public abstract class AbstractSimplePrintListener extends AbstractListener { 115 116 private static ILogger LOG = LoggerFactory.getLogger( AbstractSimplePrintListener.class ); 117 118 private String defaultTemplateDir = "/WEB-INF/igeoportal/print"; 119 120 /** 121 * @param e 122 */ 123 @Override 124 public void actionPerformed( FormEvent e ) { 125 RPCWebEvent rpc = (RPCWebEvent) e; 126 try { 127 validate( rpc ); 128 } catch ( Exception ex ) { 129 LOG.logError( ex.getMessage(), ex ); 130 gotoErrorPage( ex.getMessage() ); 131 } 132 133 ViewContext vc = getViewContext( rpc ); 134 if ( vc == null ) { 135 LOG.logError( "no valid ViewContext available; maybe your session has reached timeout limit" ); //$NON-NLS-1$ 136 gotoErrorPage( Messages.getString( "AbstractSimplePrintListener.MISSINGCONTEXT" ) ); 137 setNextPage( "igeoportal/error.jsp" ); 138 return; 139 } 140 try { 141 printMap( vc, rpc ); 142 } catch ( Exception ex ) { 143 ex.printStackTrace(); 144 LOG.logError( ex.getMessage(), ex ); 145 gotoErrorPage( ex.getMessage() ); 146 } 147 } 148 149 /** 150 * 151 * @param vc 152 * @param rpc 153 * @throws PortalException 154 * @throws IOException 155 * @throws SAXException 156 * @throws XMLParsingException 157 * @throws InconsistentRequestException 158 */ 159 private void printMap( ViewContext vc, RPCWebEvent rpc ) 160 throws PortalException, IOException, InconsistentRequestException, XMLParsingException, 161 SAXException { 162 163 List<String> getMap = createGetMapRequests( vc, rpc ); 164 String image = performGetMapRequests( getMap ); 165 166 String legend = accessLegend( createLegendURLs( vc ) ); 167 168 String format = (String) rpc.getRPCMethodCall().getParameters()[0].getValue(); 169 170 RPCStruct struct = (RPCStruct) rpc.getRPCMethodCall().getParameters()[1].getValue(); 171 String printTemplate = (String) struct.getMember( "TEMPLATE" ).getValue(); 172 ServletContext sc = ( (HttpServletRequest) getRequest() ).getSession( true ).getServletContext(); 173 174 // read print template directory from defaultTemplateDir, or, if available, from the init 175 // parameters 176 String templateDir = getInitParameter( "TEMPLATE_DIR" ); 177 if ( templateDir == null ) { 178 templateDir = defaultTemplateDir; 179 } 180 181 String path = sc.getRealPath( templateDir ) + '/' + printTemplate + ".jasper"; 182 String pathx = sc.getRealPath( templateDir ) + '/' + printTemplate + ".jrxml"; 183 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 184 LOG.logDebug( "The jasper template is read from: ", path ); 185 LOG.logDebug( "The jrxml template is read from: ", pathx ); 186 } 187 188 Map<String, Object> parameter = new HashMap<String, Object>(); 189 parameter.put( "MAP", image ); 190 parameter.put( "LEGEND", legend ); 191 192 // enable 193 if ( getInitParameter( "LOGO_URL" ) != null ){ 194 String logoUrl = getHomePath() + getInitParameter( "LOGO_URL" ); 195 if ( new File( logoUrl ).exists() ) { 196 parameter.put( "LOGO_URL", logoUrl ); 197 } 198 } 199 200 parameter.put( "ROOT",getHomePath() ); 201 202 SimpleDateFormat sdf = new SimpleDateFormat( "dd.MM.yyyy", Locale.getDefault() ); 203 // TODO deprecated - will be remove in future versions 204 parameter.put( "DATUM", sdf.format( new GregorianCalendar().getTime() ) ); 205 // -------------------------------------------------------- 206 parameter.put( "DATE", sdf.format( new GregorianCalendar().getTime() ) ); 207 double scale = calcScale( pathx, getMap.get( 0 ) ); 208 parameter.put( "MAPSCALE", "" + Math.round( scale ) ); 209 LOG.logDebug( "print map scale: ", scale ); 210 // set text area values 211 RPCMember[] members = struct.getMembers(); 212 for ( int i = 0; i < members.length; i++ ) { 213 if ( members[i].getName().startsWith( "TA:" ) ) { 214 String s = members[i].getName().substring( 3, members[i].getName().length() ); 215 String val = (String) members[i].getValue(); 216 if ( val != null ) { 217 val = new String( val.getBytes(), CharsetUtils.getSystemCharset() ); 218 } 219 LOG.logDebug( "text area name: ", s ); 220 LOG.logDebug( "text area value: ", val ); 221 parameter.put( s, val ); 222 } 223 } 224 System.out.println(parameter); 225 if ( "application/pdf".equals( format ) ) { 226 227 // create the pdf 228 Object result = null; 229 try { 230 JRDataSource ds = new JREmptyDataSource(); 231 result = JasperRunManager.runReportToPdf( path, parameter, ds ); 232 } catch ( JRException e ) { 233 LOG.logError( "Template: " + path ); 234 LOG.logError( e.getLocalizedMessage(), e ); 235 throw new PortalException( Messages.getString( "AbstractSimplePrintListener.REPORTCREATION" ) ); 236 } finally { 237 File file = new File( image ); 238 file.delete(); 239 file = new File( legend ); 240 file.delete(); 241 } 242 243 forwardPDF( result ); 244 245 } else if ( "image/png".equals( format ) ) { 246 247 // create the image 248 Image result = null; 249 try { 250 JRDataSource ds = new JREmptyDataSource(); 251 JasperPrint prt = JasperFillManager.fillReport( path, parameter, ds ); 252 result = JasperPrintManager.printPageToImage( prt, 0, 1 ); 253 } catch ( JRException e ) { 254 LOG.logError( e.getLocalizedMessage(), e ); 255 throw new PortalException( Messages.getString( "AbstractSimplePrintListener.REPORTCREATION" ) ); 256 } finally { 257 File file = new File( image ); 258 file.delete(); 259 file = new File( legend ); 260 file.delete(); 261 } 262 263 forwardImage( result, format ); 264 265 } 266 } 267 268 protected double calcScale( String path, String getmap ) 269 throws InconsistentRequestException, XMLParsingException, IOException, SAXException { 270 //TODO The map path is static. It should be instead read from somewhere else. 271 //A good idea would be to save the path in the web.xml of the coreesponding application, 272 //or in controller.xml of the PdfPrintListener and sends it with rpc request 273 274 Map<String, String> model = KVP2Map.toMap( getmap ); 275 model.put( "ID", "22" ); 276 GetMap gm = GetMap.create( model ); 277 278 File file = new File( path ); 279 XMLFragment xml = new XMLFragment( file.toURL() ); 280 281 String xpathW = "detail/band/image/reportElement[./@key = 'image-1']/@width"; 282 283 NamespaceContext nsc = CommonNamespaces.getNamespaceContext(); 284 int w = XMLTools.getRequiredNodeAsInt( xml.getRootElement(), xpathW, nsc ); 285 286 // CoordinateSystem crs = CRSFactory.create( gm.getSrs() ); 287 288 // mapsize in template in metre; templates generated by iReport are designed 289 // to assume a resolution of 72dpi 290 double ms = ( w / 72d ) * 0.0254; 291 // TODO 292 // consider no metric CRS 293 return gm.getBoundingBox().getWidth() / ms; 294 } 295 296 /** 297 * accesses the legend URLs passed, draws the result onto an image that are stored in a temporary file. The name of 298 * the file will be returned. 299 * 300 * @param legends 301 * @return filename of image file 302 */ 303 private String accessLegend( List<String[]> legends ) 304 throws IOException { 305 int width = Integer.parseInt( getInitParameter( "LEGENDWIDTH" ) ); 306 int height = Integer.parseInt( getInitParameter( "LEGENDHEIGHT" ) ); 307 String tmp = getInitParameter( "LEGENDBGCOLOR" ); 308 if ( tmp == null ) { 309 tmp = "0xFFFFFF"; 310 } 311 Color bg = Color.decode( tmp ); 312 BufferedImage bi = new BufferedImage( width, height, BufferedImage.TYPE_INT_ARGB ); 313 Graphics g = bi.getGraphics(); 314 g.setColor( bg ); 315 g.fillRect( 0, 0, bi.getWidth(), bi.getHeight() ); 316 g.setColor( Color.BLACK ); 317 int k = 10; 318 319 for ( int i = 0; i < legends.size(); i++ ) { 320 if ( k > bi.getHeight() ) { 321 if ( LOG.getLevel() <= ILogger.LOG_WARNING ) { 322 LOG.logWarning( "The necessary legend size is larger than the available legend space." ); 323 } 324 } 325 String[] s = legends.get( i ); 326 if ( s[1] != null ) { 327 LOG.logDebug( "reading legend: " + s[1] ); 328 Image img = null; 329 try { 330 img = ImageUtils.loadImage( new URL( s[1] ) ); 331 } catch ( Exception e ) { 332 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 333 String msg = StringTools.concat( 400, "Exception for Layer: ", s[0], " - ", s[1] ); 334 LOG.logDebug( msg ); 335 LOG.logDebug( e.getLocalizedMessage() ); 336 } 337 if ( getInitParameter( "MISSING_IMAGE" ) != null ){ 338 String missingImageUrl = getHomePath() + getInitParameter( "MISSING_IMAGE" ); 339 File missingImage = new File( missingImageUrl ); 340 if ( missingImage.exists() ) { 341 img = ImageUtils.loadImage( missingImage ); 342 } 343 } 344 } 345 if ( img != null ) { 346 if ( img.getWidth( null ) < 50 ) { 347 // it is assumed that no label is assigned 348 g.drawImage( img, 0, k, null ); 349 g.drawString( s[0], img.getWidth( null ) + 10, k + img.getHeight( null ) / 2 ); 350 } else { 351 g.drawImage( img, 0, k, null ); 352 } 353 k = k + img.getHeight( null ) + 10; 354 } 355 } else { 356 g.drawString( "- " + s[0], 0, k + 10 ); 357 k = k + 20; 358 } 359 } 360 g.dispose(); 361 362 return storeImage( bi ); 363 } 364 365 /** 366 * performs the GetMap requests passed, draws the result onto an image that are stored in a temporary file. The name 367 * of the file will be returned. 368 * 369 * @param list 370 * @return filename of image file 371 * @throws PortalException 372 * @throws IOException 373 */ 374 private String performGetMapRequests( List<String> list ) 375 throws PortalException, IOException { 376 377 Map<String, String> map = KVP2Map.toMap( list.get( 0 ) ); 378 map.put( "ID", "ww" ); 379 GetMap getMap = null; 380 try { 381 getMap = GetMap.create( map ); 382 } catch ( Exception e ) { 383 LOG.logError( e.getMessage(), e ); 384 String s = Messages.format( "AbstractSimplePrintListener.GETMAPCREATION", list.get( 0 ) ); 385 throw new PortalException( s ); 386 } 387 BufferedImage bi = new BufferedImage( getMap.getWidth(), getMap.getHeight(), BufferedImage.TYPE_INT_ARGB ); 388 Graphics g = bi.getGraphics(); 389 for ( int i = 0; i < list.size(); i++ ) { 390 URL url = new URL( list.get( i ) ); 391 Image img = null; 392 try { 393 img = ImageUtils.loadImage( url ); 394 } catch ( Exception e ) { 395 //This is the case where a getmap request produces an error. This does not definitly mean that 396 //the wms is not working, it could also be because the bounding box is not correct or too big. Try to zoom 397 // in, you might find something 398 LOG.logInfo( "could not load map from: ", url.toExternalForm() ); 399 } 400 g.drawImage( img, 0, 0, null ); 401 } 402 g.dispose(); 403 return storeImage( bi ); 404 } 405 406 /** 407 * stores the passed image in the defined temporary directory and returns the dynamicly created filename 408 * 409 * @param bi 410 * @return filename of image file 411 * @throws IOException 412 */ 413 private String storeImage( BufferedImage bi ) 414 throws IOException { 415 416 String s = UUID.randomUUID().toString(); 417 String tempDir = getInitParameter( "TEMPDIR" ); 418 if ( !tempDir.endsWith( "/" ) ) { 419 tempDir = tempDir + '/'; 420 } 421 if ( tempDir.startsWith( "/" ) ) { 422 tempDir = tempDir.substring( 1, tempDir.length() ); 423 } 424 ServletContext sc = ( (HttpServletRequest) this.getRequest() ).getSession( true ).getServletContext(); 425 String fileName = StringTools.concat( 300, sc.getRealPath( tempDir ), '/', s, ".png" ); 426 427 FileOutputStream fos = new FileOutputStream( new File( fileName ) ); 428 429 ImageUtils.saveImage( bi, fos, "png", 1 ); 430 fos.close(); 431 432 return fileName; 433 } 434 435 private void forwardPDF( Object result ) 436 throws PortalException { 437 // must be a byte array 438 String tempDir = getInitParameter( "TEMPDIR" ); 439 if ( !tempDir.endsWith( "/" ) ) { 440 tempDir = tempDir + '/'; 441 } 442 if ( tempDir.startsWith( "/" ) ) { 443 tempDir = tempDir.substring( 1, tempDir.length() ); 444 } 445 ServletContext sc = ( (HttpServletRequest) this.getRequest() ).getSession( true ).getServletContext(); 446 447 String fileName = UUID.randomUUID().toString(); 448 String s = StringTools.concat( 200, sc.getRealPath( tempDir ), '/', fileName, ".pdf" ); 449 try { 450 RandomAccessFile raf = new RandomAccessFile( s, "rw" ); 451 raf.write( (byte[]) result ); 452 raf.close(); 453 } catch ( Exception e ) { 454 e.printStackTrace(); 455 LOG.logError( "could not write temporary pdf file: " + s, e ); 456 throw new PortalException( Messages.format( "AbstractSimplePrintListener.PDFCREATION", s ), e ); 457 } 458 459 getRequest().setAttribute( "PDF", StringTools.concat( 200, tempDir, fileName, ".pdf" ) ); 460 } 461 462 private void forwardImage( Image result, String format ) 463 throws PortalException { 464 465 format = format.substring( format.indexOf( '/' ) + 1 ); 466 467 String tempDir = getInitParameter( "TEMPDIR" ); 468 if ( !tempDir.endsWith( "/" ) ) { 469 tempDir = tempDir + '/'; 470 } 471 if ( tempDir.startsWith( "/" ) ) { 472 tempDir = tempDir.substring( 1, tempDir.length() ); 473 } 474 ServletContext sc = ( (HttpServletRequest) this.getRequest() ).getSession( true ).getServletContext(); 475 476 String fileName = UUID.randomUUID().toString(); 477 String s = StringTools.concat( 200, sc.getRealPath( tempDir ), "/", fileName, ".", format ); 478 try { 479 // make sure we have a BufferedImage 480 if ( !( result instanceof BufferedImage ) ) { 481 BufferedImage img = new BufferedImage( result.getWidth( null ), result.getHeight( null ), 482 BufferedImage.TYPE_INT_ARGB ); 483 484 Graphics g = img.getGraphics(); 485 g.drawImage( result, 0, 0, null ); 486 g.dispose(); 487 result = img; 488 } 489 490 ImageUtils.saveImage( (BufferedImage) result, s, 1 ); 491 } catch ( Exception e ) { 492 LOG.logError( "could not write temporary pdf file: " + s, e ); 493 throw new PortalException( Messages.format( "AbstractSimplePrintListener.PDFCREATION", s ), e ); 494 } 495 496 getRequest().setAttribute( "PDF", StringTools.concat( 200, tempDir, fileName, ".", format ) ); 497 } 498 499 /** 500 * fills the passed PrintMap request template with required values 501 * 502 * @param vc 503 * @return returns a list with all base requests 504 */ 505 private List<String> createGetMapRequests( ViewContext vc, RPCWebEvent rpc ) { 506 507 User user = getUser(); 508 String vsp = getVendorspecificParameters(rpc); 509 510 // set boundingbox/envelope 511 Point[] points = vc.getGeneral().getBoundingBox(); 512 513 int width = Integer.parseInt( getInitParameter( "WIDTH" ) ); 514 int height = Integer.parseInt( getInitParameter( "HEIGHT" ) ); 515 516 StringBuffer sb = new StringBuffer( 1000 ); 517 sb.append( "&BBOX=" ).append( points[0].getX() ).append( ',' ); 518 sb.append( points[0].getY() ).append( ',' ).append( points[1].getX() ); 519 sb.append( ',' ).append( points[1].getY() ).append( "&WIDTH=" ); 520 sb.append( width ).append( "&HEIGHT=" ).append( height ); 521 if ( user != null ) { 522 sb.append( "&user=" ).append( user.getName() ); 523 sb.append( "&password=" ).append( user.getPassword() ); 524 } 525 if ( vsp != null ) { 526 sb.append( "&" ).append( vsp ); 527 } 528 String[] reqs = PortalUtils.createBaseRequests( vc ); 529 List<String> list = new ArrayList<String>( reqs.length ); 530 for ( int i = 0; i < reqs.length; i++ ) { 531 list.add( reqs[i] + sb.toString() ); 532 System.out.println(reqs[i] + sb.toString()); 533 LOG.logDebug( "GetMap request:", reqs[i] + sb.toString() ); 534 } 535 536 return list; 537 } 538 539 /** 540 * returns <code>null</code> and should be overwritten by an extending class 541 * @return <code>null</code> 542 */ 543 protected String getVendorspecificParameters(RPCWebEvent rpc) { 544 // TODO Auto-generated method stub 545 return null; 546 } 547 548 /** 549 * returns <code>null</code> and should be overwritten by an extending class 550 * 551 * @return <code>null</code> 552 */ 553 protected User getUser() { 554 return null; 555 } 556 557 /** 558 * reads the view context to print from the users session 559 * 560 * @param rpc 561 * @return the viewcontext defined by the rpc 562 */ 563 abstract protected ViewContext getViewContext( RPCWebEvent rpc ); 564 565 /** 566 * returns legend access URLs for all visible layers of the passed view context. If a visible layer does not define 567 * a LegendURL 568 * 569 * @param vc 570 * @return legend access URLs for all visible layers of the passed view context. If a visible layer does not define 571 * a LegendURL 572 */ 573 private List<String[]> createLegendURLs( ViewContext vc ) { 574 Layer[] layers = vc.getLayerList().getLayers(); 575 List<String[]> list = new ArrayList<String[]>(); 576 for ( int i = 0; i < layers.length; i++ ) { 577 if ( !layers[i].isHidden() ) { 578 Style style = layers[i].getStyleList().getCurrentStyle(); 579 String[] s = new String[2]; 580 s[0] = layers[i].getTitle(); 581 if ( style.getLegendURL() != null ) { 582 s[1] = style.getLegendURL().getOnlineResource().toExternalForm(); 583 } 584 list.add( s ); 585 } 586 } 587 return list; 588 } 589 590 /** 591 * validates the incoming request/RPC if conatins all required elements 592 * 593 * @param rpc 594 * @throws PortalException 595 */ 596 protected void validate( RPCWebEvent rpc ) 597 throws PortalException { 598 RPCStruct struct = (RPCStruct) rpc.getRPCMethodCall().getParameters()[1].getValue(); 599 if ( struct.getMember( "TEMPLATE" ) == null ) { 600 throw new PortalException( Messages.getString( "portal.common.control.VALIDATIONERROR" ) ); 601 } 602 603 if ( rpc.getRPCMethodCall().getParameters()[0].getValue() == null ) { 604 throw new PortalException( Messages.getString( "portal.common.control.VALIDATIONERROR" ) ); 605 } 606 } 607 608 }