Child pages
  • TDS Servlets Configuration (4.4.0-alpha)
Skip to end of metadata
Go to start of metadata

1. Spring Configuration.

In TDS we use two Spring application contexts: the root context and the web or servlet context.
The root context contains resources visible to all components that may be required by servlets, filters... and the web context hosts local TDS components needed for the request-processing and are delegated to by the DispatcherServlet.

To load the TDS Spring configuration into our root application context we use the Spring ContextLoaderListener adding the following configuration to the web.xml file:

Spring ContextLoaderListener in web.xml
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

and the context-param that tells where the xml file (or files) with our bean definitions live:

Context param configuration to specify our application context file
<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/applicationContext-tdsConfig.xml</param-value>
</context-param>

The servlet context is also defined in the web.xml file with this configuration:

Root servlet configuration in web.xm
<servlet>
   <servlet-name>root</servlet-name>
   <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <init-param>
         <param-name>contextConfigLocation</param-name>
         <param-value>/WEB-INF/servlet-context.xml</param-value>
      </init-param>
     <load-on-startup>1</load-on-startup>
</servlet>

This dispatcher servlet acts as FrontController and dispatches web requests to the Spring annotated controllers. It is mapped to "/":

Root servlet is mapped to "/"
<servlet-mapping>
  <servlet-name>root</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>

1.1 Root Application Context.

In the root application context is where the shared resources are defined and also where the classpath scanning, which is actually the process how most components are discovered, is enabled.
Also, it is where all the Spring MVC infrastructure for processing requests with annotated controllers is defined.

Configuratuion in the applicationContext-tdsConfig.xml file to enable annotated controllers request-processing infrastructure and classpath scanning
<!-- Enables the Spring MVC @Controller programming model  -->
<mvc:annotation-driven />

<!-- enabling component-scan in Spring for annotated wirings -->
<context:component-scan base-package="thredds"/>

1.2 Servlet Application Context.

This application context contains resources shared by Spring controllers such as view resolvers. TDS defines two view resolvers: InternalResourceViewResolver and a XmlViewResolver.

  • InternalResourceViewResolver translates a string returned by a handler into a JSP resource in the directory /WEB-INF/jsp.
  • XmlViewResolver uses bean definitions in the XML specified in the resource location: /WEB-INF/view.xml.
View resolvers in the servlet-context.xml
<bean id="xmlViewResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
  <property name="cache" value="true"/>
  <property name="order" value="1"/>
  <property name="location" value="/WEB-INF/view.xml"/>
</bean>

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
   <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
   <property name="prefix" value="/WEB-INF/jsp/"/>
   <property name="suffix" value=".jsp"/>
</bean>

1.2.1 TDS custom views.

The /WEB-INF/view.xml defines two custom views that are used by the TDS: FileView and InvCatalogXmlView.

TDS custom views definitions
<bean id="threddsFileView" class="thredds.server.views.FileView"  init-method="init" />

<bean id="threddsInvCatXmlView" class="thredds.server.views.InvCatalogXmlView" />

When a controller returns the string "threddsFileView" or "threddsInvCatXmlView" the XmlViewResolver will translate it into the corresponding view object (FileView or InvCatalogXmlView) that will render the output.

2 Other Spring Configuration: non-annotated controllers.

TDS also uses some non-annotated Spring controllers. These controllers have their own application context file each and are also defined in the web.xml file. The name of the xml context file must match the name of the servlet specified in the web.xml file, unless configured other way.

Definition of the cdmRemote servlet in web.xml
<servlet>
   <servlet-name>cdmRemote</servlet-name>
   <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
   <load-on-startup>4</load-on-startup>
</servlet>

In the code above, the declared name of the servlet is cdmRemote so the application context for the servlet is created using the beans defined in the cdmRemote-servlet.xml.
The url mappings to this controller are defined in the web.xml and in the application context file of the controller.

