All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gov.nasa.worldwind.wms.Capabilities Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */
package gov.nasa.worldwind.wms;

import gov.nasa.worldwind.exception.*;
import gov.nasa.worldwind.retrieve.*;
import gov.nasa.worldwind.util.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.*;
import java.io.*;
import java.net.*;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.logging.Level;

/**
 * @author tag
 * @version $Id: Capabilities.java 1171 2013-02-11 21:45:02Z dcollins $
 */
public abstract class Capabilities
{
    public static final String WMS_SERVICE_NAME = "OGC:WMS";

    protected Document doc;
    protected Element service;
    protected Element capability;
    protected XPath xpath;
    protected URL capsURL;

    public static Capabilities retrieve(URI uri, String service) throws Exception
    {
        return retrieve(uri, service, null, null);
    }

    public static Capabilities retrieve(URI uri, Integer connectTimeout, Integer readTimeout) throws Exception
    {
        return retrieve(uri, null, connectTimeout, readTimeout);
    }

    public static Capabilities retrieve(URI uri, String service, Integer connectTimeout, Integer readTimeout)
        throws Exception
    {
        if (uri == null)
        {
            String message = Logging.getMessage("nullValue.URIIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        InputStream is = null;

        try
        {
            // Request the capabilities document from the server.
            CapabilitiesRequest req = new CapabilitiesRequest(uri, service);
            URL capsURL = req.getUri().toURL();

            URLRetriever retriever = URLRetriever.createRetriever(capsURL, new RetrievalPostProcessor()
            {
                public ByteBuffer run(Retriever retriever)
                {
                    return retriever.getBuffer();
                }
            });

            if (retriever == null)
            {
                String message = Logging.getMessage("generic.UnrecognizedProtocol");
                Logging.logger().severe(message);
                throw new WWRuntimeException(message);
            }

            if (connectTimeout != null)
                retriever.setConnectTimeout(connectTimeout);

            if (readTimeout != null)
                retriever.setReadTimeout(readTimeout);

            retriever.call();

            if (!retriever.getState().equals(URLRetriever.RETRIEVER_STATE_SUCCESSFUL))
            {
                String message = Logging.getMessage("generic.RetrievalFailed", uri.toString());
                Logging.logger().severe(message);
                throw new WWRuntimeException(message);
            }

            if (retriever.getBuffer() == null || retriever.getBuffer().limit() == 0)
            {
                String message = Logging.getMessage("generic.RetrievalReturnedNoContent", uri.toString());
                Logging.logger().severe(message);
                throw new WWRuntimeException(message);
            }

            if (retriever.getContentType().equalsIgnoreCase("application/vnd.ogc.se_xml"))
            {
                String exceptionMessage = WWXML.extractOGCServiceException(retriever.getBuffer());
                String message = Logging.getMessage("WMS.ServiceException",
                    uri.toString() + ": " + (exceptionMessage != null ? exceptionMessage : ""));
                Logging.logger().severe(message);
                throw new WWRuntimeException(message);
            }

            // Parse the DOM as a capabilities document.
            is = WWIO.getInputStreamFromByteBuffer(retriever.getBuffer());

            Capabilities caps = Capabilities.parse(WWXML.createDocumentBuilder(true).parse(is));
            if (caps != null)
                caps.capsURL = capsURL;
            
            return caps;
        }
        catch (URISyntaxException e)
        {
            Logging.logger().log(java.util.logging.Level.SEVERE,
                Logging.getMessage("generic.URIInvalid", uri.toString()), e);
            throw e;
        }
        catch (ParserConfigurationException e)
        {
            Logging.logger().fine(Logging.getMessage("WMS.ParserConfigurationException", uri.toString()));
            throw e;
        }
        catch (IOException e)
        {
            Logging.logger().log(java.util.logging.Level.SEVERE,
                Logging.getMessage("generic.ExceptionAttemptingToReadFrom", uri.toString()), e);
            throw e;
        }
        catch (SAXException e)
        {
            Logging.logger().fine(Logging.getMessage("WMS.ParsingError", uri.toString()));
            throw e;
        }
        finally
        {
            WWIO.closeStream(is, uri.toString());
        }
    }

    public static Capabilities parse(Document doc)
    {
        XPath xpath = WWXML.makeXPath();
        xpath.setNamespaceContext(new WMSNamespaceContext());

        try
        {
            String exceptionMessage = WWXML.checkOGCException(doc);
            if (exceptionMessage != null)
            {
                String message = Logging.getMessage("WMS.ServiceException", exceptionMessage);
                Logging.logger().severe(message);
                throw new ServiceException(exceptionMessage);
            }

            String version = xpath.evaluate(altPaths("*/@wms:version"), doc);
            if (version == null || version.length() == 0)
                return null;

            if (version.compareTo("1.3") < 0)
                return new CapabilitiesV111(doc, xpath);
            else
                return new CapabilitiesV130(doc, xpath);
        }
        catch (XPathExpressionException e)
        {
            Logging.logger().log(Level.SEVERE, "WMS.ParsingError", e);
            return null;
        }
    }

    protected Capabilities(Document doc, XPath xpath)
    {
        this.doc = doc;
        this.xpath = xpath;

        try
        {
            this.service = (Element) this.xpath.evaluate(altPaths("*/wms:Service"), doc, XPathConstants.NODE);
            if (this.service == null)
            {
                String message = Logging.getMessage("WMS.NoServiceElement", "XML document");
                Logging.logger().severe(message);
                throw new IllegalArgumentException(message);
            }

            this.capability = (Element) this.xpath.evaluate(altPaths("*/wms:Capability"), doc, XPathConstants.NODE);
            if (this.capability == null)
            {
                String message = Logging.getMessage("WMS.NoCapabilityElement", "XML document");
                Logging.logger().severe(message);
                throw new IllegalArgumentException(message);
            }
        }
        catch (XPathExpressionException e)
        {
            Logging.logger().log(Level.SEVERE, "WMS.ParsingError", e);
        }
    }

    public URL getCapsURL()
    {
        return capsURL;
    }

    private static String altPaths(String path) // hack for WW server layer names with leading pipe
    {
        return path != null ? path + "|" + path.replaceAll("wms:", "") : null;
    }

    protected String getText(String path)
    {
        return this.getText(null, path);
    }

    protected String getText(Element context, String path)
    {
        try
        {
            return this.xpath.evaluate(altPaths(path), context != null ? context : doc);
        }
        catch (XPathExpressionException e)
        {
            return null;
        }
    }

    protected String[] getTextArray(Element context, String path)
    {
        try
        {
            NodeList nodes = (NodeList) this.xpath.evaluate(altPaths(path), context != null ? context : doc,
                XPathConstants.NODESET);
            if (nodes == null || nodes.getLength() == 0)
                return null;

            String[] strings = new String[nodes.getLength()];
            for (int i = 0; i < nodes.getLength(); i++)
            {
                strings[i] = nodes.item(i).getTextContent();
            }
            return strings;
        }
        catch (XPathExpressionException e)
        {
            return null;
        }
    }

    protected String[] getUniqueText(Element context, String path)
    {
        String[] strings = this.getTextArray(context, path);
        if (strings == null)
            return null;

        ArrayList sarl = new ArrayList();
        for (String s : strings)
        {
            if (!sarl.contains(s))
                sarl.add(s);
        }

        return sarl.toArray(new String[1]);
    }

    protected Element getElement(Element context, String path)
    {
        try
        {
            Node node = (Node) this.xpath.evaluate(altPaths(path), context != null ? context : doc,
                XPathConstants.NODE);
            if (node == null)
                return null;

            return node instanceof Element ? (Element) node : null;
        }
        catch (XPathExpressionException e)
        {
            return null;
        }
    }

    protected Element[] getElements(Element context, String path)
    {
        try
        {
            NodeList nodes = (NodeList) this.xpath.evaluate(altPaths(path), context != null ? context : doc,
                XPathConstants.NODESET);
            if (nodes == null || nodes.getLength() == 0)
                return null;

            Element[] elements = new Element[nodes.getLength()];
            for (int i = 0; i < nodes.getLength(); i++)
            {
                Node node = nodes.item(i);
                if (node instanceof Element)
                    elements[i] = (Element) node;
            }
            return elements;
        }
        catch (XPathExpressionException e)
        {
            return null;
        }
    }

    protected Element[] getUniqueElements(Element context, String path, String uniqueTag)
    {
        Element[] elements = this.getElements(context, path);
        if (elements == null)
            return null;

        HashMap styles = new HashMap();
        for (Element e : elements)
        {
            String name = this.getText(e, uniqueTag);
            if (name != null)
                styles.put(name, e);
        }

        return styles.values().toArray(new Element[1]);
    }

    private HashMap namedLayerElements = new HashMap();
    private HashMap namedLayers = new HashMap();

    private void fillLayerList()
    {
        if (this.namedLayers.size() == 0)
        {
            Element[] nels = this.getElements(this.capability, "descendant::wms:Layer[wms:Name]");
            if (nels == null || nels.length == 0)
                return;

            for (Element le : nels)
            {
                String name = this.getLayerName(le);
                if (name != null)
                {
                    Layer layer = new Layer(le);
                    this.namedLayers.put(name, layer);
                    this.namedLayerElements.put(le, layer);
                }
            }
        }
    }

    public Document getDocument()
    {
        return this.doc;
    }

    public Element[] getNamedLayers()
    {
        if (this.namedLayerElements.size() == 0)
            this.fillLayerList();

        return this.namedLayerElements.keySet().toArray(new Element[this.namedLayerElements.size()]);
    }

    public Element getLayerByName(String layerName)
    {
        if (this.namedLayers.size() == 0)
            this.fillLayerList();

        Layer l = this.namedLayers.get(layerName);
        return l != null ? l.element : null;
    }

    public Long getLayerLatestLastUpdateTime(Capabilities caps, String[] layerNames)
    {
        if (caps == null)
        {
            String message = Logging.getMessage("nullValue.WMSCapabilities");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        if (layerNames == null)
        {
            String message = Logging.getMessage("nullValue.WMSLayerNames");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        String lastUpdate = null;

        for (String name : layerNames)
        {
            Element layer = caps.getLayerByName(name);
            if (layer == null)
                continue;

            String update = caps.getLayerLastUpdate(layer);
            if (update != null && update.length() > 0 && (lastUpdate == null || update.compareTo(lastUpdate) > 0))
                lastUpdate =  update;
        }

        if (lastUpdate != null)
        {
            try
            {
                return Long.parseLong(lastUpdate);
            }
            catch (NumberFormatException e)
            {
                String message = Logging.getMessage("generic.ConversionError", lastUpdate);
                Logging.logger().warning(message);
            }
        }

        return null;
    }

    public Double[] getLayerExtremeElevations(Capabilities caps, String[] layerNames)
    {
        if (caps == null)
        {
            String message = Logging.getMessage("nullValue.WMSCapabilities");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        if (layerNames == null)
        {
            String message = Logging.getMessage("nullValue.WMSLayerNames");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        String extremeMin = null;
        String extremeMax = null;

        for (String name : layerNames)
        {
            Element layer = caps.getLayerByName(name);
            if (layer == null)
                continue;

            String min = caps.getLayerExtremeElevationsMin(layer);
            if (min != null && (extremeMin == null || min.compareTo(min) > 0))
                extremeMin =  min;

            String max = caps.getLayerExtremeElevationsMax(layer);
            if (max != null && (extremeMax == null || max.compareTo(max) > 0))
                extremeMax =  max;
        }

        if (extremeMin != null || extremeMax != null)
        {
            try
            {
                Double[] extremes = new Double[] {null, null};

                if (extremeMin != null)
                    extremes[0] = Double.parseDouble(extremeMin);
                if (extremeMax != null)
                    extremes[1] = Double.parseDouble(extremeMax);

                return extremes;
            }
            catch (NumberFormatException e)
            {
                String message = Logging.getMessage("generic.ConversionError",
                    extremeMin != null ? extremeMin : "" + extremeMax != null ? extremeMax : "");
                Logging.logger().severe(message);
            }
        }

        return null;
    }

    // ********* Document Items ********* //

    public String getVersion()
    {
        return this.getText("*/@wms:version");
    }

    public String getUpdateSequence()
    {
        return this.getText("*/@wms:updateSequence");
    }

    // ********* Service Items ********* //

    public String getAbstract()
    {
        return this.getText(this.service, "wms:Abstract");
    }

    public String getAccessConstraints()
    {
        return this.getText(this.service, "wms:AccessConstraints");
    }

    public String getContactOrganization()
    {
        return this.getText(
            this.service, "wms:ContactInformation/wms:ContactPersonPrimary/wms:ContactOrganization");
    }

    public String getContactPerson()
    {
        return this.getText(
            this.service, "wms:ContactInformation/wms:ContactPersonPrimary/wms:ContactPerson");
    }

    public String getFees()
    {
        return this.getText(this.service, "wms:Fees");
    }

    public String[] getKeywordList()
    {
        return this.getTextArray(this.service, "wms:KeywordList/wms:Keyword");
    }

    public String getLayerLimit()
    {
        return this.getText(this.service, "wms:LayerLimit");
    }

    public String getMaxWidth()
    {
        return this.getText(this.service, "wms:MaxWidth");
    }

    public String getMaxHeight()
    {
        return this.getText(this.service, "wms:MaxHeight");
    }

    public String getServiceName()
    {
        return this.getText(this.service, "wms:Name");
    }

    public String getTitle()
    {
        return this.getText(this.service, "wms:Title");
    }

    // ********* Capability Items ********* //

    public String getOnlineResource()
    {
        return this.getText(this.capability, "wms:OnlineResource/@xlink:href");
    }

    public String[] getGetCapabilitiesFormats()
    {
        return this.getTextArray(this.capability,
            "wms:Request/wms:GetCapabilities/wms:Format");
    }

    public String getGetCapabilitiesRequestGetURL()
    {
        return this.getText(this.capability,
            "wms:Request/wms:GetCapabilities/wms:DCPType/wms:HTTP/wms:Get/wms:OnlineResource/@xlink:href");
    }

    public String getGetCapabilitiesRequestPostURL()
    {
        return this.getText(this.capability,
            "wms:Request/wms:GetCapabilities/wms:DCPType/wms:HTTP/wms:Post/wms:OnlineResource/@xlink:href");
    }

    public String[] getExceptionFormats()
    {
        return this.getTextArray(this.capability, "wms:Exception/wms:Format");
    }

    public String getFeatureInfoRequestGetURL()
    {
        return this.getText(this.capability,
            "wms:Request/wms:GetFeatureInfo/wms:DCPType/wms:HTTP/wms:Get/wms:OnlineResource/@xlink:href");
    }

    public String getFeatureInfoRequestPostURL()
    {
        return this.getText(this.capability,
            "wms:Request/wms:GetFeatureInfo/wms:DCPType/wms:HTTP/wms:Post/wms:OnlineResource/@xlink:href");
    }

    public String[] getGetMapFormats()
    {
        return this.getTextArray(this.capability,
            "wms:Request/wms:GetMap/wms:Format");
    }

    public String getGetMapRequestGetURL()
    {
        return this.getText(this.capability,
            "wms:Request/wms:GetMap/wms:DCPType/wms:HTTP/wms:Get/wms:OnlineResource/@xlink:href");
    }

    public String getGetMapRequestPostURL()
    {
        return this.getText(this.capability,
            "wms:Request/wms:GetMap/wms:DCPType/wms:HTTP/wms:Post/wms:OnlineResource/@xlink:href");
    }

    public String getVendorSpecificCapabilities()
    {
        return this.getText(this.capability, "wms:VendorSpecificCapabilities");
    }

    public Element getLayer()
    {
        return this.getElement(this.capability, "wms:Layer");
    }

    // ********* Layer Items ********* //

    protected static class Layer
    {
        protected HashMap styleElements = new HashMap();
        protected final Element element;
        protected Layer layer;
        protected String name;
        protected String title;

        public Layer(Element element)
        {
            this.element = element;
        }
    }

    public String getLayerAbstract(Element layer)
    {
        return this.getText(layer, "wms:Abstract");
    }

    public String getLayerAttributionTitle(Element layer)
    {
        return this.getText(layer, "ancestor-or-self::wms:Layer/wms:Attribution/wms:Title");
    }

    public String getLayerAttributionURL(Element layer)
    {
        return this.getText(layer, "ancestor-or-self::wms:Layer/wms:Attribution/wms:OnlineResource/@xlink:href");
    }

    public String getLayerAttributionLogoFormat(Element layer)
    {
        return this.getText(layer, "ancestor-or-self::wms:Layer/wms:Attribution/wms:LogoURL/wms:Format");
    }

    public String getLayerAttributionLogoHeight(Element layer)
    {
        return this.getText(layer, "ancestor-or-self::wms:Layer/wms:Attribution/wms:LogoURL/@wms:height");
    }

    public String getLayerAttributionLogoURL(Element layer)
    {
        return this.getText(layer,
            "ancestor-or-self::wms:Layer/wms:Attribution/wms:LogoURL/wms:OnlineResource/@xlink:href");
    }

    public String getLayerAttributionLogoWidth(Element layer)
    {
        return this.getText(layer, "ancestor-or-self::wms:Layer/wms:Attribution/wms:LogoURL/@wms:width");
    }

    public Element[] getLayerAuthorityURLs(Element layer)
    {
        return this.getUniqueElements(layer, "ancestor-or-self::wms:Layer/wms:AuthorityURL", "@wms:type");
    }

    public abstract BoundingBox[] getLayerBoundingBoxes(Element layer);

    public String getLayerCascaded(Element layer)
    {
        return this.getText(layer, "ancestor-or-self::wms:Layer/@cascaded");
    }

    public String[] getLayerCRS(Element layer)
    {
        return this.getUniqueText(layer, "ancestor-or-self::wms:Layer/wms:CRS");
    }

    public String getLayerDataURLFormat(Element layer)
    {
        return this.getText(layer, "wms:DataURL/wms:Format");
    }

    public String getLayerDataURL(Element layer)
    {
        return this.getText(layer, "wms:DataURL/wms:OnlineResource/@xlink:href");
    }

    public Element[] getLayerDimensions(Element layer)
    {
        Element[] dims = this.getElements(layer, "ancestor-or-self::wms:Layer/wms:Dimension");

        if (dims == null || dims.length == 0)
            return null;

        ArrayList uniqueDims = new ArrayList();
        ArrayList dimNames = new ArrayList();
        for (Element e : dims)
        {
            // Filter out dimensions with same name.
            // Keep all those with a null name, even though wms says they're invalid. Let the app decide.
            String name = this.getDimensionName(e);
            if (name != null && dimNames.contains(name))
                continue;

            uniqueDims.add(e);
            dimNames.add(name);
        }

        return uniqueDims.toArray(new Element[uniqueDims.size()]);
    }

    public Element[] getLayerExtents(Element layer)
    {
        Element[] extents = this.getElements(layer, "ancestor-or-self::wms:Layer/wms:Extent");

        if (extents == null || extents.length == 0)
            return null;

        ArrayList uniqueExtents = new ArrayList();
        ArrayList extentNames = new ArrayList();
        for (Element e : extents)
        {
            // Filter out dimensions with same name.
            // Keep all those with a null name, even though wms says they're invalid. Let the app decide.
            String name = this.getDimensionName(e);
            if (name != null && extentNames.contains(name))
                continue;

            uniqueExtents.add(e);
            extentNames.add(name);
        }

        return uniqueExtents.toArray(new Element[uniqueExtents.size()]);
    }

    public abstract BoundingBox getLayerGeographicBoundingBox(Element layer);

    public String getLayerFeatureListFormat(Element layer)
    {
        return this.getText(layer, "wms:FeatureListURL/wms:Format");
    }

    public String getLayerFeatureListURL(Element layer)
    {
        return this.getText(layer, "wms:FeatureListURL/wms:OnlineResource/@xlink:href");
    }

    public String getLayerFixedHeight(Element layer)
    {
        return this.getText(layer, "ancestor-or-self::wms:Layer/@fixedHeight");
    }

    public String getLayerFixedWidth(Element layer)
    {
        return this.getText(layer, "ancestor-or-self::wms:Layer/@fixedWidth");
    }

    public Element[] getLayerIdentifiers(Element layer)
    {
        return this.getUniqueElements(layer, "wms:Identifier", "wms:authority");
    }

    public String[] getLayerKeywordList(Element layer)
    {
        return this.getTextArray(layer, "wms:KeywordList/wms:Keyword");
    }

    public abstract String getLayerMaxScaleDenominator(Element layer);

    public Element[] getLayerMetadataURLs(Element layer)
    {
        return this.getElements(layer, "wms:MetadataURL");
    }

    public abstract String getLayerMinScaleDenominator(Element layer);

    public String getLayerName(Element layerElement)
    {
        Layer layer = this.namedLayerElements.get(layerElement);
        return layer != null && layer.name != null ? layer.name : this.getText(layerElement, "wms:Name");
    }

    public String getLayerNoSubsets(Element layer)
    {
        return this.getText(layer, "ancestor-or-self::wms:Layer/@noSubsets");
    }

    public String getLayerOpaque(Element layer)
    {
        return this.getText(layer, "ancestor-or-self::wms:Layer/@opaque");
    }

    public String getLayerQueryable(Element layer)
    {
        return this.getText(layer, "ancestor-or-self::wms:Layer/@queryable");
    }

    public String[] getLayerSRS(Element layer)
    {
        return this.getUniqueText(layer, "ancestor-or-self::wms:Layer/wms:SRS");
    }

    public Element[] getLayerStyles(Element layerElement)
    {
        Layer layer = this.namedLayerElements.get(layerElement);
        if (layer == null)
            return null;

        if (layer.styleElements != null && layer.styleElements.size() != 0)
            return layer.styleElements.keySet().toArray(new Element[1]);

        Element[] styleElements = this.getUniqueElements(layerElement, "ancestor-or-self::wms:Layer/wms:Style", "Name");
        if (styleElements == null)
            return null;

        layer.styleElements = new HashMap();
        for (Element se : styleElements)
        {
            Style style = new Style(se, layer);
            layer.styleElements.put(se, style);
            this.styleElements.put(se, style);
        }

        return layer.styleElements.keySet().toArray(new Element[1]);
    }

    public Element[] getLayerSubLayers(Element layer)
    {
        return this.getElements(layer, "wms:Layer");
    }

    public String getLayerTitle(Element layerElement)
    {
        Layer layer = this.namedLayerElements.get(layerElement);
        if (layer == null)
            return this.getText(layerElement, "wms:Title");

        return layer.title != null ? layer.title : (layer.title = this.getText(layerElement, "wms:Title"));
    }

    public Element getLayerStyleByName(Element layerElement, String styleName)
    {
        Layer layer = this.namedLayerElements.get(layerElement);
        if (layer == null)
            return null;

        if (layer.styleElements == null || layer.styleElements.size() == 0)
        {
            // Initialize the layer's style list.
            this.getLayerStyles(layerElement);
            if (layer.styleElements == null || layer.styleElements.size() == 0)
                return null;
        }

        Collection