/* * Copyright (c) 2007 The University of Reading * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University of Reading, nor the names of the * authors or contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package thredds.server.wms; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.servlet.ModelAndView; import thredds.server.dataset.TdsRequestedDataset; import thredds.server.wms.config.WmsDetailedConfig; import thredds.servlet.ServletUtil; import ucar.nc2.dt.GridDataset; import uk.ac.rdg.resc.ncwms.controller.AbstractWmsController; import uk.ac.rdg.resc.ncwms.controller.RequestParams; import uk.ac.rdg.resc.ncwms.exceptions.LayerNotDefinedException; import uk.ac.rdg.resc.ncwms.exceptions.OperationNotSupportedException; import uk.ac.rdg.resc.ncwms.exceptions.WmsException; import uk.ac.rdg.resc.ncwms.usagelog.UsageLogEntry; import uk.ac.rdg.resc.ncwms.wms.Dataset; import uk.ac.rdg.resc.ncwms.wms.Layer; /** * <p>WmsController for THREDDS</p> * * @author Jon Blower */ public final class ThreddsWmsController extends AbstractWmsController { private static final Logger log = LoggerFactory.getLogger( ThreddsWmsController.class ); private final Logger logServerStartup = org.slf4j.LoggerFactory.getLogger( "serverStartup" ); private WmsDetailedConfig wmsConfig; private static final class ThreddsLayerFactory implements LayerFactory { private ThreddsDataset ds; public ThreddsLayerFactory( ThreddsDataset ds ) { this.ds = ds; } @Override public Layer getLayer( String layerName ) throws LayerNotDefinedException { ThreddsLayer layer = ds.getLayerById( layerName ); if ( layer == null ) throw new LayerNotDefinedException( layerName ); return layer; } } /** * Called by Spring to initialize the controller. Loads the WMS configuration * from /content/thredds/wmsConfig.xml. * @throws Exception if the config file could not be loaded for some reason. */ @Override public void init() throws Exception { super.init(); ThreddsServerConfig tdsWmsServerConfig = (ThreddsServerConfig) this.serverConfig; logServerStartup.info( "WMS:allow= " + tdsWmsServerConfig.isAllow() ); if ( tdsWmsServerConfig.isAllow() ) { logServerStartup.info( "WMS:allowRemote= " + tdsWmsServerConfig.isAllowRemote() ); File wmsConfigFile = tdsWmsServerConfig.getTdsContext().getConfigFileSource().getFile( "wmsConfig.xml" ); if ( wmsConfigFile == null || !wmsConfigFile.exists() || !wmsConfigFile.isFile() ) { tdsWmsServerConfig.setAllow( false ); logServerStartup.error( "init(): Disabling WMS: Could not find wmsConfig.xml. [Default version available at ${TOMCAT_HOME}/webapps/thredds/WEB-INF/altContent/startup/wmsConfig.xml." ); return; } this.wmsConfig = WmsDetailedConfig.fromFile( wmsConfigFile ); logServerStartup.info( "init(): Loaded WMS configuration from wmsConfig.xml" ); } } @Override protected ModelAndView dispatchWmsRequest( String request, RequestParams params, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, UsageLogEntry usageLogEntry ) throws Exception { ThreddsServerConfig tdsWmsServerConfig = (ThreddsServerConfig) this.serverConfig; if ( ! tdsWmsServerConfig.isAllow() ) { log.debug( "dispatchWmsRequest(): WMS service not supported." ); httpServletResponse.sendError( HttpServletResponse.SC_FORBIDDEN, "WMS service not supported." ); return null; } GridDataset gd = null; try { TdsRequestedDataset reqDataset = new TdsRequestedDataset( httpServletRequest ); if ( reqDataset.isRemote() && ! tdsWmsServerConfig.isAllowRemote() ) { log.debug( "dispatchWmsRequest(): WMS service not supported for remote datasets." ); throw new WmsException( "WMS service not supported for remote (non-server-resident) datasets.", "LayerNotDefined"); } try { gd = reqDataset.openAsGridDataset( httpServletRequest, httpServletResponse ); } catch ( FileNotFoundException e ) { // LOOK ToDo Instead could catch FileNotFoundExceptions below and also add to exceptionResolver in wms-servlet.xml log.debug( "dispatchWmsRequest(): File not found [{}]:{}.", reqDataset.getPath(), e.getMessage()); throw new LayerNotDefinedException( reqDataset.getPath()); } catch ( Exception e ) { log.error( "dispatchWmsRequest()on [" + reqDataset.getPath() + "]:", e); httpServletResponse.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return null; } if ( gd == null ) { // We have sent an auth challenge to the client, so we send no // further information return null; } // Extract the metadata from the GridDataset to form a Dataset object // TODO: what to use for the dataset ID? // TODO: It can be inefficient to create an entire {@link ThreddsDataset} object when // all we need is a single layer. This means that a lot of unnecessary objects will be // created when only a single layer is needed, e.g. for a GetMap operation. Should create // a means to extract a single layer without creating a whole dataset; however, this // could be tricky when dealing with virtual layers (e.g. velocities). //ThreddsDataset ds = new ThreddsDataset( reqDataset.getPath(), gd, this.wmsConfig ); ThreddsDataset ds = ThreddsDataset.getThreddsDatasetForRequest(request, gd, reqDataset, this.wmsConfig, params ); // Create an object that extracts layers from the dataset ThreddsLayerFactory layerFactory = new ThreddsLayerFactory( ds ); ModelAndView modelAndView; switch (request) { case "GetCapabilities": // The Capabilities document will contain a single dataset Collection<? extends Dataset> datasets = Arrays.asList(ds); // In THREDDS we don't know the last update time so we use null modelAndView = getCapabilities(datasets, null, params, httpServletRequest, usageLogEntry); //httpServletResponse.setContentType(ContentType.xml.toString()); break; case "GetMap": modelAndView = getMap(params, layerFactory, httpServletResponse, usageLogEntry); break; case "GetFeatureInfo": modelAndView = getFeatureInfo(params, layerFactory, httpServletRequest, httpServletResponse, usageLogEntry); break; // The REQUESTs below are non-standard and could be refactored into // a different servlet endpoint case "GetMetadata": ThreddsMetadataController tms = new ThreddsMetadataController(layerFactory, tdsWmsServerConfig, ds); // This is a request for non-standard metadata. (This will one // day be replaced by queries to Capabilities fragments, if possible.) // Delegate to the ThreddsMetadataController modelAndView = tms.handleRequest(httpServletRequest, httpServletResponse, usageLogEntry); break; case "GetLegendGraphic": // This is a request for an image that contains the colour scale // and range for a given layer modelAndView = getLegendGraphic(params, layerFactory, httpServletResponse); break; case "GetTransect": modelAndView = getTransect(params, layerFactory, httpServletResponse, usageLogEntry); break; case "GetVerticalProfile": modelAndView = getVerticalProfile(params, layerFactory, httpServletResponse, usageLogEntry); break; case "GetVerticalSection": modelAndView = getVerticalSection(params, layerFactory, httpServletResponse, usageLogEntry); break; default: throw new OperationNotSupportedException(request); } return modelAndView; } catch ( LayerNotDefinedException e ) { log.debug( "dispatchWmsRequest(): LayerNotDefinedException: " + e.getMessage()); throw e; } catch ( WmsException e ) { log.debug( "dispatchWmsRequest(): WmsException: " , e ); throw e; } catch ( thredds.server.dataset.DatasetException e ) { log.error( "dispatchWmsRequest(): DatasetException: " + e.getMessage() ); throw new WmsException( e.getMessage() ); } catch ( java.net.SocketException e ) { log.debug( "dispatchWmsRequest(): SocketException: " + e.getMessage()); httpServletResponse.setStatus(ServletUtil.STATUS_CLIENT_ABORT); return null; } catch ( IOException e ) { if ( e.getClass().getName().equals( "org.apache.catalina.connector.ClientAbortException")) { log.debug("dispatchWmsRequest(): ClientAbortException: " + e.getMessage()); return null; } log.error( "dispatchWmsRequest(): IOException: ", e ); if ( httpServletResponse.isCommitted() ) return null; throw e; } catch ( Exception e ) { log.error( "dispatchWmsRequest(): Exception: ", e ); if ( httpServletResponse.isCommitted() ) return null; throw e; } catch ( Error e ) { log.error( "dispatchWmsRequest(): Error: ", e ); if ( httpServletResponse.isCommitted() ) return null; throw e; } finally { // We ensure that the GridDataset object is closed if ( gd != null) gd.close(); } } }