/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.tradefed.util.net; import com.android.tradefed.util.xml.AbstractXmlParser; import com.android.tradefed.util.xml.AbstractXmlParser.ParseException; import org.kxml2.io.KXmlSerializer; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import java.io.IOException; import java.io.InputStream; import java.util.LinkedList; import java.util.List; /** * A mechanism to simplify writing XmlRpc. Deals with XML and XmlRpc boilerplate. * <p/> * Call semantics: * <ol> * <li>Call an "Open" method</li> * <li>Construct the value on the serializer. This may involve calling other helper methods, * perhaps recursively.</li> * <li>Call a respective "Close" method</li> * </ol> * <p/> * It is the caller's responsibility to ensure that "Open" and "Close" calls are matched properly. * The helper methods do not check this. */ public class XmlRpcHelper { public static final String TRUE_VAL = "1"; public static final String FALSE_VAL = "0"; /** * Write the opening of a method call to the serializer. * * @param serializer the {@link KXmlSerializer} * @param ns the namespace * @param name the name of the XmlRpc method to invoke */ public static void writeOpenMethodCall(KXmlSerializer serializer, String ns, String name) throws IOException { serializer.startTag(ns, "methodCall"); serializer.startTag(ns, "methodName"); serializer.text(name); serializer.endTag(ns, "methodName"); serializer.startTag(ns, "params"); } /** * Write the end of a method call to the serializer. * * @param serializer the {@link KXmlSerializer} * @param ns the namespace */ public static void writeCloseMethodCall(KXmlSerializer serializer, String ns) throws IOException { serializer.endTag(ns, "params"); serializer.endTag(ns, "methodCall"); } /** * Write the opening of a method argument to the serializer. After calling this function, the * caller should send the argument value directly to the serializer. * * @param serializer the {@link KXmlSerializer} * @param ns the namespace * @param valueType the XmlRpc type of the method argument */ public static void writeOpenMethodArg(KXmlSerializer serializer, String ns, String valueType) throws IOException { serializer.startTag(ns, "param"); serializer.startTag(ns, "value"); serializer.startTag(ns, valueType); } /** * Write the end of a method argument to the serializer. * * @param serializer the {@link KXmlSerializer} * @param ns the namespace * @param valueType the XmlRpc type of the method argument */ public static void writeCloseMethodArg(KXmlSerializer serializer, String ns, String valueType) throws IOException { serializer.endTag(ns, valueType); serializer.endTag(ns, "value"); serializer.endTag(ns, "param"); } /** * Write a full method argument to the serializer. This function is not paired with any other * function. * * @param serializer the {@link KXmlSerializer} * @param ns the namespace * @param valueType the XmlRpc type of the method argument * @param value the value of the method argument */ public static void writeFullMethodArg(KXmlSerializer serializer, String ns, String valueType, String value) throws IOException { serializer.startTag(ns, "param"); serializer.startTag(ns, valueType); serializer.text(value); serializer.endTag(ns, valueType); serializer.endTag(ns, "param"); } /** * Write the opening of a struct member to the serializer. After calling this function, the * caller should send the member value directly to the serializer. * * @param serializer the {@link KXmlSerializer} * @param ns the namespace * @param name the name of the XmlRpc member * @param valueType the XmlRpc type of the member */ public static void writeOpenStructMember(KXmlSerializer serializer, String ns, String name, String valueType) throws IOException { serializer.startTag(ns, "member"); serializer.startTag(ns, "name"); serializer.text(name); serializer.endTag(ns, "name"); serializer.startTag(ns, "value"); serializer.startTag(ns, valueType); } /** * Write the end of a struct member to the serializer. * * @param serializer the {@link KXmlSerializer} * @param ns the namespace * @param valueType the XmlRpc type of the member */ public static void writeCloseStructMember(KXmlSerializer serializer, String ns, String valueType) throws IOException { serializer.endTag(ns, valueType); serializer.endTag(ns, "value"); serializer.endTag(ns, "member"); } private static class RpcResponseHandler extends DefaultHandler { private final List<String> mResponses = new LinkedList<String>(); private String mType = null; private StringBuilder mValue = new StringBuilder(); private boolean mInParams = false; private boolean mInValue = false; private static final String PARAMS_TAG = "params"; private static final String VALUE_TAG = "value"; private static final String PARAM_TAG = "param"; @Override public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { if (!mInParams && !PARAMS_TAG.equals(localName)) { // nothing to do yet return; } else if (PARAMS_TAG.equals(localName)) { mInParams = true; return; } else if (VALUE_TAG.equals(localName) || PARAM_TAG.equals(localName)) { // Treat <value> and <param> equivalently mInValue = true; return; } else if (mInParams && mInValue) { // expect this to be the data type mType = localName; } } @Override public void endElement(String uri, String localName, String qName) { if (PARAMS_TAG.equals(localName)) { mInParams = false; } else if (VALUE_TAG.equals(localName) || PARAM_TAG.equals(localName)) { // Treat <value> and <param> equivalently mInValue = false; } else if (mType != null && mType.equals(localName)) { // commit mResponses.add(mType); mResponses.add(mValue.toString()); // clear mType = null; mValue.delete(0, mValue.length()); } } @Override public void characters(char[] ch, int start, int length) throws SAXException { if (mType == null) { // nothing to do return; } mValue.append(ch, start, length); } public List<String> getResponses() { return mResponses; } } private static class XmlRpcResponseParser extends AbstractXmlParser { private RpcResponseHandler mHandler = new RpcResponseHandler(); @Override protected DefaultHandler createXmlHandler() { return mHandler; } public List<String> getResponses() { return mHandler.getResponses(); } } /** * Parses an XmlRpc response document. Returns a flat list of pairs; the even elements are * datatype names, and the odds are string representations of the values, as passed over the * wire. * * @param input An {@link InputStream} from which the parser can read the XmlRpc response * document. * @return A flat {@code List<String>} containing datatype/value pairs, or {@code null} if * there was a parse error. */ public static List<String> parseResponseTuple(InputStream input) { XmlRpcResponseParser parser = new XmlRpcResponseParser(); try { parser.parse(input); return parser.getResponses(); } catch (ParseException e) { System.err.format("got parse exception %s\n", e); } return null; } }