Url mappings for the cdmRemote controller in the web.xml file
<servlet-mapping>
   <servlet-name>cdmRemote</servlet-name>
   <url-pattern>/cdmremote/*</url-pattern>
</servlet-mapping>
Mappings for the cdmRemote controller in the cdmRemote-servlet.xml file
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
      <value>
        /**=cdmRemoteController
      </value>
    </property>
  </bean>

This way the controller will handle all the requests matching thredds/cdmRemote/**.
Note the patterns in the cdmRemote are applied after the url pattern defined in the web.xml. So, there is no need to repeat /cdmremote in them.

Current servlets using this configuration in the TDS are:

  • catalogService
  • cdmRemote
  • radarServer
  • wms

In controllers using this configuration the request's servletPath will be the name declared in the web.xml and the infoPath everything between the servletPath and the query string whereas in annotated controllers (root servlet is mapped to "/") the servletPath will be empty and the infoPath will be everything.

3 Non-Spring servlets.

Besides Spring controllers, in TDS there are servlets that do not use Spring configuration at all:

  • OpenDap
  • FileServer
  • WCSServlet
  • DLWriterServlet
  • RestrictedDataset

In this case, the servlet and the url mappings are completely defined in the web.xml

Opendap servlet configuration in web.xml
<servlet>
   <display-name>OPeNDAP Server</display-name>
   <servlet-name>Opendap</servlet-name>
   <servlet-class>thredds.server.opendap.OpendapServlet</servlet-class>
   <load-on-startup>2</load-on-startup>
</servlet>
...
<servlet-mapping>
   <servlet-name>Opendap</servlet-name>
   <url-pattern>/dodsC/*</url-pattern>
</servlet-mapping>

4 Servlets and Controllers mappings summary

4.1 Servlets

Servlet

Mapping

Opendap

/dodsC/*

FileServer

/fileServer/*

WCSServlet

/wcs/*

DLWriterServlet

/DLwriter/*

RestrictedDataset

/restrictedAccess/*

4.2 Non-annotated Spring controllers

Controller Class

Servlet Name

Mappings

Configuration file

LocalCatalogServiceController

catalogService

/catalog/*/.xml
/catalog/*/.html

catalogService-servlet.xml

RemoteCatalogServiceController

catalogService

/catalog/catalogServices
/catalog/remoteCatalogService
/catalog/remoteCatalogValidation.html

catalogService-servlet.xml

CdmRemoteController

cdmRemote

/cdmremote/**

cdmRemote-servlet.xml

CatalogRadarServerController

radarServer

/radarServer/**/catalog.xml
/radarServer/**/dataset.xml
/radarServer/**/catalog.html
/radarServer/**/dataset.html

radarServer-servlet.xml

QueryRadarServerController

radarServer

/radarServer/*/?*

radarServer-servlet.xml

StationRadarServerController

radarServer

/radarServer/**/stations.xml

radarServer-servlet.xml

ThreddsWmsController

wms

/wms/**

wms-servlet.xml

 

root

/

servlet-context.xml

4.3 Annotated Spring controllers

Controller Class

Handler method

Mapping

RootController

getRootPage

/

ViewerController

launchViewer

{viewer}.jnlp

ServerInfoController

getServerInfoHtml

/serverInfo.html

ServerInfoController

getServerInfoXML

/serverInfo.xml

ServerInfoController

getServerVersion

/serverVersion.txt

DirDisplayController

handleRequestInternal

/admin/**

LogController

handleRequestInternal

/admin/log/**
/admin/roots

CollectionController

handleRequestInternal

/admin/collection
/admin/collection/trigger

DebugController

handleRequestInternal

/admin/debug
/admin/debug/*

FmrcCacheController

handleRequestInternal

/admin/fmrcCache
/admin/fmrcCache/*

CdmrfController

metadataRequestHandler

/cdmrfeature/**, params={"req!=data", "req!=dataForm","req!=header" }

CdmrfController

headerRequestHandler

/cdmrfeature/**, params={"req=header"}

CdmrfController

dataRequestHandler

/cdmrfeature/**, params={"req=data"}

MetadataController

getMetadata

/metadata/**

NcssController

handleRequest

/ncss/**

NcssInfoController

getDatasetDescription

/ncss/**/dataset.html
/ncss/**/dataset.xml
/ncss/**/pointDataset.html, params={"!var"}

NcssDatasetInfoController

getStations

/ncss/**/station.xml

NcssDatasetBoundariesController

getDatasetBoundaries

/ncss/**/datasetBoundaries

5 Adding custom services to TDS

Current TDS Spring configuration is enabled to scan packages starting by "thredds" and it will discover all annotated controllers in those packages. So, to create a custom service you can create your own package starting by "thredds" and containing the annotated controllers with their corresponding request mappings.

