package com.ibm.sbt.test.lib; /* * © Copyright IBM Corp. 2013 * * 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. */ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.RandomAccessFile; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map.Entry; import org.apache.commons.io.IOUtils; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.ProtocolVersion; import org.apache.http.conn.EofSensorInputStream; import org.apache.http.entity.BasicHttpEntity; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHttpResponse; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.w3c.dom.CharacterData; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import com.ibm.commons.runtime.Context; import com.ibm.commons.util.io.base64.Base64OutputStream; import com.ibm.commons.xml.DOMUtil; import com.ibm.commons.xml.XMLException; import com.ibm.sbt.services.client.ClientServicesException; import com.ibm.sbt.services.endpoints.Endpoint; /** * * @author Lorenzo Boccaccia * @author Carlos Manias * */ public class MockSerializer { // used to know when to append and when to reset the mock file; //TODO will // not work if we test with multiple endpoints in the same test run private static final HashSet<String> seen = new HashSet<String>(); private static final HashMap<String, Iterator<Node>> replyStream = new HashMap<String, Iterator<Node>>(); private Endpoint endpoint; private String endpointName; public MockSerializer(Endpoint endpoint) { if (endpoint instanceof MockEndpoint) { endpointName = ((MockEndpoint) endpoint).getInnerEndpoint(); } else { for (Entry e : Context.get().getSessionMap().entrySet()) { if (e.getValue() == endpoint) this.endpointName = e.getKey().toString(); } } } public void writeData(String data) throws IOException { File file = getFile(true); RandomAccessFile raf = new RandomAccessFile(file, "rw"); // Seek to end of file System.out.println("Writing Record @" + file.length() + " in " + file.getAbsolutePath()); raf.seek((file.length() - "\n</responses>".length())); raf.write(data.getBytes("UTF-8")); raf.write("\n</responses>".getBytes("UTF-8")); } private Iterator<Node> getReader() throws IOException { String path = getResource(); if (replyStream.containsKey(path)) { return replyStream.get(path); } Document doc; try { doc = DOMUtil.createDocument(getClass().getResourceAsStream(path), false); Iterator<Node> nodeIt = DOMUtil.evaluateXPath(doc, "//response") .getNodeIterator(); replyStream.put(path, nodeIt); return nodeIt; } catch (XMLException e) { throw new IOException(e); } } public synchronized HttpResponse recordResponse(HttpResponse response) { try { StringWriter out = new StringWriter(); out.write("\n<response "); out.write("statusCode=\""); int statusCode = response.getStatusLine().getStatusCode(); out.write(String.valueOf(statusCode).trim()); out.write("\" "); out.write("statusReason=\""); String reasonPhrase = response.getStatusLine().getReasonPhrase(); out.write(String.valueOf(reasonPhrase).trim()); out.write("\">\n"); out.write("<headers>"); Header[] allHeaders = response.getAllHeaders(); out.write(serialize(allHeaders)); String serializedEntity = null; if (response.getEntity()!=null) { out.write("</headers>\n<data><![CDATA["); serializedEntity = serialize(response.getEntity() .getContent()); serializedEntity = serializedEntity.replaceAll("<!\\[CDATA\\[", "\\!\\[CDATA\\[").replaceAll("\\]\\]>", "\\]\\]"); out.write(serializedEntity); out.write("]]></data>\n</response>"); } else { out.write("</headers>\n</response>"); } out.flush(); out.close(); writeData(out.toString()); return buildResponse(allHeaders, statusCode, reasonPhrase, serializedEntity); } catch (IOException e) { throw new UnsupportedOperationException(e); } } private HttpResponse buildResponse(Header[] allHeaders, int statusCode, String reasonPhrase, String serializedEntity) { BasicHttpResponse r = new BasicHttpResponse(new ProtocolVersion("HTTP", 1, 0), statusCode, reasonPhrase); r.setHeaders(allHeaders); if (serializedEntity!=null) { BasicHttpEntity e = new BasicHttpEntity(); // TODO: use content-encoding header try { e.setContent(new ByteArrayInputStream(serializedEntity .getBytes("UTF-8"))); } catch (UnsupportedEncodingException e1) { throw new UnsupportedOperationException(e1); } for (Header h : allHeaders) { if (h.getName().equalsIgnoreCase("Content-Type")) { e.setContentType(h.getValue()); } } e.setContentLength(serializedEntity.length()); r.setEntity(e); } return r; } private String serialize(InputStream is) throws IOException { ByteArrayOutputStream w = new ByteArrayOutputStream(); IOUtils.copy(is, w); return w.toString("UTF-8"); } private String serialize(Node o) throws IOException { ByteArrayOutputStream w = new ByteArrayOutputStream(); try { DOMUtil.serialize(w, o, null); } catch (XMLException e) { throw new IOException(e); } return w.toString("UTF-8"); } private String serialize(Header[] o) { StringWriter w = new StringWriter(); for (Header h : o) { w.write("\n <header>\n <name><![CDATA["); w.write(h.getName()); w.write("]]></name>\n <value><![CDATA["); w.write(h.getValue()); w.write("]]></value>\n </header>"); } return w.toString(); } private String serialize(Object o) throws IOException { if (o instanceof EofSensorInputStream) return serialize((EofSensorInputStream) o); if (o instanceof Node) return serialize((Node) o); if (o instanceof Header[]) return serialize((Header[]) o); ByteArrayOutputStream w = new ByteArrayOutputStream(); Base64OutputStream base64OutputStream = new Base64OutputStream(w); ObjectOutputStream os = new ObjectOutputStream(base64OutputStream); os.writeObject(o); os.flush(); os.close(); base64OutputStream.flush(); base64OutputStream.close(); return w.toString("UTF-8"); } private Object deserialize(String o) throws IOException { ObjectInputStream is = new ObjectInputStream(new ByteArrayInputStream( o.getBytes())); try { return is.readObject(); } catch (ClassNotFoundException e) { throw new IOException(e); } finally { is.close(); } } public HttpResponse replayResponse() throws ClientServicesException { try { Node r = getReader().next(); NamedNodeMap nnm = r.getAttributes(); String code = nnm.getNamedItem("statusCode").getTextContent(); String reason = nnm.getNamedItem("statusReason").getTextContent(); Node headers = (Node) DOMUtil.evaluateXPath(r, "./headers") .getSingleNode(); Node data = (Node) DOMUtil.evaluateXPath(r, "./data") .getSingleNode(); String entity = null; if (data!= null ) { if (data.getFirstChild() == null) entity = ""; else entity = ((CharacterData) data.getFirstChild()).getData(); } Iterator<Node> hIt = (Iterator<Node>) DOMUtil.evaluateXPath( headers, "./header").getNodeIterator(); ArrayList<Header> allHeaders = new ArrayList<Header>(); while (hIt.hasNext()) { Node headerNode = hIt.next(); String name = ((Node) DOMUtil.evaluateXPath(headerNode, "./name").getSingleNode()).getTextContent(); String value = ((Node) DOMUtil.evaluateXPath(headerNode, "./value").getSingleNode()).getTextContent(); allHeaders.add(new BasicHeader(name, value)); } return buildResponse( allHeaders.toArray(new Header[allHeaders.size()]), Integer.valueOf(code), reason, entity); } catch (FileNotFoundException e) { StackTraceElement trace = getStackTraceElement(); String fullClassName = trace.getClassName(); String methodName = trace.getMethodName(); String endpointName = getEndpointName(); throw new MockingException(e, "Mocking file missing for test: " + fullClassName + "." + methodName + "/" + endpointName); } catch (Exception e) { throw new MockingException(e, "Corrupted Mocking file, please regenerate: " + getPath()); } } private void deserializeThrowable(Node r) { // TODO Auto-generated method stub } private StackTraceElement getStackTraceElement() { StackTraceElement last = null; StackTraceElement[] stackTraceElements = Thread.currentThread() .getStackTrace(); for (StackTraceElement trace : stackTraceElements) { try { if (Class.forName(trace.getClassName()) .getMethod(trace.getMethodName()) .isAnnotationPresent(Test.class)) last = trace; if (Class.forName(trace.getClassName()) .getMethod(trace.getMethodName()) .isAnnotationPresent(After.class)) last = trace; if (Class.forName(trace.getClassName()) .getMethod(trace.getMethodName()) .isAnnotationPresent(Before.class)) last = trace; if (Class.forName(trace.getClassName()) .getMethod(trace.getMethodName()) .isAnnotationPresent(AfterClass.class)) last = trace; if (Class.forName(trace.getClassName()) .getMethod(trace.getMethodName()) .isAnnotationPresent(BeforeClass.class)) last = trace; } catch (Exception e) { } } return last; } private File getFile(boolean write) throws IOException { String path = getPath(); boolean reset = !seen.contains(path); seen.add(path); File file = new File(path); File parentFolder = new File(file.getParent()); parentFolder.mkdirs(); if (write && reset && file.exists()) file.delete(); if (!file.exists()) { file.createNewFile(); FileOutputStream st = new FileOutputStream(file); st.write("<?xml version=\"1.0\"?>\n<responses>\n</responses>" .getBytes("UTF-8")); st.flush(); st.close(); } return file; } private String getEndpointName() { return endpointName; } private String getPath() { StackTraceElement trace = getStackTraceElement(); String basePath = System.getProperty("user.dir"); String fullClassName = trace.getClassName() .replace(".", File.separator); String className = fullClassName.substring(fullClassName .lastIndexOf(File.separatorChar)); String packageName = fullClassName.substring(0, fullClassName.lastIndexOf(File.separatorChar)); String methodName = trace.getMethodName(); String endpointName = getEndpointName(); String path = new StringBuilder(basePath).append(File.separator) .append("src").append(File.separator).append("test").append(File.separator).append("resources").append(File.separator).append(packageName) .append(File.separator).append("mockData") .append(File.separator).append(endpointName) .append(className).append("_") .append(methodName).append(".mock").toString(); return path; } private String getResource() { StackTraceElement trace = getStackTraceElement(); String methodName = trace.getMethodName(); String fullClassName = trace.getClassName() .replace(".", "/"); String packageName = fullClassName.substring(0, fullClassName.lastIndexOf("/")); String className = fullClassName.substring(fullClassName .lastIndexOf("/")); String resource = new StringBuilder("/").append(packageName) .append("/").append("mockData") .append("/").append(endpointName).append(className).append("_") .append(methodName).append(".mock").toString(); return resource; } }