|
| 1 | +// Licensed to the Apache Software Foundation (ASF) under one |
| 2 | +// or more contributor license agreements. See the NOTICE file |
| 3 | +// distributed with this work for additional information |
| 4 | +// regarding copyright ownership. The ASF licenses this file |
| 5 | +// to you under the Apache License, Version 2.0 (the |
| 6 | +// "License"); you may not use this file except in compliance |
| 7 | +// with the License. You may obtain a copy of the License at |
| 8 | +// |
| 9 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | +// |
| 11 | +// Unless required by applicable law or agreed to in writing, |
| 12 | +// software distributed under the License is distributed on an |
| 13 | +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 14 | +// KIND, either express or implied. See the License for the |
| 15 | +// specific language governing permissions and limitations |
| 16 | +// under the License. |
| 17 | + |
| 18 | +package com.xensource.xenapi; |
| 19 | + |
| 20 | + |
| 21 | +import org.apache.ws.commons.util.NamespaceContextImpl; |
| 22 | +import org.apache.xmlrpc.XmlRpcException; |
| 23 | +import org.apache.xmlrpc.client.XmlRpcClient; |
| 24 | +import org.apache.xmlrpc.client.XmlRpcClientConfigImpl; |
| 25 | +import org.apache.xmlrpc.client.XmlRpcHttpClientConfig; |
| 26 | +import org.apache.xmlrpc.common.TypeFactory; |
| 27 | +import org.apache.xmlrpc.common.TypeFactoryImpl; |
| 28 | +import org.apache.xmlrpc.common.XmlRpcStreamConfig; |
| 29 | +import org.apache.xmlrpc.parser.MapParser; |
| 30 | +import org.apache.xmlrpc.parser.TypeParser; |
| 31 | +import org.xml.sax.Attributes; |
| 32 | +import org.xml.sax.SAXException; |
| 33 | +import org.xml.sax.SAXParseException; |
| 34 | + |
| 35 | +import javax.xml.namespace.QName; |
| 36 | +import java.net.URL; |
| 37 | +import java.util.HashMap; |
| 38 | +import java.util.Map; |
| 39 | +import java.util.TimeZone; |
| 40 | + |
| 41 | +public class ConnectionNew extends Connection { |
| 42 | + /** |
| 43 | + * The version of the bindings that this class belongs to. |
| 44 | + * |
| 45 | + * @deprecated This field is not used any more. |
| 46 | + */ |
| 47 | + private APIVersion apiVersion; |
| 48 | + |
| 49 | + |
| 50 | + /** |
| 51 | + * Updated when Session.login_with_password() is called. |
| 52 | + */ |
| 53 | + @Override |
| 54 | + public APIVersion getAPIVersion() |
| 55 | + { |
| 56 | + return apiVersion; |
| 57 | + } |
| 58 | + |
| 59 | + /** |
| 60 | + * The opaque reference to the session used by this connection |
| 61 | + */ |
| 62 | + private String sessionReference; |
| 63 | + |
| 64 | + /** |
| 65 | + * As seen by the xmlrpc library. From our point of view it's a server. |
| 66 | + */ |
| 67 | + private final XmlRpcClient client; |
| 68 | + |
| 69 | + /** |
| 70 | + * Creates a connection to a particular server using a given url. This object can then be passed |
| 71 | + * in to any other API calls. |
| 72 | + * |
| 73 | + * Note this constructor does NOT call Session.loginWithPassword; the programmer is responsible for calling it, |
| 74 | + * passing the Connection as a parameter. No attempt to connect to the server is made until login is called. |
| 75 | + * |
| 76 | + * When this constructor is used, a call to dispose() will do nothing. The programmer is responsible for manually |
| 77 | + * logging out the Session. |
| 78 | + * |
| 79 | + * @param url The URL of the server to connect to |
| 80 | + * @param replyTimeout The reply timeout for xml-rpc calls in seconds |
| 81 | + * @param connTimeout The connection timeout for xml-rpc calls in seconds |
| 82 | + */ |
| 83 | + public ConnectionNew(URL url, int replyTimeout, int connTimeout) |
| 84 | + { |
| 85 | + super(url, replyTimeout, connTimeout); |
| 86 | + this.client = getClientFromURL(url, replyTimeout, connTimeout); |
| 87 | + } |
| 88 | + |
| 89 | + private XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl(); |
| 90 | + |
| 91 | + @Override |
| 92 | + public XmlRpcClientConfigImpl getConfig() |
| 93 | + { |
| 94 | + return config; |
| 95 | + } |
| 96 | + |
| 97 | + static class CustomerMapParser extends MapParser { |
| 98 | + |
| 99 | + private int level = 0; |
| 100 | + private StringBuffer nameBuffer = new StringBuffer(); |
| 101 | + private Object nameObject; |
| 102 | + private Map map; |
| 103 | + private boolean inName; |
| 104 | + private boolean inValue; |
| 105 | + private boolean doneValue; |
| 106 | + |
| 107 | + public CustomerMapParser(XmlRpcStreamConfig pConfig, NamespaceContextImpl pContext, TypeFactory pFactory) { |
| 108 | + super(pConfig, pContext, pFactory); |
| 109 | + } |
| 110 | + |
| 111 | + protected void addResult(Object pResult) throws SAXException { |
| 112 | + if (this.inName) { |
| 113 | + this.nameObject = pResult; |
| 114 | + } else { |
| 115 | + if (this.nameObject == null) { |
| 116 | + throw new SAXParseException("Invalid state: Expected name", this.getDocumentLocator()); |
| 117 | + } |
| 118 | + |
| 119 | + this.map.put(this.nameObject, pResult); |
| 120 | + } |
| 121 | + |
| 122 | + } |
| 123 | + |
| 124 | + public void startDocument() throws SAXException { |
| 125 | + super.startDocument(); |
| 126 | + this.level = 0; |
| 127 | + this.map = new HashMap(); |
| 128 | + this.inValue = this.inName = false; |
| 129 | + } |
| 130 | + |
| 131 | + public void characters(char[] pChars, int pOffset, int pLength) throws SAXException { |
| 132 | + if (this.inName && !this.inValue) { |
| 133 | + this.nameBuffer.append(pChars, pOffset, pLength); |
| 134 | + } else { |
| 135 | + super.characters(pChars, pOffset, pLength); |
| 136 | + } |
| 137 | + |
| 138 | + } |
| 139 | + |
| 140 | + public void ignorableWhitespace(char[] pChars, int pOffset, int pLength) throws SAXException { |
| 141 | + if (this.inName) { |
| 142 | + this.characters(pChars, pOffset, pLength); |
| 143 | + } else { |
| 144 | + super.ignorableWhitespace(pChars, pOffset, pLength); |
| 145 | + } |
| 146 | + |
| 147 | + } |
| 148 | + |
| 149 | + public void startElement(String pURI, String pLocalName, String pQName, Attributes pAttrs) throws SAXException { |
| 150 | + switch (this.level++) { |
| 151 | + case 0: |
| 152 | + if (!"".equals(pURI) || !"struct".equals(pLocalName)) { |
| 153 | + throw new SAXParseException("Expected struct, got " + new QName(pURI, pLocalName), this.getDocumentLocator()); |
| 154 | + } |
| 155 | + break; |
| 156 | + case 1: |
| 157 | + if (!"".equals(pURI) || !"member".equals(pLocalName)) { |
| 158 | + throw new SAXParseException("Expected member, got " + new QName(pURI, pLocalName), this.getDocumentLocator()); |
| 159 | + } |
| 160 | + |
| 161 | + this.doneValue = this.inName = this.inValue = false; |
| 162 | + this.nameObject = null; |
| 163 | + this.nameBuffer.setLength(0); |
| 164 | + break; |
| 165 | + case 2: |
| 166 | + if (this.doneValue) { |
| 167 | + throw new SAXParseException("Expected /member, got " + new QName(pURI, pLocalName), this.getDocumentLocator()); |
| 168 | + } |
| 169 | + |
| 170 | + if ("".equals(pURI) && "name".equals(pLocalName)) { |
| 171 | + if (this.nameObject != null) { |
| 172 | + throw new SAXParseException("Expected value, got " + new QName(pURI, pLocalName), this.getDocumentLocator()); |
| 173 | + } |
| 174 | + |
| 175 | + this.inName = true; |
| 176 | + } else if ("".equals(pURI) && "value".equals(pLocalName)) { |
| 177 | + if (this.nameObject == null) { |
| 178 | + throw new SAXParseException("Expected name, got " + new QName(pURI, pLocalName), this.getDocumentLocator()); |
| 179 | + } |
| 180 | + |
| 181 | + this.inValue = true; |
| 182 | + this.startValueTag(); |
| 183 | + } |
| 184 | + break; |
| 185 | + case 3: |
| 186 | + if (this.inName && "".equals(pURI) && "value".equals(pLocalName)) { |
| 187 | + if (!this.cfg.isEnabledForExtensions()) { |
| 188 | + throw new SAXParseException("Expected /name, got " + new QName(pURI, pLocalName), this.getDocumentLocator()); |
| 189 | + } |
| 190 | + |
| 191 | + this.inValue = true; |
| 192 | + this.startValueTag(); |
| 193 | + } else { |
| 194 | + super.startElement(pURI, pLocalName, pQName, pAttrs); |
| 195 | + } |
| 196 | + break; |
| 197 | + default: |
| 198 | + super.startElement(pURI, pLocalName, pQName, pAttrs); |
| 199 | + } |
| 200 | + |
| 201 | + } |
| 202 | + |
| 203 | + public void endElement(String pURI, String pLocalName, String pQName) throws SAXException { |
| 204 | + switch (--this.level) { |
| 205 | + case 0: |
| 206 | + this.setResult(this.map); |
| 207 | + case 1: |
| 208 | + break; |
| 209 | + case 2: |
| 210 | + if (this.inName) { |
| 211 | + this.inName = false; |
| 212 | + if (this.nameObject == null) { |
| 213 | + this.nameObject = this.nameBuffer.toString(); |
| 214 | + } else { |
| 215 | + for(int i = 0; i < this.nameBuffer.length(); ++i) { |
| 216 | + if (!Character.isWhitespace(this.nameBuffer.charAt(i))) { |
| 217 | + throw new SAXParseException("Unexpected non-whitespace character in member name", this.getDocumentLocator()); |
| 218 | + } |
| 219 | + } |
| 220 | + } |
| 221 | + } else if (this.inValue) { |
| 222 | + this.endValueTag(); |
| 223 | + this.doneValue = true; |
| 224 | + } |
| 225 | + break; |
| 226 | + case 3: |
| 227 | + if (this.inName && this.inValue && "".equals(pURI) && "value".equals(pLocalName)) { |
| 228 | + this.endValueTag(); |
| 229 | + } else { |
| 230 | + super.endElement(pURI, pLocalName, pQName); |
| 231 | + } |
| 232 | + break; |
| 233 | + default: |
| 234 | + super.endElement(pURI, pLocalName, pQName); |
| 235 | + } |
| 236 | + |
| 237 | + } |
| 238 | + } |
| 239 | + |
| 240 | + private XmlRpcClient getClientFromURL(URL url, int replyWait, int connWait) |
| 241 | + { |
| 242 | + config.setTimeZone(TimeZone.getTimeZone("UTC")); |
| 243 | + config.setServerURL(url); |
| 244 | + config.setReplyTimeout(replyWait * 1000); |
| 245 | + config.setConnectionTimeout(connWait * 1000); |
| 246 | + XmlRpcClient client = new XmlRpcClient(); |
| 247 | + client.setConfig(config); |
| 248 | + client.setTypeFactory(new TypeFactoryImpl(client) { |
| 249 | + @Override |
| 250 | + public TypeParser getParser(XmlRpcStreamConfig pConfig, NamespaceContextImpl pContext, String pURI, String pLocalName) { |
| 251 | + TypeParser parser = super.getParser(pConfig, pContext, pURI, pLocalName); |
| 252 | + if (parser instanceof MapParser) { |
| 253 | + return new CustomerMapParser(pConfig, pContext, this); |
| 254 | + } |
| 255 | + return parser; |
| 256 | + } |
| 257 | + }); |
| 258 | + return client; |
| 259 | + } |
| 260 | + |
| 261 | + @Override |
| 262 | + public String getSessionReference() |
| 263 | + { |
| 264 | + return this.sessionReference; |
| 265 | + } |
| 266 | + |
| 267 | + @Override |
| 268 | + protected Map dispatch(String methodCall, Object[] methodParams) throws XmlRpcException, Types.XenAPIException |
| 269 | + { |
| 270 | + Map response = (Map) client.execute(methodCall, methodParams); |
| 271 | + |
| 272 | + if (methodCall.equals("session.login_with_password") && |
| 273 | + response.get("Status").equals("Success")) |
| 274 | + { |
| 275 | + Session session = Types.toSession(response.get("Value")); |
| 276 | + sessionReference = session.ref; |
| 277 | + setAPIVersion(session); |
| 278 | + } |
| 279 | + else if (methodCall.equals("session.slave_local_login_with_password") && |
| 280 | + response.get("Status").equals("Success")) |
| 281 | + { |
| 282 | + sessionReference = Types.toSession(response.get("Value")).ref; |
| 283 | + apiVersion = APIVersion.latest(); |
| 284 | + } |
| 285 | + else if (methodCall.equals("session.logout")) |
| 286 | + { |
| 287 | + // Work around a bug in XenServer 5.0 and below. |
| 288 | + // session.login_with_password should have rejected us with |
| 289 | + // HOST_IS_SLAVE, but instead we don't find out until later. |
| 290 | + // We don't want to leak the session, so we need to log out |
| 291 | + // this session from the master instead. |
| 292 | + if (response.get("Status").equals("Failure")) |
| 293 | + { |
| 294 | + Object[] error = (Object[]) response.get("ErrorDescription"); |
| 295 | + if (error.length == 2 && error[0].equals("HOST_IS_SLAVE")) |
| 296 | + { |
| 297 | + try |
| 298 | + { |
| 299 | + XmlRpcHttpClientConfig clientConfig = (XmlRpcHttpClientConfig)client.getClientConfig(); |
| 300 | + URL client_url = clientConfig.getServerURL(); |
| 301 | + URL masterUrl = new URL(client_url.getProtocol(), (String)error[1], client_url.getPort(), client_url.getFile()); |
| 302 | + |
| 303 | + Connection tmp_conn = new Connection(masterUrl, sessionReference, clientConfig.getReplyTimeout(), clientConfig.getConnectionTimeout()); |
| 304 | + |
| 305 | + Session.logout(tmp_conn); |
| 306 | + } |
| 307 | + catch (Exception ex) |
| 308 | + { |
| 309 | + // Ignore |
| 310 | + } |
| 311 | + } |
| 312 | + } |
| 313 | + |
| 314 | + this.sessionReference = null; |
| 315 | + } |
| 316 | + |
| 317 | + return Types.checkResponse(response); |
| 318 | + } |
| 319 | + |
| 320 | + |
| 321 | + private void setAPIVersion(Session session) throws Types.XenAPIException, XmlRpcException |
| 322 | + { |
| 323 | + try |
| 324 | + { |
| 325 | + long major = session.getThisHost(this).getAPIVersionMajor(this); |
| 326 | + long minor = session.getThisHost(this).getAPIVersionMinor(this); |
| 327 | + apiVersion = APIVersion.fromMajorMinor(major, minor); |
| 328 | + } |
| 329 | + catch (Types.BadServerResponse exn) |
| 330 | + { |
| 331 | + apiVersion = APIVersion.UNKNOWN; |
| 332 | + } |
| 333 | + } |
| 334 | +} |
0 commit comments