///////////////////////////////////////////////////////////////////////////// // This file is part of the "Java-DAP" project, a Java implementation // of the OPeNDAP Data Access Protocol. // // Copyright (c) 2010, OPeNDAP, Inc. // Copyright (c) 2002,2003 OPeNDAP, Inc. // // Author: James Gallagher <jgallagher@opendap.org> // // All rights reserved. // // Redistribution and use in source and binary forms, // with or without modification, are permitted provided // that the following conditions are met: // // - Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // - 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. // // - Neither the name of the OPeNDAP nor the names of its contributors may // be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT // HOLDER OR CONTRIBUTORS 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 opendap.dap; import java.net.URLConnection; import com.coverity.security.Escape; import ucar.httpservices.HTTPMethod; import org.apache.http.*; /** * Aprses and holds the Server Version information returned by a DAP server. * This information is used to determine the version of the DAP protocol used to * by the DAP server to encode the data.<br> * <br> * Currently the Server Version can come from * more than one source. An DAP server responding to to a client request * over HTTP must include either an <code>XDAP</code> header or an <code> * XDODS-Server</code> header. <br>> * <br> * The <code>XDAP</code> header content is defined as the protocol version * of the transmission and must be of the form: <br> * <br> * MV.mv[.mmv]<br> * <br> * Where:<br> * <br> * MV = Major Version Number<br> * mv = Minor Version Number<br> * mmv = An optional sub-minor version number.<br> * <br> * For example an <code>XDAP</code> header value of 3.2 indicates a major version of 3 * minor version of 2. An <code>XDAP</code> header value of 2.6.3 indicates a major * version number of 2, a minor version number of 6, and a subminor version * of 3.<br> * <br> * The <code>XDODS-Server</code> header is a legacy mechanism which * identifies the version of the server software generating the response. * This is somewhat loosely coupled to the protocol version and was the * legacy mechanism for identifying the protocol version. It should be * considered deprecated. Thus, clients seeking to read data from OPeNDAP * servers should first check the server response for the existence of an * <code>XDAP</code> header, if one is not found then the client should * check for an <code>XDODS-Server</code> header. If the repsonse is missing * both headers then an exception should be thrown as the server response is * invalid. * * @author ndp * @see ClientIO * @see DDS */ public class ServerVersion implements java.io.Serializable { static final long serialVersionUID = 1; public static final int XDODS_SERVER = 0; public static final int XDAP = 1; public static final String DAP2_PROTOCOL_VERSION = "3.2"; /** * Major version number. */ private int major; /** * Minor version number. */ private int minor; /** * Sub-Minor version number. */ private int subminor; /** * Full version string. */ private String versionString; /** * Determines Server (Protocol) Version based on the headers associated * with the passed org.apache.http.methods.GetMethod. * * @param method The GetMethod containing the DAP2 headers. * @throws DAP2Exception When bad things happen (like the headers are * missing or incorrectly constructed. */ public ServerVersion(HTTPMethod method) throws DAP2Exception { // Did the Server send an XDAP header? Header h = method.getResponseHeader("XDAP"); if (h != null) { versionString = h.getValue(); processXDAPVersion(versionString); return; } // Did the Server send an XDODS-Server header? h = method.getResponseHeader("XDODS-Server"); if (h != null) { versionString = h.getValue(); processXDODSServerVersion(versionString); return; } // This is important! If neither of these headers (XDAP or // XDODS-Server) is present then we are not connected to a real // OPeNDAP server. Period. Without the information contained // in these headers some data types (Such as Sequence) cannot // be correctly serialized/deserialized. throw new DAP2Exception("Not a valid OPeNDAP server - " + "Missing MIME Header fields! Either \"XDAP\" " + "or \"XDODS-Server.\" must be present."); } /** * Determines Server (Protocol) Version based on the headers associated * with the passed java.net.URLConnection. * * @param connection The URLCOnnection containing the DAP2 headers * @throws DAP2Exception When bad things happen (like the headers are * missing or incorrectly constructed. */ public ServerVersion(URLConnection connection) throws DAP2Exception { // Did the Server send an XDAP header? String sHeader_server = connection.getHeaderField("XDAP"); if (sHeader_server != null) { processXDAPVersion(sHeader_server); return; } // Did the Server send an XDODS-Server header? sHeader_server = connection.getHeaderField("XDODS-Server"); if(sHeader_server == null) // Did the Server send an xdods-server header? sHeader_server = connection.getHeaderField("xdods-server"); if (sHeader_server != null) { processXDODSServerVersion(sHeader_server); return; } // This is important! If neither of these headers (XDAP or // XDODS-Server is present then we are not connected to a real // OPeNDAP server. Period. Without the information contained // in these headers some data types (Such as Sequence) cannot // be correctly serialized/deserialized. throw new DAP2Exception("Not a valid OPeNDAP server - " + "Missing MIME Header fields! Either \"XDAP\" " + "or \"XDODS-Server.\" must be present."); } /** * Construct a new ServerVersion, setting major and minor version based * on the full version string. Currently the Server Version can come from * more than one source. An OPeNDAP server responding to to a client request * over HTTP must include either an <code>XDAP</code> header or an <code> * XDODS-Server</code> header. <br>> * <br> * The <code>XDAP</code> header content is defined as the protocol version * of the transmission and must be of the form: <br> * <br> * MV.mv[.mmv]<br> * <br> * Where:<br> * <br> * MV = Major Version Number<br> * mv = Minor Version Number<br> * mmv = An optional sub-minor version number.<br> * <br> * For example an <code>XDAP</code> header value of 3.2 indicates a major version of 3 * minor version of 2. An <code>XDAP</code> header value of 2.6.3 indicates a major * version number of 2, a minor version number of 6, and a subminor version * of 3.<br> * <br> * The <code>XDODS-Server</code> header is a legacy mechanism which * identifies the version of the server software generating the response. * This is somewhat loosely coupled to the protocol version and was the * legacy mechanism for identifying the protocol version. It should be * considered deprecated. Thus, clients seeking to read data from OPeNDAP * servers should first check the server response for the existence of an * <code>XDAP</code> header, if one is not found then the client should * check for an <code>XDODS-Server</code> header. If the repsonse is missing * both headers then an exception should be thrown as the server response is * invalid. * * @param ver the full version string. * @param headerType The type of header that the version was read from. * May be set to <code>ServerVersion.XDODS_SERVER</code> or * <code>ServerVersion.XDAP</code> * @throws DAP2Exception When the things go wrong. */ public ServerVersion(String ver, int headerType) throws DAP2Exception { this.versionString = ver; this.major = this.minor = 0; // set version to default values this.subminor = -1; // LogStream.out.println("Server Version String: " + ver); switch (headerType) { case XDAP: processXDAPVersion(ver); break; case XDODS_SERVER: processXDODSServerVersion(ver); break; default: throw new DAP2Exception("Invalid Header Type. Must be one of " + "ServerVersion.XDAP or ServerVersion.XDODS_SERVER"); } } private void processXDODSServerVersion(String ver) throws DAP2Exception { String badVersionMsg = "Invalid XDODS-Server header: " + Escape.html(ver) + " Version must contain an identifying word (ex: opendap or " + "DODS followed by a \"/\" and then MV.mv (Where MV = " + "MajorVersionNumber and mv = MinorVersionNumber)"; // search for the String, e.g. DODS/2.15, and set major and minor // accordingly int verIndex = ver.indexOf("/"); if (verIndex != -1) { // This skips over the identifying word (dods, opendap, dap, etc) verIndex += 1; // skip over "/" to number } else { // If the identifying word is missing then we punt and try to // read the value as if it is just the Major.Minor number. // Which is really bullshit, but a bunch of servers got built that // do corrrectly utilze this parameter. verIndex = 0; } int dotIndex = ver.indexOf('.', verIndex); if (dotIndex != -1) { String majorString = ver.substring(verIndex, dotIndex); major = Integer.parseInt(majorString); String minorString = ver.substring(dotIndex + 1); int minorDotIndex = minorString.indexOf('.'); if (minorDotIndex != -1) { minor = Integer.parseInt(minorString.substring(0, minorDotIndex)); subminor = Integer.parseInt(minorString.substring(minorDotIndex + 1)); } else minor = Integer.parseInt(minorString); } else { throw new DAP2Exception(badVersionMsg); } } private void processXDAPVersion(String ver) { int dotIndex = ver.indexOf('.'); if (dotIndex != -1) { String majorString = ver.substring(0, dotIndex); major = Integer.parseInt(majorString); String minorString = ver.substring(dotIndex + 1); int minorDotIndex = minorString.indexOf('.'); if (minorDotIndex != -1) minor = Integer.parseInt(minorString.substring(0, minorDotIndex)); else minor = Integer.parseInt(minorString); } } /** * Construct a new ServerVersion, setting major and minor version explicitly. * * @param major the major version number. * @param minor the minor version number. */ public ServerVersion(int major, int minor) { this.major = major; this.minor = minor; } /** * Returns the major version number. * * @return the major version number. */ public final int getMajor() { return major; } /** * Returns the minor version number. * * @return the minor version number. */ public final int getMinor() { return minor; } /** * Returns the sub-minor version number, if it exists. * * @return the minor version number or -1 if the sub-minor version has not been set. */ public final int getSubMinor() { return minor; } /** * Returns the full version string. * * @return the full version string. */ public final String toString() { String version = major + "." + minor; if (subminor >= 0) version += "." + subminor; return "Version string: " + versionString + " produces headers XDAP: " + version + " XDODS-Server: DODS/" + version; } /** * Returns the full version string. * * @return the full version string. */ public final String getVersionString() { return versionString; } }