001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/enterprise/servlet/WMSHandler.java $ 002 /*---------------- FILE HEADER ------------------------------------------ 003 004 This file is part of deegree. 005 Copyright (C) 2001-2006 by: 006 EXSE, Department of Geography, University of Bonn 007 http://www.giub.uni-bonn.de/deegree/ 008 lat/lon GmbH 009 http://www.lat-lon.de 010 011 This library is free software; you can redistribute it and/or 012 modify it under the terms of the GNU Lesser General Public 013 License as published by the Free Software Foundation; either 014 version 2.1 of the License, or (at your option) any later version. 015 016 This library is distributed in the hope that it will be useful, 017 but WITHOUT ANY WARRANTY; without even the implied warranty of 018 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 019 Lesser General Public License for more details. 020 021 You should have received a copy of the GNU Lesser General Public 022 License along with this library; if not, write to the Free Software 023 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 024 025 Contact: 026 027 Andreas Poth 028 lat/lon GmbH 029 Aennchenstr. 19 030 53115 Bonn 031 Germany 032 E-Mail: poth@lat-lon.de 033 034 Prof. Dr. Klaus Greve 035 Department of Geography 036 University of Bonn 037 Meckenheimer Allee 166 038 53115 Bonn 039 Germany 040 E-Mail: greve@giub.uni-bonn.de 041 042 ---------------------------------------------------------------------------*/ 043 044 package org.deegree.enterprise.servlet; 045 046 import java.awt.Color; 047 import java.awt.Graphics; 048 import java.awt.image.BufferedImage; 049 import java.io.IOException; 050 import java.io.OutputStream; 051 import java.io.OutputStreamWriter; 052 import java.io.PrintWriter; 053 import java.io.StringReader; 054 import java.net.MalformedURLException; 055 import java.net.URISyntaxException; 056 import java.net.URL; 057 import java.util.List; 058 059 import javax.servlet.http.HttpServletResponse; 060 import javax.xml.transform.Source; 061 import javax.xml.transform.TransformerException; 062 import javax.xml.transform.stream.StreamResult; 063 import javax.xml.transform.stream.StreamSource; 064 065 import org.deegree.datatypes.QualifiedName; 066 import org.deegree.datatypes.values.TypedLiteral; 067 import org.deegree.enterprise.ServiceException; 068 import org.deegree.framework.log.ILogger; 069 import org.deegree.framework.log.LoggerFactory; 070 import org.deegree.framework.util.CharsetUtils; 071 import org.deegree.framework.util.ImageUtils; 072 import org.deegree.framework.util.MimeTypeMapper; 073 import org.deegree.framework.util.NetWorker; 074 import org.deegree.framework.util.StringTools; 075 import org.deegree.framework.xml.DOMPrinter; 076 import org.deegree.framework.xml.Marshallable; 077 import org.deegree.framework.xml.XMLFragment; 078 import org.deegree.framework.xml.XSLTDocument; 079 import org.deegree.ogcwebservices.ExceptionReport; 080 import org.deegree.ogcwebservices.OGCWebService; 081 import org.deegree.ogcwebservices.OGCWebServiceException; 082 import org.deegree.ogcwebservices.OGCWebServiceRequest; 083 import org.deegree.ogcwebservices.OGCWebServiceResponse; 084 import org.deegree.ogcwebservices.wms.InvalidFormatException; 085 import org.deegree.ogcwebservices.wms.WMService; 086 import org.deegree.ogcwebservices.wms.WMServiceFactory; 087 import org.deegree.ogcwebservices.wms.capabilities.WMSCapabilities; 088 import org.deegree.ogcwebservices.wms.capabilities.WMSCapabilities_1_3_0; 089 import org.deegree.ogcwebservices.wms.configuration.WMSConfigurationType; 090 import org.deegree.ogcwebservices.wms.configuration.WMSDeegreeParams; 091 import org.deegree.ogcwebservices.wms.operation.DescribeLayerResult; 092 import org.deegree.ogcwebservices.wms.operation.GetFeatureInfo; 093 import org.deegree.ogcwebservices.wms.operation.GetFeatureInfoResult; 094 import org.deegree.ogcwebservices.wms.operation.GetLegendGraphic; 095 import org.deegree.ogcwebservices.wms.operation.GetLegendGraphicResult; 096 import org.deegree.ogcwebservices.wms.operation.GetMap; 097 import org.deegree.ogcwebservices.wms.operation.GetMapResult; 098 import org.deegree.ogcwebservices.wms.operation.GetStylesResult; 099 import org.deegree.ogcwebservices.wms.operation.PutStylesResult; 100 import org.deegree.ogcwebservices.wms.operation.WMSGetCapabilitiesResult; 101 import org.deegree.owscommon.XMLFactory; 102 import org.deegree.owscommon_new.DomainType; 103 import org.deegree.owscommon_new.Operation; 104 import org.deegree.owscommon_new.OperationsMetadata; 105 import org.w3c.dom.Node; 106 import org.xml.sax.SAXException; 107 108 /** 109 * <code>WMSHandler</code> is the handler class for WMS requests and their results. 110 * 111 * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a> 112 * @author last edited by: $Author: aschmitz $ 113 * 114 * @version 2.0, $Revision: 7973 $, $Date: 2007-08-10 09:24:54 +0200 (Fr, 10 Aug 2007) $ 115 * 116 * @since 2.0 117 */ 118 public class WMSHandler extends AbstractOWServiceHandler { 119 120 private static ILogger LOG = LoggerFactory.getLogger( WMSHandler.class ); 121 122 private Color bgColor = Color.WHITE; 123 124 private HttpServletResponse resp = null; 125 126 private OGCWebServiceRequest request = null; 127 128 private String exceptionFormat; 129 130 private String format = null; 131 132 private boolean transparent = false; 133 134 private int height = 400; 135 136 private int width = 600; 137 138 private WMSConfigurationType configuration = null; 139 140 /** 141 * 142 */ 143 WMSHandler() { 144 LOG.logDebug( "New WMSHandler instance created: " + this.getClass().getName() ); 145 } 146 147 /** 148 * performs the passed OGCWebServiceRequest by accessing service from the pool and passing the 149 * request to it 150 */ 151 public void perform( OGCWebServiceRequest request, HttpServletResponse response ) 152 throws ServiceException { 153 this.request = request; 154 resp = response; 155 try { 156 157 OGCWebService service = WMServiceFactory.getService(); 158 configuration = (WMSConfigurationType) ( (WMService) service ).getCapabilities(); 159 160 // EXCEPTION HANDLING NOTES: 161 // currently, the exceptions are handled differently for each request type, 162 // change the behaviour here 163 if ( request instanceof GetMap ) { 164 GetMap req = (GetMap) request; 165 exceptionFormat = req.getExceptions(); 166 format = req.getFormat(); 167 bgColor = req.getBGColor(); 168 transparent = req.getTransparency(); 169 height = req.getHeight(); 170 width = req.getWidth(); 171 } 172 173 if ( request instanceof GetLegendGraphic ) { 174 GetLegendGraphic req = (GetLegendGraphic) request; 175 exceptionFormat = req.getExceptions(); 176 format = req.getFormat(); 177 height = req.getHeight(); 178 width = req.getWidth(); 179 } 180 181 if ( request instanceof GetFeatureInfo ) { 182 GetFeatureInfo req = (GetFeatureInfo) request; 183 exceptionFormat = req.getExceptions(); 184 } 185 186 if ( exceptionFormat == null || exceptionFormat.equals( "" ) ) { 187 if ( "1.1.1".equals( request.getVersion() ) ) { 188 exceptionFormat = "application/vnd.ogc.se_xml"; 189 } else { 190 exceptionFormat = "XML"; 191 } 192 } 193 194 // fixup the exception formats, 1.3.0 has it different 195 if ( "INIMAGE".equalsIgnoreCase( exceptionFormat ) ) { 196 exceptionFormat = "application/vnd.ogc.se_inimage"; 197 } 198 if ( "BLANK".equalsIgnoreCase( exceptionFormat ) ) { 199 exceptionFormat = "application/vnd.ogc.se_blank"; 200 } 201 202 if ( service == null ) { 203 writeServiceExceptionReport( new OGCWebServiceException( "WMS", "could not access a WMService instance" ) ); 204 return; 205 } 206 207 // first, try the normal case 208 Object o = service.doService( request ); 209 handleResponse( o ); 210 211 } catch ( OGCWebServiceException e ) { 212 writeServiceExceptionReport( e ); 213 } 214 } 215 216 /** 217 * 218 * 219 * @param result 220 * @throws OGCWebServiceException 221 */ 222 private void handleResponse( Object result ) { 223 // this method may need restructuring 224 225 // handle exception case 226 if ( result instanceof OGCWebServiceException ) { 227 writeServiceExceptionReport( (OGCWebServiceException) result ); 228 return; 229 } 230 231 try { 232 OGCWebServiceResponse response = (OGCWebServiceResponse) result; 233 234 if ( response.getException() != null ) { 235 // handle the case that an exception occured during the 236 // request performance 237 writeServiceExceptionReport( response.getException() ); 238 } else { 239 if ( response instanceof OGCWebServiceException ) { 240 writeServiceExceptionReport( (OGCWebServiceException) response ); 241 } else if ( response instanceof Exception ) { 242 sendException( resp, (Exception) response ); 243 } else if ( response instanceof WMSGetCapabilitiesResult ) { 244 handleGetCapabilitiesResponse( (WMSGetCapabilitiesResult) response ); 245 } else if ( response instanceof GetMapResult ) { 246 handleGetMapResponse( (GetMapResult) response ); 247 } else if ( response instanceof GetFeatureInfoResult ) { 248 handleFeatureInfoResponse( (GetFeatureInfoResult) response ); 249 } else if ( response instanceof GetStylesResult ) { 250 handleGetStylesResponse( (GetStylesResult) response ); 251 } else if ( response instanceof PutStylesResult ) { 252 handlePutStylesResponse( (PutStylesResult) response ); 253 } else if ( response instanceof DescribeLayerResult ) { 254 handleDescribeLayerResponse( (DescribeLayerResult) response ); 255 } else if ( response instanceof GetLegendGraphicResult ) { 256 handleGetLegendGraphicResponse( (GetLegendGraphicResult) response ); 257 } 258 } 259 } catch ( InvalidFormatException ife ) { 260 LOG.logError( ife.getMessage(), ife ); 261 writeServiceExceptionReport( ife ); 262 } catch ( Exception e ) { 263 LOG.logError( e.getMessage(), e ); 264 writeServiceExceptionReport( new OGCWebServiceException( "WMS:write", e.getLocalizedMessage() ) ); 265 } 266 } 267 268 /** 269 * handles the response to a get capabilities request 270 * 271 * @param response 272 * @throws IOException 273 * @throws TransformerException 274 */ 275 private void handleGetCapabilitiesResponse( WMSGetCapabilitiesResult response ) 276 throws IOException, TransformerException { 277 WMSConfigurationType capa = response.getCapabilities(); 278 279 WMSDeegreeParams params = capa.getDeegreeParams(); 280 281 // version war follows 282 283 boolean version130 = "1.3.0".equals( capa.calculateVersion( request.getVersion() ) ); 284 285 // version not set -> use highest supported version 286 // use request's version otherwise 287 288 boolean support111 = false; 289 boolean support130 = false; 290 for ( String version : params.getSupportedVersions() ) { 291 if ( "1.1.1".equals( version ) ) 292 support111 = true; 293 if ( "1.3.0".equals( version ) ) 294 support130 = true; 295 } 296 297 if ( ( !support130 ) && ( !support111 ) ) { 298 support111 = true; 299 } 300 301 if ( version130 && support130 ) { 302 resp.setContentType( "text/xml" ); 303 } else { 304 resp.setContentType( "application/vnd.ogc.wms_xml" ); 305 } 306 307 XMLFragment doc = null; 308 309 if ( ( ( ( !version130 ) && support111 ) || ( !support130 ) ) && ( capa instanceof WMSCapabilities_1_3_0 ) ) { 310 doc = org.deegree.ogcwebservices.wms.XMLFactory.exportAs_1_1_1( (WMSCapabilities_1_3_0) capa ); 311 } else { 312 doc = org.deegree.ogcwebservices.wms.XMLFactory.export( (WMSCapabilities) capa ); 313 } 314 315 if ( ( version130 && support130 ) || ( !support111 ) ) { 316 doc.getRootElement().setAttribute( "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance" ); 317 doc.getRootElement().setAttribute( 318 "xsi:schemaLocation", 319 "http://www.opengis.net/wms " 320 + "http://schemas.opengis.net/wms/1.3.0/capabilities_1_3_0.xsd" 321 + " http://www.opengis.net/sld " 322 + "http://hillary.lat-lon.de/~schmitz/sld.xsd" ); 323 324 doc.prettyPrint( resp.getWriter() ); 325 } else { 326 String xml = new XMLFragment( doc.getRootElement() ).getAsString(); 327 xml = doc.getAsString(); 328 String dtd = NetWorker.url2String( configuration.getDeegreeParams().getDTDLocation() ); 329 StringBuffer sb = new StringBuffer(); 330 sb.append( "<!DOCTYPE WMT_MS_Capabilities SYSTEM " ); 331 sb.append( "'" + dtd + "' \n" ); 332 sb.append( "[\n<!ELEMENT VendorSpecificCapabilities EMPTY>\n]>" ); 333 334 int p = xml.indexOf( "?>" ); 335 if ( p > -1 ) { 336 xml = xml.substring( p + 2, xml.length() ); 337 } 338 339 xml = StringTools.concat( 50000, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "\n", sb.toString(), xml ); 340 341 xml = StringTools.replace( xml, "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", "", false ); 342 343 try { 344 PrintWriter pw = resp.getWriter(); 345 pw.print( xml ); 346 pw.close(); 347 } catch ( Exception e ) { 348 LOG.logError( "-", e ); 349 } 350 351 } 352 } 353 354 /** 355 * handles the response to a get map request 356 * 357 * @param response 358 */ 359 private void handleGetMapResponse( GetMapResult response ) 360 throws InvalidFormatException { 361 // schmitz: added the toLowerCase to avoid errors 362 String mime = MimeTypeMapper.toMimeType( ( (GetMap) request ).getFormat().toLowerCase() ); 363 364 if ( !MimeTypeMapper.isImageType( mime ) ) { 365 throw new InvalidFormatException( mime + " is not a known image format" ); 366 } 367 368 writeImage( response.getMap(), mime ); 369 } 370 371 /** 372 * handles the response to a get featureinfo request 373 * 374 * @param response 375 */ 376 private void handleFeatureInfoResponse( GetFeatureInfoResult response ) 377 throws Exception { 378 GetFeatureInfo req = (GetFeatureInfo) request; 379 380 String s = req.getInfoFormat(); 381 382 // check if GML is actually the correct one 383 // THIS IS A HACK 384 if ( req.isInfoFormatDefault() ) { 385 OperationsMetadata om = configuration.getOperationMetadata(); 386 Operation op = om.getOperation( new QualifiedName( "GetFeatureInfo" ) ); 387 DomainType dt = (DomainType) op.getParameter( new QualifiedName( "Format" ) ); 388 List<TypedLiteral> vals = dt.getValues(); 389 s = vals.get( 0 ).getValue(); 390 } 391 392 String mime = MimeTypeMapper.toMimeType( s ); 393 resp.setContentType( mime + "; charset=" + CharsetUtils.getSystemCharset() ); 394 395 String fir = response.getFeatureInfo(); 396 397 String filter = FeatureInfoFilterDef.getString( s ); 398 399 if ( filter != null ) { 400 handleFilteredFeatureInfoResponse( fir, filter ); 401 } else { 402 OutputStreamWriter os = null; 403 try { 404 os = new OutputStreamWriter( resp.getOutputStream(), CharsetUtils.getSystemCharset() ); 405 os.write( fir ); 406 } catch ( Exception e ) { 407 LOG.logError( "could not write to outputstream", e ); 408 } finally { 409 os.close(); 410 } 411 } 412 } 413 414 /** 415 * @param fir 416 * @param filter 417 * @throws MalformedURLException 418 * @throws SAXException 419 * @throws IOException 420 * @throws URISyntaxException 421 * @throws TransformerException 422 */ 423 private void handleFilteredFeatureInfoResponse( String fir, String filter ) 424 throws Exception { 425 URL url = new URL( configuration.getBaseURL(), filter ); 426 LOG.logDebug( "used XSLT for transformation: ", url ); 427 LOG.logDebug( "GML document to transform", fir ); 428 if ( url != null ) { 429 Source xmlSource = new StreamSource( new StringReader( fir ) ); 430 Source xslSource; 431 try { 432 xslSource = new StreamSource( url.openStream() ); 433 } catch ( IOException ioe ) { 434 throw new InvalidFormatException( "Unknown feature info format." ); 435 } 436 OutputStream os = null; 437 try { 438 os = resp.getOutputStream(); 439 StreamResult result = new StreamResult( os ); 440 XSLTDocument.transform( xmlSource, xslSource, result, null, null ); 441 } catch ( IOException e ) { 442 LOG.logError( "could not write to outputstream", e ); 443 } finally { 444 os.close(); 445 } 446 } 447 } 448 449 /** 450 * handles the response to a get styles request 451 * 452 * @param response 453 */ 454 private void handleGetStylesResponse( GetStylesResult response ) { 455 throw new RuntimeException( "method: handleGetStylesResponse not implemented yet" ); 456 } 457 458 /** 459 * handles the response to a put styles request 460 * 461 * @param response 462 */ 463 private void handlePutStylesResponse( PutStylesResult response ) { 464 throw new RuntimeException( "method: handlePutStylesResponse not implemented yet" ); 465 } 466 467 /** 468 * handles the response to a describe layer request 469 * 470 * @param response 471 */ 472 private void handleDescribeLayerResponse( DescribeLayerResult response ) { 473 throw new RuntimeException( "method: handleDescribeLayerResponse not implemented yet" ); 474 } 475 476 /** 477 * handles the response to a get legend graphic request 478 * 479 * @param response 480 */ 481 private void handleGetLegendGraphicResponse( GetLegendGraphicResult response ) 482 throws Exception { 483 String mime = MimeTypeMapper.toMimeType( ( (GetLegendGraphic) request ).getFormat() ); 484 485 if ( !MimeTypeMapper.isImageType( mime ) ) { 486 throw new InvalidFormatException( mime + " is not a known image format" ); 487 } 488 489 writeImage( response.getLegendGraphic(), mime ); 490 } 491 492 /** 493 * writes an service exception report into the <tt>OutputStream</tt> back to the client. the 494 * method considers the format an exception shall be returned to the client as defined in the 495 * request. 496 * 497 * @param exception 498 * the exception object containing the code and message 499 * @throws OGCWebServiceException 500 */ 501 private void writeServiceExceptionReport( OGCWebServiceException exception ) { 502 String code = "none"; 503 if ( exception.getCode() != null ) { 504 code = exception.getCode().value; 505 } 506 String message = exception.getMessage(); 507 508 LOG.logInfo( "sending exception in format " + exceptionFormat ); 509 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 510 ExceptionReport report = new ExceptionReport( new OGCWebServiceException[] { exception } ); 511 String msg; 512 if ( exceptionFormat.equals( "XML" ) ) { 513 msg = XMLFactory.exportNS( report ).getAsPrettyString(); 514 } else { 515 msg = XMLFactory.export( report ).getAsPrettyString(); 516 } 517 518 LOG.logDebug( "Exception being sent: " + msg ); 519 } 520 521 if ( exceptionFormat.equals( "application/vnd.ogc.se_inimage" ) ) { 522 BufferedImage bi = new BufferedImage( width, height, BufferedImage.TYPE_INT_ARGB ); 523 Graphics g = bi.getGraphics(); 524 525 if ( !transparent ) { 526 g.setColor( bgColor ); 527 g.fillRect( 0, 0, bi.getWidth(), bi.getHeight() ); 528 } 529 530 g.setColor( Color.BLUE ); 531 g.drawString( code, 5, 20 ); 532 int pos1 = message.indexOf( ':' ); 533 g.drawString( message.substring( 0, pos1 + 1 ), 5, 50 ); 534 g.drawString( message.substring( pos1 + 1, message.length() ), 5, 80 ); 535 String mime = MimeTypeMapper.toMimeType( format ); 536 writeImage( bi, mime ); 537 } else if ( exceptionFormat.equals( "application/vnd.ogc.se_blank" ) ) { 538 BufferedImage bi = new BufferedImage( width, height, BufferedImage.TYPE_INT_ARGB ); 539 Graphics g = bi.getGraphics(); 540 541 if ( !transparent ) { 542 g.setColor( bgColor ); 543 g.fillRect( 0, 0, bi.getWidth(), bi.getHeight() ); 544 } 545 546 g.dispose(); 547 String mime = MimeTypeMapper.toMimeType( format ); 548 writeImage( bi, mime ); 549 } else { 550 LOG.logInfo( "Sending OGCWebServiceException to client." ); 551 ExceptionReport report = new ExceptionReport( new OGCWebServiceException[] { exception } ); 552 try { 553 XMLFragment doc; 554 555 if ( exceptionFormat.equals( "XML" ) ) { 556 resp.setContentType( "text/xml" ); 557 doc = XMLFactory.exportNS( report ); 558 } else { 559 resp.setContentType( "application/vnd.ogc.se_xml" ); 560 doc = XMLFactory.export( report ); 561 } 562 563 OutputStream os = resp.getOutputStream(); 564 doc.write( os ); 565 os.close(); 566 } catch ( Exception ex ) { 567 LOG.logError( "ERROR: " + ex.getMessage(), ex ); 568 } 569 } 570 } 571 572 /** 573 * writes the passed image to the response output stream. 574 * 575 * @param output 576 * @param mime 577 */ 578 private void writeImage( Object output, String mime ) { 579 try { 580 OutputStream os = null; 581 resp.setContentType( mime ); 582 if ( mime.equalsIgnoreCase( "image/gif" ) ) { 583 os = resp.getOutputStream(); 584 ImageUtils.saveImage( (BufferedImage) output, os, "gif", 1 ); 585 } else if ( mime.equalsIgnoreCase( "image/jpg" ) || mime.equalsIgnoreCase( "image/jpeg" ) ) { 586 os = resp.getOutputStream(); 587 ImageUtils.saveImage( (BufferedImage) output, os, "jpeg", 588 configuration.getDeegreeParams().getMapQuality() ); 589 } else if ( mime.equalsIgnoreCase( "image/png" ) ) { 590 os = resp.getOutputStream(); 591 ImageUtils.saveImage( (BufferedImage) output, os, "png", 1 ); 592 } else if ( mime.equalsIgnoreCase( "image/tif" ) || mime.equalsIgnoreCase( "image/tiff" ) ) { 593 os = resp.getOutputStream(); 594 ImageUtils.saveImage( (BufferedImage) output, os, "tif", 1 ); 595 } else if ( mime.equalsIgnoreCase( "image/bmp" ) ) { 596 os = resp.getOutputStream(); 597 ImageUtils.saveImage( (BufferedImage) output, os, "bmp", 1 ); 598 } else if ( mime.equalsIgnoreCase( "image/svg+xml" ) ) { 599 os = resp.getOutputStream(); 600 resp.setContentType( "text/xml; charset=" + CharsetUtils.getSystemCharset() ); 601 PrintWriter pw = new PrintWriter( os ); 602 DOMPrinter.printNode( pw, (Node) output ); 603 pw.close(); 604 } else { 605 resp.setContentType( "text/xml; charset=" + CharsetUtils.getSystemCharset() ); 606 os = resp.getOutputStream(); 607 OGCWebServiceException exce = new OGCWebServiceException( "WMS:writeImage", 608 "unsupported image format: " + mime ); 609 os.write( ( (Marshallable) exce ).exportAsXML().getBytes() ); 610 } 611 612 os.close(); 613 } catch ( Exception e ) { 614 LOG.logError( resp.isCommitted() ? "Response is already committed!" : "Response is not committed yet." ); 615 LOG.logError( "Error while writing image: ", e ); 616 } 617 } 618 619 }