/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2008-2011 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc. * * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. * * OpenNMS(R) is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * OpenNMS(R) is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenNMS(R). If not, see: * http://www.gnu.org/licenses/ * * For more information contact: * OpenNMS(R) Licensing <license@opennms.org> * http://www.opennms.org/ * http://www.opennms.com/ *******************************************************************************/ package org.opennms.netmgt.xmlrpcd; import static org.junit.Assert.assertNotNull; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ConnectException; import java.net.Socket; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Vector; import junit.framework.AssertionFailedError; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.builder.HashCodeBuilder; import org.apache.log4j.Logger; import org.apache.xmlrpc.WebServer; import org.apache.xmlrpc.XmlRpcHandler; /** * <p> * Mock XML-RPC server that anticipates specific XML-RPC method calls. * </p> * * @author mikeh@aiinet.com * @author dj@gregor.com */ public class XmlrpcAnticipator implements XmlRpcHandler { /** * Represents an XML-RPC call as a String method name and a Vector of * method arguments. Note: the equals method looks for Hashtables in the * Vector and ignores comparisons on the values for any entries with a key * of "description". * * @author <a href="mailto:dj@opennms.org">DJ Gregor</a> */ public class XmlrpcCall { private String m_method; private Vector<Object> m_vector; public XmlrpcCall(String method, Vector<Object> vector) { assertNotNull("null method not allowed", method); assertNotNull("null vector not allowed", vector); m_method = method; m_vector = vector; } @Override public String toString() { final StringBuffer b = new StringBuffer(); b.append("Method: " + m_method + "\n"); for (final Object o : m_vector) { b.append("Parameter (" + o.getClass().getName() + ") "+ o + "\n"); } return b.toString(); } @Override public int hashCode() { return new HashCodeBuilder(9, 3) .append(m_method) .append(m_vector) .toHashCode(); } @Override public boolean equals(final Object o) { if (o == null) return false; if (!(o instanceof XmlrpcCall)) return false; final XmlrpcCall c = (XmlrpcCall) o; if (!m_method.equals(c.m_method)) { return false; } if (m_vector.size() != c.m_vector.size()) { return false; } for (int i = 0; i < m_vector.size(); i++) { Object a = m_vector.get(i); Object b = c.m_vector.get(i); if (!a.getClass().getName().equals(b.getClass().getName())) { return false; } if (a instanceof Hashtable<?,?>) { if (!hashtablesMatchIgnoringDescriptionKeys(a, b)) { return false; } } else if (!a.equals(b)) { return false; } } return true; } @SuppressWarnings("unchecked") private boolean hashtablesMatchIgnoringDescriptionKeys(Object a, Object b) { Hashtable<String, String> ha = (Hashtable<String, String>) a; Hashtable<String, String> hb = (Hashtable<String, String>) b; if (ha.size() != hb.size()) { return false; } if (!ha.keySet().equals(hb.keySet())) { return false; } for (String key : ha.keySet()) { if (key.equals("description")) { // This shouldn't happen, but let's test anyway if (!hb.containsKey(key)) { return false; } } else { if (!ha.get(key).equals(hb.get(key))) { return false; } } } return true; } } private List<XmlrpcCall> m_anticipated = new ArrayList<XmlrpcCall>(); private List<XmlrpcCall> m_unanticipated = new ArrayList<XmlrpcCall>(); private WebServer m_webServer = null; private int m_port; /** default port number */ private static final int DEFAULT_PORT_NUMBER = 9000; /** logger */ private Logger m_logger = Logger.getLogger(getClass().getName()); private static final String CHECK_METHOD_NAME = "XmlrpcAnticipatorCheck"; public XmlrpcAnticipator(int port, boolean delayWebServer) throws IOException { m_port = port; if (!delayWebServer) { setupWebServer(); } } public XmlrpcAnticipator(int port) throws IOException { this(port, false); } public XmlrpcAnticipator() throws IOException { this(DEFAULT_PORT_NUMBER, false); } public void setupWebServer() throws IOException { m_logger.info("XmlrpcAnticipator starting on port number " + m_port); m_webServer = new WebServer(m_port); m_webServer.addHandler("$default", this); m_webServer.start(); waitForStartup(); m_logger.info("XmlrpcAnticipator running on port number " + m_port); } /** * Stop listening for OpenNMS events. * */ public void shutdown() throws IOException { if (m_webServer == null) { return; } m_webServer.shutdown(); waitForShutdown(); m_webServer = null; } private void waitForStartup() throws IOException { boolean keepRunning = true; Socket s = null; while (keepRunning) { try { Thread.sleep(10); } catch (InterruptedException e) { // do nothing } try { s = new Socket("localhost", m_port); keepRunning = false; sendCheckCall(s); } catch (ConnectException e) { // do nothing } finally { if (s != null) { s.close(); } } } } private void sendCheckCall(Socket s) throws IOException { OutputStream out = null; PrintWriter p = null; try { out = s.getOutputStream(); p = new PrintWriter(out); p.print("POST / HTTP/1.0\r\n"); p.print("Connection: close\r\n"); p.print("\r\n"); p.print("<?xml.version=\"1.0\"?><methodCall><methodName>" + CHECK_METHOD_NAME + "</methodName><params></params></methodCall>\r\n"); } finally { IOUtils.closeQuietly(p); IOUtils.closeQuietly(out); out = null; p = null; } } private void waitForShutdown() throws IOException { boolean keepRunning = true; Socket s = null; while (keepRunning) { try { Thread.sleep(100); } catch (InterruptedException e) { // do nothing } try { s = new Socket("localhost", m_port); sendCheckCall(s); } catch (ConnectException e) { keepRunning = false; } finally { if (s != null) { s.close(); } } } } public void anticipateCall(String method, Object... args) { Vector<Object> params = new Vector<Object>(); for(Object arg: args) { params.add(arg); } m_anticipated.add(new XmlrpcCall(method, params)); } @SuppressWarnings("unchecked") public Object execute(String method, @SuppressWarnings("rawtypes") Vector vector) { if (m_webServer == null) { String message = "Hey! We aren't initialized (anymore)! " + "We should not be receiving execute calls!"; System.err.println(message); System.err.println(new XmlrpcCall(method, vector)); vector.add(message); return vector; } ourExecute(method, vector); return vector; } public synchronized void ourExecute(String method, Vector<Object> vector) { // Ignore internal checks if (method.equals(CHECK_METHOD_NAME)) { return; } XmlrpcCall c = new XmlrpcCall(method, vector); if (m_anticipated.contains(c)) { m_anticipated.remove(c); } else { m_unanticipated.add(c); } } public synchronized Collection<XmlrpcCall> getAnticipated() { return Collections.unmodifiableCollection(m_anticipated); } public void reset() { m_anticipated = new ArrayList<XmlrpcCall>(); m_unanticipated = new ArrayList<XmlrpcCall>(); } /** * @return */ public Collection<XmlrpcCall> unanticipatedEvents() { return Collections.unmodifiableCollection(m_unanticipated); } public void verifyAnticipated() { StringBuffer problems = new StringBuffer(); if (m_anticipated.size() > 0) { problems.append(m_anticipated.size() + " expected calls still outstanding:\n"); problems.append(listCalls("\t", m_anticipated)); } if (m_unanticipated.size() > 0) { problems.append(m_unanticipated.size() + " unanticipated calls received:\n"); problems.append(listCalls("\t", m_unanticipated)); } if (problems.length() > 0) { problems.deleteCharAt(problems.length() - 1); problems.insert(0, "XML-RPC Anticipator listening at port " + m_port + " has:\n"); throw new AssertionFailedError(problems.toString()); } } private static String listCalls(String prefix, Collection<XmlrpcCall> calls) { StringBuffer b = new StringBuffer(); for (Iterator<XmlrpcCall> it = calls.iterator(); it.hasNext();) { XmlrpcCall call = it.next(); b.append(prefix); b.append(call); b.append("\n"); } return b.toString(); } protected void finalize() { try { shutdown(); } catch (IOException e) { System.err.println("IOException received while shutting down WebServer in finalize()"); e.printStackTrace(); } } }