Basic Spring annotated controller
package thredds.service.demo;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/demoService")
public class DemoServiceController {

	@RequestMapping("/**")
	public  ResponseEntity<String> handleRequest(HttpServletRequest request, HttpServletResponse response){

		HttpHeaders responseHeaders = new HttpHeaders();
		responseHeaders.setContentType(MediaType.TEXT_PLAIN);
		return new ResponseEntity<String>(request.getServletPath(), responseHeaders, HttpStatus.OK);

	}
}

Next step after you created your jar with your controllers you will need to drop it in your TDS running instance in the directory $TOMCAT_HOME/webapps/thredds/WEB-INF/lib. Then, you are ready to add your service to your catalogs.

Adding your custom service to your catalogs
<service name="demoService" serviceType="demoService" base="/thredds/demoService" />

The values of the attributes name and serviceType are arbitrary but the value of the base attribute must be "/thredds" followed by the root mapping you have used in your controller. The DemoServiceController of the example has "/demoService" as root mapping and will handle all the requests starting by "/thredds/demoService" therefore that must be the value of the base attribute in the service configuration.

5.1 Handling TDS datasets

TDS opens datasets using the datasetPath. This datasetPath can be obtained from the request url and the servlet path. The servlet path for annotated controllers is everything in the request url between "/thredds/" and the query string and the datasetPath will be, usually, the part of the servlet path after the root mapping of your service.

Example request
http://thredds-dev.ucar.edu/thredds/ncss/grib/NCEP/DGEX/CONUS_12km/best?var=MSLP_Eta_model_reduction_msl&latitude=40.019&longitude=-105.293&time=2013-09-28T06:00:00Z&vertCoord=&accept=xml

Servlet Path: /ncss/grib/NCEP/DGEX/CONUS_12km/best
Dataset Path: /grib/NCEP/DGEX/CONUS_12km/best
Query String: var=MSLP_Eta_model_reduction_msl&latitude=40.019&longitude=-105.293&time=2013-09-28T06:00:00Z&vertCoord=&accept=xml

Once you got the datasetPath you can open it using the DatasetHandler methods (assuming the tds-classes.jar and cdm.jar are in your classpath).
The DatasetHandler class provides several methods to open datasets depending on which type of dataset are you dealing with and all of them take as parameters a HttpServletRequest, a HttpServletResponse and a String with the datasetPath:

Method

Return Type

getFeatureCollection

InvDatasetFeatureCollection

openGridDataset

GridDataset

getNetcdfFile

NetcdfFile

An approach to deal with the different types of datasets is to use the FeatureDataset interface to wrap them all:

Netcdf Subset Service code that opens a TDS dataset and returns it wrapped in a FeatureDataset
public FeatureDataset findDatasetByPath(HttpServletRequest req,
			HttpServletResponse res, String datasetPath) throws IOException {

		FeatureType type = null;
		FeatureDataset fd = null;
		InvDatasetFeatureCollection ftCollection =  DatasetHandler.getFeatureCollection(req, res, datasetPath);

		if(ftCollection != null){
			type = ftCollection.getDataType();
			if(type == FeatureType.GRID){
				GridDataset gds = DatasetHandler.openGridDataset(req, res, datasetPath);
				//builds a FeatureDataset from an TypedDataset
				fd = new ucar.nc2.dt.grid.GridDataset( new NetcdfDataset(  gds.getNetcdfFile() ));
			}

			if(type == FeatureType.STATION ){
				 fd = ftCollection.getFeatureDatasetPoint();
			}

		}else{
			//Try as file?
		    NetcdfFile ncfile = DatasetHandler.getNetcdfFile(req, res, datasetPath);
		    Set<Enhance> enhance = Collections.unmodifiableSet(EnumSet.of(Enhance.CoordSystems, Enhance.ConvertEnums));
			if (ncfile != null) {
				//Wrap it into a FeatureDataset
				fd = FeatureDatasetFactoryManager.wrap(
						FeatureType.ANY,
						NetcdfDataset.wrap(ncfile, enhance),
						null,
						new Formatter( System.err ));
			}
		}

		return fd;

	}

This way controllers and classes that deal with your business logic will work with a common interface for all datasets.

  • No labels