/*---------------- FILE HEADER ------------------------------------------ This file is part of deegree. Copyright (C) 2001-2006 by: EXSE, Department of Geography, University of Bonn http://www.giub.uni-bonn.de/deegree/ lat/lon GmbH http://www.lat-lon.de This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact: Andreas Poth lat/lon GmbH Aennchenstr. 19 53115 Bonn Germany E-Mail: poth@lat-lon.de Prof. Dr. Klaus Greve Department of Geography University of Bonn Meckenheimer Allee 166 53115 Bonn Germany E-Mail: greve@giub.uni-bonn.de ---------------------------------------------------------------------------*/ package org.deegree.enterprise.servlet; import java.awt.Color; import java.awt.Graphics; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.StringReader; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.util.List; import javax.imageio.ImageIO; import javax.servlet.http.HttpServletResponse; import javax.xml.transform.Source; import javax.xml.transform.TransformerException; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.deegree.datatypes.QualifiedName; import org.deegree.datatypes.values.TypedLiteral; import org.deegree.enterprise.ServiceException; import org.deegree.framework.log.ILogger; import org.deegree.framework.log.LoggerFactory; import org.deegree.framework.util.CharsetUtils; import org.deegree.framework.util.ImageUtils; import org.deegree.framework.util.MimeTypeMapper; import org.deegree.framework.util.NetWorker; import org.deegree.framework.util.StringTools; import org.deegree.framework.xml.DOMPrinter; import org.deegree.framework.xml.Marshallable; import org.deegree.framework.xml.XMLFragment; import org.deegree.framework.xml.XSLTDocument; import org.deegree.ogcwebservices.ExceptionReport; import org.deegree.ogcwebservices.OGCWebService; import org.deegree.ogcwebservices.OGCWebServiceException; import org.deegree.ogcwebservices.OGCWebServiceRequest; import org.deegree.ogcwebservices.OGCWebServiceResponse; import org.deegree.ogcwebservices.wms.InvalidFormatException; import org.deegree.ogcwebservices.wms.WMService; import org.deegree.ogcwebservices.wms.WMServiceFactory; import org.deegree.ogcwebservices.wms.capabilities.WMSCapabilities; import org.deegree.ogcwebservices.wms.capabilities.WMSCapabilities_1_3_0; import org.deegree.ogcwebservices.wms.configuration.WMSConfigurationType; import org.deegree.ogcwebservices.wms.configuration.WMSDeegreeParams; import org.deegree.ogcwebservices.wms.operation.DescribeLayerResult; import org.deegree.ogcwebservices.wms.operation.GetFeatureInfo; import org.deegree.ogcwebservices.wms.operation.GetFeatureInfoResult; import org.deegree.ogcwebservices.wms.operation.GetLegendGraphic; import org.deegree.ogcwebservices.wms.operation.GetLegendGraphicResult; import org.deegree.ogcwebservices.wms.operation.GetMap; import org.deegree.ogcwebservices.wms.operation.GetMapResult; import org.deegree.ogcwebservices.wms.operation.GetStylesResult; import org.deegree.ogcwebservices.wms.operation.PutStylesResult; import org.deegree.ogcwebservices.wms.operation.WMSGetCapabilitiesResult; import org.deegree.owscommon.XMLFactory; import org.deegree.owscommon_new.DomainType; import org.deegree.owscommon_new.Operation; import org.deegree.owscommon_new.OperationsMetadata; import org.w3c.dom.Node; import org.xml.sax.SAXException; /** * <code>WMSHandler</code> is the handler class for WMS requests and their results. * * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a> * @author last edited by: $Author: schmitz $ * * @version 2.0, $Revision: 1.50 $, $Date: 2006/11/24 15:05:35 $ * * @since 2.0 */ public class WMSHandler extends AbstractOWServiceHandler { private static ILogger LOG = LoggerFactory.getLogger( WMSHandler.class ); private Color bgColor = Color.WHITE; private HttpServletResponse resp = null; private OGCWebServiceRequest request = null; private String exceptionFormat; private String format = null; private boolean transparent = false; private int height = 400; private int width = 600; private WMSConfigurationType configuration = null; /** * */ WMSHandler() { LOG.logDebug( "New WMSHandler instance created: " + this.getClass().getName() ); } /** * performs the passed OGCWebServiceRequest by accessing service from the * pool and passing the request to it */ public void perform( OGCWebServiceRequest request, HttpServletResponse response ) throws ServiceException { LOG.entering(); try { resp = response; this.request = request; OGCWebService service = WMServiceFactory.getService(); configuration = (WMSConfigurationType) ( (WMService) service ).getCapabilities(); // EXCEPTION HANDLING NOTES: // currently, the exceptions are handled differently for each request type, // change the behaviour here if ( request instanceof GetMap ) { GetMap req = (GetMap) request; exceptionFormat = req.getExceptions(); format = req.getFormat(); bgColor = req.getBGColor(); transparent = req.getTransparency(); height = req.getHeight(); width = req.getWidth(); } if ( request instanceof GetLegendGraphic ) { GetLegendGraphic req = (GetLegendGraphic) request; exceptionFormat = req.getExceptions(); format = req.getFormat(); height = req.getHeight(); width = req.getWidth(); } if ( request instanceof GetFeatureInfo ) { GetFeatureInfo req = (GetFeatureInfo) request; exceptionFormat = req.getExceptions(); } if ( exceptionFormat == null || exceptionFormat.equals( "" ) ) { if ( "1.1.1".equals( request.getVersion() ) ) { exceptionFormat = "application/vnd.ogc.se_xml"; } else { exceptionFormat = "XML"; } } // fixup the exception formats, 1.3.0 has it different if ( "INIMAGE".equalsIgnoreCase( exceptionFormat ) ) { exceptionFormat = "application/vnd.ogc.se_inimage"; } if ( "BLANK".equalsIgnoreCase( exceptionFormat ) ) { exceptionFormat = "application/vnd.ogc.se_blank"; } if ( service == null ) { writeServiceExceptionReport( new OGCWebServiceException( "WMS", "could not access a WMService instance" ) ); return; } // first, try the normal case Object o = service.doService( request ); handleResponse( o ); } catch ( OGCWebServiceException e ) { writeServiceExceptionReport( e ); } LOG.exiting(); } /** * * * @param result * @throws OGCWebServiceException */ private void handleResponse( Object result ) { LOG.entering(); // this method may need restructuring // handle exception case if ( result instanceof OGCWebServiceException ) { writeServiceExceptionReport( (OGCWebServiceException) result ); LOG.exiting(); return; } try { OGCWebServiceResponse response = (OGCWebServiceResponse) result; if ( response.getException() != null ) { // handle the case that an exception occured during the // request performance writeServiceExceptionReport( response.getException() ); } else { if ( response instanceof OGCWebServiceException ) { writeServiceExceptionReport( (OGCWebServiceException) response ); } else if ( response instanceof Exception ) { sendException( resp, (Exception) response ); } else if ( response instanceof WMSGetCapabilitiesResult ) { handleGetCapabilitiesResponse( (WMSGetCapabilitiesResult) response ); } else if ( response instanceof GetMapResult ) { handleGetMapResponse( (GetMapResult) response ); } else if ( response instanceof GetFeatureInfoResult ) { handleFeatureInfoResponse( (GetFeatureInfoResult) response ); } else if ( response instanceof GetStylesResult ) { handleGetStylesResponse( (GetStylesResult) response ); } else if ( response instanceof PutStylesResult ) { handlePutStylesResponse( (PutStylesResult) response ); } else if ( response instanceof DescribeLayerResult ) { handleDescribeLayerResponse( (DescribeLayerResult) response ); } else if ( response instanceof GetLegendGraphicResult ) { handleGetLegendGraphicResponse( (GetLegendGraphicResult) response ); } } } catch ( InvalidFormatException ife ) { LOG.logError( ife.getMessage(), ife ); writeServiceExceptionReport( new OGCWebServiceException( "InvalidFormat", ife.getMessage() ) ); } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); writeServiceExceptionReport( new OGCWebServiceException( "WMS:write", e.getLocalizedMessage() ) ); } LOG.exiting(); } /** * handles the response to a get capabilities request * * @param response * @throws IOException * @throws TransformerException */ private void handleGetCapabilitiesResponse( WMSGetCapabilitiesResult response ) throws IOException, TransformerException { LOG.entering(); WMSConfigurationType capa = response.getCapabilities(); WMSDeegreeParams params = capa.getDeegreeParams(); // version war follows boolean version130 = "1.3.0".equals( capa.calculateVersion( request.getVersion() ) ); // version not set -> use highest supported version // use request's version otherwise boolean support111 = false; boolean support130 = false; for ( String version : params.getSupportedVersions() ) { if ( "1.1.1".equals( version ) ) support111 = true; if ( "1.3.0".equals( version ) ) support130 = true; } if ( ( !support130 ) && ( !support111 ) ) { support111 = true; } if ( version130 && support130 ) { resp.setContentType( "text/xml" ); } else { resp.setContentType( "application/vnd.ogc.wms_xml" ); } XMLFragment doc = null; if ( ( ( ( !version130 ) && support111 ) || ( !support130 ) ) && ( capa instanceof WMSCapabilities_1_3_0 ) ) { doc = org.deegree.ogcwebservices.wms.XMLFactory.exportAs_1_1_1( (WMSCapabilities_1_3_0) capa ); } else { doc = org.deegree.ogcwebservices.wms.XMLFactory.export( (WMSCapabilities) capa ); } if ( ( version130 && support130 ) || ( !support111 ) ) { doc.getRootElement().setAttribute( "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance" ); doc.getRootElement().setAttribute( "xsi:schemaLocation", "http://www.opengis.net/wms " + "http://schemas.opengis.net/wms/1.3.0/capabilities_1_3_0.xsd" + " http://www.opengis.net/sld " + "http://hillary.lat-lon.de/~schmitz/sld.xsd" ); doc.prettyPrint( resp.getWriter() ); } else { String xml = DOMPrinter.nodeToString( doc.getRootElement(), "" ); String dtd = NetWorker.url2String( configuration.getDeegreeParams().getDTDLocation() ); StringBuffer sb = new StringBuffer(); sb.append( "<!DOCTYPE WMT_MS_Capabilities SYSTEM " ); sb.append( "'" + dtd + "' \n" ); sb.append( "[\n<!ELEMENT VendorSpecificCapabilities EMPTY>\n]>" ); if ( xml.indexOf( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" ) > -1 ) { xml = StringTools.replace( xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "\n" + sb.toString(), false ); } else { xml = StringTools.concat( 50000, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "\n", sb.toString(), xml ); } xml = StringTools.replace( xml, "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", "", false ); try { PrintWriter pw = resp.getWriter(); pw.print( xml ); pw.close(); } catch ( Exception e ) { LOG.logError( "-", e ); } } LOG.exiting(); } /** * handles the response to a get map request * * @param response */ private void handleGetMapResponse( GetMapResult response ) throws InvalidFormatException { LOG.entering(); String mime = MimeTypeMapper.toMimeType( ( (GetMap) request ).getFormat() ); if ( !MimeTypeMapper.isImageType( mime ) ) { throw new InvalidFormatException( mime + " is not a known image format" ); } writeImage( response.getMap(), mime ); LOG.exiting(); } /** * handles the response to a get featureinfo request * * @param response */ private void handleFeatureInfoResponse( GetFeatureInfoResult response ) throws Exception { LOG.entering(); GetFeatureInfo req = (GetFeatureInfo) request; String s = req.getInfoFormat(); // check if GML is actually the correct one // THIS IS A HACK if ( req.isInfoFormatDefault() ) { OperationsMetadata om = configuration.getOperationMetadata(); Operation op = om.getOperation( new QualifiedName( "GetFeatureInfo" ) ); DomainType dt = (DomainType) op.getParameter( new QualifiedName( "Format" ) ); List<TypedLiteral> vals = dt.getValues(); s = vals.get( 0 ).getValue(); } String mime = MimeTypeMapper.toMimeType( s ); resp.setContentType( mime + "; charset=" + CharsetUtils.getSystemCharset() ); String fir = response.getFeatureInfo(); String filter = FeatureInfoFilterDef.getString( s ); if ( filter != null ) { handleFilteredFeatureInfoResponse( fir, filter ); } else { OutputStreamWriter os = null; try { os = new OutputStreamWriter( resp.getOutputStream(), CharsetUtils.getSystemCharset() ); os.write( fir ); } catch ( Exception e ) { LOG.logError( "could not write to outputstream", e ); } finally { os.close(); } } LOG.exiting(); } /** * @param fir * @param filter * @throws MalformedURLException * @throws SAXException * @throws IOException * @throws URISyntaxException * @throws TransformerException */ private void handleFilteredFeatureInfoResponse( String fir, String filter ) throws Exception { LOG.entering(); URL url = new URL( configuration.getBaseURL(), filter ); LOG.logDebug( "used XSLT for transformation: ", url ); LOG.logDebug( "GML document to transform", fir ); if ( url != null ) { Source xmlSource = new StreamSource( new StringReader( fir ) ); Source xslSource; try { xslSource = new StreamSource( url.openStream() ); } catch ( IOException ioe ) { throw new InvalidFormatException( "Unknown feature info format." ); } OutputStream os = null; try { os = resp.getOutputStream(); StreamResult result = new StreamResult( os ); XSLTDocument.transform( xmlSource, xslSource, result, null, null ); } catch ( IOException e ) { LOG.logError( "could not write to outputstream", e ); } finally { os.close(); } } LOG.exiting(); } /** * handles the response to a get styles request * * @param response */ private void handleGetStylesResponse( GetStylesResult response ) { throw new RuntimeException( "method: handleGetStylesResponse not implemented yet" ); } /** * handles the response to a put styles request * * @param response */ private void handlePutStylesResponse( PutStylesResult response ) { throw new RuntimeException( "method: handlePutStylesResponse not implemented yet" ); } /** * handles the response to a describe layer request * * @param response */ private void handleDescribeLayerResponse( DescribeLayerResult response ) { throw new RuntimeException( "method: handleDescribeLayerResponse not implemented yet" ); } /** * handles the response to a get legend graphic request * * @param response */ private void handleGetLegendGraphicResponse( GetLegendGraphicResult response ) throws Exception { LOG.entering(); String mime = MimeTypeMapper.toMimeType( ( (GetLegendGraphic) request ).getFormat() ); if ( !MimeTypeMapper.isImageType( mime ) ) { throw new InvalidFormatException( mime + " is not a known image format" ); } writeImage( response.getLegendGraphic(), mime ); LOG.exiting(); } /** * writes an service exception report into the <tt>OutputStream</tt> back * to the client. the method considers the format an exception shall be * returned to the client as defined in the request. * * @param exception the exception object containing the code and message * @throws OGCWebServiceException */ private void writeServiceExceptionReport( OGCWebServiceException exception ) { LOG.entering(); String code = "none"; if ( exception.getCode() != null ) { code = exception.getCode().value; } String message = exception.getMessage(); LOG.logInfo( "sending exception in format " + exceptionFormat ); if ( exceptionFormat.equals( "application/vnd.ogc.se_inimage" ) ) { BufferedImage bi = new BufferedImage( width, height, BufferedImage.TYPE_INT_ARGB ); Graphics g = bi.getGraphics(); if ( !transparent ) { g.setColor( bgColor ); g.fillRect( 0, 0, bi.getWidth(), bi.getHeight() ); } g.setColor( Color.BLUE ); g.drawString( code, 5, 20 ); int pos1 = message.indexOf( ':' ); g.drawString( message.substring( 0, pos1 + 1 ), 5, 50 ); g.drawString( message.substring( pos1 + 1, message.length() ), 5, 80 ); String mime = MimeTypeMapper.toMimeType( format ); writeImage( bi, mime ); } else if ( exceptionFormat.equals( "application/vnd.ogc.se_blank" ) ) { BufferedImage bi = new BufferedImage( width, height, BufferedImage.TYPE_INT_ARGB ); Graphics g = bi.getGraphics(); if ( !transparent ) { g.setColor( bgColor ); g.fillRect( 0, 0, bi.getWidth(), bi.getHeight() ); } g.dispose(); String mime = MimeTypeMapper.toMimeType( format ); writeImage( bi, mime ); } else { LOG.logInfo( "Sending OGCWebServiceException to client." ); ExceptionReport report = new ExceptionReport( new OGCWebServiceException[] { exception } ); try { XMLFragment doc; if ( exceptionFormat.equals( "XML" ) ) { resp.setContentType( "text/xml" ); doc = XMLFactory.exportNS( report ); } else { resp.setContentType( "application/vnd.ogc.se_xml" ); doc = XMLFactory.export( report ); } OutputStream os = resp.getOutputStream(); doc.write( os ); os.close(); } catch ( Exception ex ) { LOG.logError( "ERROR: " + ex.getMessage(), ex ); } } LOG.exiting(); } /** * writes the passed image to the response output stream. * @param output * @param mime */ private void writeImage( Object output, String mime ) { try { OutputStream os = null; resp.setContentType( mime ); if ( mime.equalsIgnoreCase( "image/gif" ) ) { os = resp.getOutputStream(); ImageUtils.saveImage( (BufferedImage) output, os, "gif", 1 ); } else if ( mime.equalsIgnoreCase( "image/jpg" ) || mime.equalsIgnoreCase( "image/jpeg" ) ) { os = resp.getOutputStream(); ImageUtils.saveImage( (BufferedImage) output, os, "jpeg", configuration.getDeegreeParams().getMapQuality() ); } else if ( mime.equalsIgnoreCase( "image/png" ) ) { os = resp.getOutputStream(); ImageIO.write( (BufferedImage) output, "png", os ); } else if ( mime.equalsIgnoreCase( "image/tif" ) || mime.equalsIgnoreCase( "image/tiff" ) ) { os = resp.getOutputStream(); ImageUtils.saveImage( (BufferedImage) output, os, "tif", 1 ); } else if ( mime.equalsIgnoreCase( "image/bmp" ) ) { os = resp.getOutputStream(); ImageUtils.saveImage( (BufferedImage) output, os, "bmp", 1 ); } else if ( mime.equalsIgnoreCase( "image/svg+xml" ) ) { os = resp.getOutputStream(); resp.setContentType( "text/xml; charset=" + CharsetUtils.getSystemCharset() ); PrintWriter pw = new PrintWriter( os ); DOMPrinter.printNode( pw, (Node) output ); pw.close(); } else { resp.setContentType( "text/xml; charset=" + CharsetUtils.getSystemCharset() ); os = resp.getOutputStream(); OGCWebServiceException exce = new OGCWebServiceException( "WMS:writeImage", "unsupported image format: " + mime ); os.write( ( (Marshallable) exce ).exportAsXML().getBytes() ); } os.close(); } catch ( Exception e ) { LOG.logError( "-", e ); } } } /* ******************************************************************** Changes to this class. What the people have been up to: $Log: WMSHandler.java,v $ Revision 1.50 2006/11/24 15:05:35 schmitz Added a bogus sld schema document to be able to validate the GetCapabilities response with a GetLegendGraphic request type. Revision 1.49 2006/11/22 15:38:31 schmitz Fixed more exception handling, especially for the GetFeatureInfo request. Revision 1.48 2006/11/22 14:06:26 schmitz Fixed some minor details in the WMS example configuration. Added CRS:84 to proj4. Fixed exception handling for WMS. Revision 1.47 2006/10/27 09:52:24 schmitz Brought the WMS up to date regarding 1.1.1 and 1.3.0 conformance. Fixed a bug while creating the default GetLegendGraphics URLs. Revision 1.46 2006/10/22 20:19:52 poth support for vendor specific operation getScaleBar removed Revision 1.45 2006/10/17 20:31:18 poth *** empty log message *** Revision 1.44 2006/09/12 10:08:33 schmitz Fixed another xlink issue, fixed content-type header for WMS capabilities. Revision 1.43 2006/09/08 13:43:42 schmitz Fixed the versioning of WMS for the case no <SupportedVersion> was defined. Revision 1.42 2006/09/08 08:42:02 schmitz Updated the WMS to be 1.1.1 conformant once again. Cleaned up the WMS code. Added cite WMS test data. Revision 1.39 2006/08/21 15:42:08 mschneider Removed (senseless) "implements ServiceDispatcher". Revision 1.38 2006/08/06 20:10:20 poth runtime ex ception will be thrown if a not implemented method will be called Revision 1.37 2006/08/06 19:48:11 poth file header and footer added Revision 1.36 2006/07/28 08:01:27 schmitz Updated the WMS for 1.1.1 compliance. Fixed some documentation. Revision 1.35 2006/07/23 10:05:54 poth setting content type for Http responses enhanced by adding charset (for mime types text/plain and text/xml) Revision 1.34 2006/07/12 14:46:15 poth comment footer added ********************************************************************** */