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 }