/* * Copyright 1998-2014 University Corporation for Atmospheric Research/Unidata * * Portions of this software were developed by the Unidata Program at the * University Corporation for Atmospheric Research. * * Access and use of this software shall impose the following obligations * and understandings on the user. The user is granted the right, without * any fee or cost, to use, copy, modify, alter, enhance and distribute * this software, and any derivative works thereof, and its supporting * documentation for any purpose whatsoever, provided that this entire * notice appears in all copies of the software, derivative works and * supporting documentation. Further, UCAR requests that the user credit * UCAR/Unidata in any publications that result from the use of this * software or in any product that includes this software. The names UCAR * and/or Unidata, however, may not be used in any advertising or publicity * to endorse or promote any products or commercial entity unless specific * written permission is obtained from UCAR/Unidata. The user also * understands that UCAR/Unidata is not obligated to provide the user with * any support, consulting, training or assistance of any kind with regard * to the use, operation and performance of this software nor to provide * the user with any updates, revisions, new versions or "bug fixes." * * THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "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 UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL, * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE. */ package thredds.server.wcs; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMapping; import thredds.server.config.TdsContext; import thredds.util.Version; import thredds.servlet.*; import thredds.server.wcs.v1_0_0_1.WcsHandler; import java.io.*; import java.util.List; import java.util.ArrayList; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.servlet.*; import javax.servlet.http.*; import ucar.nc2.util.DiskCache2; /** * Servlet handles serving data via WCS 1.0. */ @Component @RequestMapping("/wcs") public class WCSController { private static org.slf4j.Logger logServerStartup = org.slf4j.LoggerFactory.getLogger("serverStartup"); @Autowired private TdsContext tdsContext; private ucar.nc2.util.DiskCache2 diskCache = null; private boolean allow = false, deleteImmediately = true; private boolean allowRemote = false; private long maxFileDownloadSize; // ToDo Consider using a SortedMap to contain handlers. private List<VersionHandler> versionHandlers; private List<VersionHandler> experimentalHandlers; private String supportedVersionsString; private VersionHandler latestSupportedVersion; public enum Operation { GetCapabilities, DescribeCoverage, GetCoverage } @PostConstruct public void init() throws ServletException { allow = ThreddsConfig.getBoolean("WCS.allow", false); logServerStartup.info("WCS:allow= " + allow); if (!allow) { logServerStartup.info("WCS service not enabled in threddsConfig.xml: "); return; } allowRemote = ThreddsConfig.getBoolean("WCS.allowRemote", false); deleteImmediately = ThreddsConfig.getBoolean("WCS.deleteImmediately", deleteImmediately); maxFileDownloadSize = ThreddsConfig.getBytes("WCS.maxFileDownloadSize", (long) 1000 * 1000 * 1000); String cache = ThreddsConfig.get("WCS.dir", ServletUtil.getContentPath() + "cache/wcs/"); File cacheDir = new File(cache); cacheDir.mkdirs(); int scourSecs = ThreddsConfig.getSeconds("WCS.scour", 60 * 10); int maxAgeSecs = ThreddsConfig.getSeconds("WCS.maxAge", -1); maxAgeSecs = Math.max(maxAgeSecs, 60 * 5); // give at least 5 minutes to download before scouring kicks in. scourSecs = Math.max(scourSecs, 60 * 5); // always need to scour, in case user doesnt get the file, we need to clean it up // LOOK: what happens if we are still downloading when the disk scour starts? diskCache = new DiskCache2(cache, false, maxAgeSecs / 60, scourSecs / 60); // Version Handlers // - Latest non-experimental version supported is "1.0.0" this.latestSupportedVersion = new WcsHandler("1.0.0") .setDeleteImmediately(deleteImmediately) .setDiskCache(diskCache); // - Experimental WCS_Plus handler. VersionHandler wcsPlusVersion = new thredds.server.wcs.v1_0_0_Plus.WcsHandler("1.0.0.11") .setDeleteImmediately(deleteImmediately) .setDiskCache(diskCache); // Supported Versions [Note: Make sure to add these in increasing order!] this.versionHandlers = new ArrayList<>(); this.versionHandlers.add(this.latestSupportedVersion); // "1.0.0" for (VersionHandler vh : this.versionHandlers) { supportedVersionsString = (supportedVersionsString == null ? "" : supportedVersionsString + ",") + vh.getVersion().getVersionString(); } // Experimental Handlers [Requests must match exactly.] this.experimentalHandlers = new ArrayList<>(); this.experimentalHandlers.add(wcsPlusVersion); // "1.0.0.11" logServerStartup.info("WCS service - init done - "); } @PreDestroy public void destroy() { logServerStartup.info("WCSServlet.destroy() start: "); if (diskCache != null) diskCache.exit(); logServerStartup.info("WCSServlet.destroy() done:"); } @RequestMapping("**") public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { // Check whether TDS is configured to support WCS. if (!allow) { // ToDo - Server not configured to support WCS. Should response code be 404 (Not Found) instead of 403 (Forbidden)? res.sendError(HttpServletResponse.SC_FORBIDDEN, "WCS service not supported"); return; } // Check if TDS is configured to support WCS on remote datasets. String datasetURL = ServletUtil.getParameterIgnoreCase(req, "dataset"); // ToDo LOOK - move this into TdsConfig? if (datasetURL != null && !allowRemote) { res.sendError(HttpServletResponse.SC_FORBIDDEN, "WCS service not supported for remote datasets."); return; } // Get parameters needed to determine version. String serviceParam = ServletUtil.getParameterIgnoreCase(req, "Service"); String requestParam = ServletUtil.getParameterIgnoreCase(req, "Request"); String acceptVersionsParam = ServletUtil.getParameterIgnoreCase(req, "AcceptVersions"); String versionParam = ServletUtil.getParameterIgnoreCase(req, "Version"); // Make sure this is a WCS KVP request. if (serviceParam == null || !serviceParam.equalsIgnoreCase("WCS")) { res.sendError(HttpServletResponse.SC_BAD_REQUEST, "GET request not a WCS KVP requestParam (missing or bad SERVICE parameter)."); return; } // Decide on requested version. VersionHandler targetHandler; if (requestParam == null) { this.latestSupportedVersion.handleExceptionReport(res, "MissingParameterValue", "Request", ""); return; } else if (requestParam.equalsIgnoreCase(Operation.GetCapabilities.toString())) { if (acceptVersionsParam == null && versionParam == null) targetHandler = this.latestSupportedVersion; else if (acceptVersionsParam != null && versionParam != null) { this.latestSupportedVersion.handleExceptionReport(res, "NoApplicableCode", "", "Request requires one and only one version parameter: either \"Version\" or \"AcceptVersions\"."); return; } else if (acceptVersionsParam != null) { // Version negotiation per 1.1.0 spec. targetHandler = getHandlerUsingNegotiation_1_1_0(acceptVersionsParam); if (targetHandler == null) { this.latestSupportedVersion.handleExceptionReport(res, "VersionNegotiationFailed", "AcceptVersions", "The \"AcceptVersions\" parameter value [" + acceptVersionsParam + "[ did not match any supported versions [" + supportedVersionsString + "]."); return; } } else if (versionParam != null) { // Version negotiation per 1.0.0 spec. targetHandler = getHandlerUsingNegotiation_1_0_0(versionParam); if (targetHandler == null) { this.latestSupportedVersion.handleExceptionReport(res, "InvalidParameterValue", "Version", "Invale \"Version\" parameter value [" + acceptVersionsParam + "] did not match any supported versions [" + supportedVersionsString + "]."); return; } } else { // [[Can't really get here given all the if clauses above.]] // No version specified, use latest supported version. targetHandler = this.latestSupportedVersion; } } else { // Find requested version (no negotiation for "DescribeCoverage" and "GetCoverage" requests). if (!requestParam.equalsIgnoreCase(Operation.DescribeCoverage.toString()) && !requestParam.equalsIgnoreCase(Operation.GetCoverage.toString())) { this.latestSupportedVersion.handleExceptionReport(res, "InvalidParameterValue", "Request", "Invalid \"Operation\" parameter value [" + requestParam + "]."); return; } if (versionParam == null) { this.latestSupportedVersion.handleExceptionReport(res, "InvalidParameterValue", "Version", "Request requires a \"Version\" parameter."); return; } else { // Find matching supported version. targetHandler = getMatchingVersionHandler(versionParam); if (targetHandler == null) { // Find matching "experimental" version. targetHandler = getMatchingExpermimentalVersionHandler(versionParam); if (targetHandler == null) { this.latestSupportedVersion.handleExceptionReport(res, "InvalidParameterValue", "Version", "Invaled \"Version\" parameter value [" + versionParam + "]."); return; } } } } if (targetHandler == null) { this.latestSupportedVersion.handleExceptionReport(res, "VersionNegotiationFailed", "", "Version negotiation failed."); return; } targetHandler.handleKVP(null, req, res); } protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { if (!allow) { res.setStatus(HttpServletResponse.SC_FORBIDDEN); return; } res.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); res.setHeader("Allow", "GET"); } /** * Return the VersionHandler appropriate for the given version number * using the WCS 1.0.0 version number negotiation rules. * * @param versionNumber the requested version string * @return the appropriate VersionHandler for the requested version string (as per WCS 1.0 version negotiation). */ private VersionHandler getHandlerUsingNegotiation_1_0_0(String versionNumber) { if (versionNumber == null) return null; // Parse request version Version reqVersion = null; try { reqVersion = new Version(versionNumber); } catch (IllegalArgumentException e) { return null; } // Do version negotiation using list of supported non-experimental version handlers. VersionHandler handler = null; VersionHandler prevVh = null; for (VersionHandler curVh : this.versionHandlers) { int reqCompareCur = reqVersion.compareTo(curVh.getVersion()); if (reqCompareCur == 0) // Use matching version handler. return curVh; else if (reqCompareCur < 0) { if (prevVh == null) // Requested version lower than lowest supported version, // so use lowest supported version. return curVh; else // Requested version lower than current version, // so use previous version. return prevVh; } else if (reqCompareCur > 0) { // Requested version greater than current version, // so keep current version around in case it is needed. prevVh = curVh; } } if (handler == null) { // Look for exact matches in experimental version handlers. for (VersionHandler curVh : this.experimentalHandlers) { if (reqVersion.equals(curVh.getVersion())) return curVh; } // If no exact match on experimental version handlers, revert to // latest supported non-experimental version. [Note: Requested version is // greater than the latest supported non-experimental version, so use // latest supported version (should be same as prevVh).] return this.latestSupportedVersion; } return handler; } private VersionHandler getHandlerUsingNegotiation_1_1_0(String acceptVersionsParam) throws IOException { VersionHandler handler = null; String acceptableVersions[] = acceptVersionsParam.split(","); for (String curVerString : acceptableVersions) { handler = getMatchingVersionHandler(curVerString); if (handler != null) break; } return handler; } /** * Return the VersionHandler that supports the given version number or null * if the given version number is not supported. * * @param versionNumber the requested version string * @return the VersionHandler that supports the requested version string or null. */ private VersionHandler getMatchingVersionHandler(String versionNumber) { if (versionNumber == null) return null; Version reqVersion = null; try { reqVersion = new Version(versionNumber); } catch (IllegalArgumentException e) { return null; } for (VersionHandler curVh : this.versionHandlers) { if (reqVersion.equals(curVh.getVersion())) // Return the matching version. return curVh; } return null; } /** * Return the "experimental" VersionHandler that supports the given version number or null * if no "experimental" VersionHandler supports the given version number. * * @param versionNumber the requested version string * @return the "experimental" VersionHandler that supports the requested version string or null. */ private VersionHandler getMatchingExpermimentalVersionHandler(String versionNumber) { if (versionNumber == null) return null; Version reqVersion = null; try { reqVersion = new Version(versionNumber); } catch (IllegalArgumentException e) { return null; } for (VersionHandler curVh : this.experimentalHandlers) { if (reqVersion.equals(curVh.getVersion())) // Matching version found. return curVh; } return null; } }