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    }