zutil.net.ws.soap.SOAPHttpPage.java Source code

Java tutorial

Introduction

Here is the source code for zutil.net.ws.soap.SOAPHttpPage.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Ziver Koc
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package zutil.net.ws.soap;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.xml.sax.SAXException;
import zutil.converter.Converter;
import zutil.log.LogUtil;
import zutil.net.http.HttpHeader;
import zutil.net.http.HttpPage;
import zutil.net.http.HttpPrintStream;
import zutil.net.ws.*;
import zutil.net.ws.WSReturnObject.WSValueName;
import zutil.parser.Base64Encoder;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * This is an HTTPPage for the HTTPServer that 
 * handles soap messages.
 * 
 * TODO: Header should be variables not methods
 * TODO: Read WSReturnObjects as input parameter
 * TODO: Ability to have multiple arrays of same WSReturnObject
 * 
 * Features:
 * Input:
 * <br>-int
 * <br>-double
 * <br>-float
 * <br>-char
 * <br>-String
 * <br>-byte[]
 * <br>-And the Wrappers Classes except for Byte
 * 
 * Output:
 * <br>-WSReturnObject
 * <br>-byte[]
 * <br>-int
 * <br>-double
 * <br>-float
 * <br>-char
 * <br>-String
 * <br>-Arrays of Output
 * <br>-And the Wrappers Classes except for Byte
 * 
 * @author Ziver
 */
public class SOAPHttpPage implements HttpPage {
    private static final Logger logger = LogUtil.getLogger();

    /** The object that the functions will be invoked from **/
    private WebServiceDef wsDef;
    /** This instance of the web service class is used if session is disabled **/
    private WSInterface ws;
    /** Session enabled **/
    private boolean session_enabled;

    public SOAPHttpPage(WebServiceDef wsDef) {
        this.wsDef = wsDef;
        this.session_enabled = false;
    }

    /**
     * Enables session support, if enabled then a new instance 
     * of the SOAPInterface will be created, if disabled then 
     * only the given object will be used as an static interface.
     * Default is false.
     * 
     * @param enabled is if session should be enabled
     */
    public void enableSession(boolean enabled) {
        this.session_enabled = enabled;
    }

    /**
     * Sets the web service object to the specified one.
     * Only used when session is disabled
     */
    public void setObject(WSInterface obj) {
        this.ws = obj;
    }

    public void respond(HttpPrintStream out, HttpHeader headers, Map<String, Object> session,
            Map<String, String> cookie, Map<String, String> request) {

        try {
            // Read http body
            StringBuilder data = null;
            String contentType = headers.getHeader("Content-Type");
            if (contentType != null && (contentType.contains("application/soap+xml")
                    || contentType.contains("text/xml") || contentType.contains("text/plain"))) {
                int post_data_length = Integer.parseInt(headers.getHeader("Content-Length"));
                BufferedReader in = new BufferedReader(new InputStreamReader(headers.getInputStream()));
                data = new StringBuilder(post_data_length);
                for (int i = 0; i < post_data_length; i++) {
                    data.append((char) in.read());
                }
            }

            // Response
            out.setHeader("Content-Type", "text/xml");
            out.flush();

            WSInterface obj = null;
            if (session_enabled) {
                if (session.containsKey("SOAPInterface"))
                    obj = (WSInterface) session.get("SOAPInterface");
                else {
                    obj = wsDef.newInstance();
                    session.put("SOAPInterface", obj);
                }
            } else {
                if (ws == null)
                    ws = wsDef.newInstance();
                obj = ws;
            }

            Document document = genSOAPResponse((data != null ? data.toString() : ""), obj);

            OutputFormat format = OutputFormat.createPrettyPrint();
            XMLWriter writer = new XMLWriter(out, format);
            writer.write(document);

            // DEBUG
            if (logger.isLoggable(Level.FINEST)) {
                System.out.println("********** Request");
                System.out.println(request);
                System.out.println("********** Response");
                writer = new XMLWriter(System.out, format);
                writer.write(document);
            }
        } catch (Exception e) {
            logger.log(Level.WARNING, "Unhandled request", e);
        }
    }

    /**
     * Generates a soap response for the given XML
     * 
     * @param      xml    is the XML request
     * @return             a Document with the response
     */
    public Document genSOAPResponse(String xml) {
        try {
            WSInterface obj = null;
            if (ws == null)
                ws = wsDef.newInstance();
            obj = ws;

            return genSOAPResponse(xml, obj);
        } catch (Exception e) {
            logger.log(Level.WARNING, "Exception in SOAP generation", e);
        }
        return null;
    }

    protected Document genSOAPResponse(String xml, WSInterface obj) {
        Document document = DocumentHelper.createDocument();
        Element envelope = document.addElement("soap:Envelope");
        try {
            envelope.addNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/");
            envelope.addNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance");
            envelope.addNamespace("xsd", "http://www.w3.org/2001/XMLSchema");

            Element body = envelope.addElement("soap:Body");
            try {
                Element request = getXMLRoot(xml);
                if (request == null)
                    return document;
                // Header
                if (request.element("Header") != null) {
                    Element header = envelope.addElement("soap:Header");
                    prepareInvoke(obj, request.element("Header"), header);
                }

                // Body
                if (request.element("Body") != null) {
                    prepareInvoke(obj, request.element("Body"), body);
                }
            } catch (Throwable e) {
                body.clearContent();
                Element fault = body.addElement("soap:Fault");
                // The fault source
                if (e instanceof SOAPException || e instanceof SAXException || e instanceof DocumentException)
                    fault.addElement("faultcode").setText("soap:Client");
                else
                    fault.addElement("faultcode").setText("soap:Server");
                // The fault message
                if (e.getMessage() == null || e.getMessage().isEmpty())
                    fault.addElement("faultstring").setText("" + e.getClass().getSimpleName());
                else
                    fault.addElement("faultstring").setText("" + e.getMessage());
                logger.log(Level.WARNING, "Caught exception from SOAP Class", e);
            }
        } catch (Exception e) {
            logger.log(Level.WARNING, "Exception in SOAP generation", e);
        }

        return document;
    }

    /**
     * Converts an String XML to an Element
     * 
     * @param       xml       is the string XML
     * @return                the XML root Element
     */
    protected static Element getXMLRoot(String xml) throws DocumentException {
        if (xml != null && !xml.isEmpty()) {
            Document document = DocumentHelper.parseText(xml);
            return document.getRootElement();
        }
        return null;
    }

    /**
     * Takes an XML Element and invokes all the it's child elements as methods.
     * 
     * @param       obj            is the object that the methods will be called from
     * @param       requestRoot    is the Element where the children lies
     * @param       responseRoot   is the root element of the response
     */
    @SuppressWarnings("unchecked")
    private void prepareInvoke(WSInterface obj, Element requestRoot, Element responseRoot) throws Throwable {
        Iterator<Element> it = requestRoot.elementIterator();
        while (it.hasNext()) {
            Element e = it.next();
            if (wsDef.hasMethod(e.getQName().getName())) {
                WSMethodDef m = wsDef.getMethod(e.getQName().getName());
                Object[] params = new Object[m.getInputCount()];

                // Get the parameter values
                for (int i = 0; i < m.getInputCount(); i++) {
                    WSParameterDef param = m.getInput(i);
                    if (e.element(param.getName()) != null) {
                        params[i] = Converter.fromString(e.element(param.getName()).getTextTrim(),
                                param.getParamClass());
                    }
                }

                // Invoke
                Object ret = m.invoke(obj, params);

                // generate response XML
                if (m.getOutputCount() > 0) {
                    Element response = responseRoot.addElement("");
                    response.addNamespace("m", m.getNamespace());
                    response.setName("m:" + m.getName() + "Response");

                    if (ret instanceof WSReturnObject) {
                        Field[] f = ret.getClass().getFields();
                        for (int i = 0; i < m.getOutputCount(); i++) {
                            WSParameterDef param = m.getOutput(i);
                            generateSOAPXMLForObj(response, ((WSReturnObject) ret).getValue(f[i]), param.getName());
                        }
                    } else {
                        generateSOAPXMLForObj(response, ret, m.getOutput(0).getName());
                    }
                }
            } else {
                throw new NoSuchMethodException("Unable to find method: " + e.getQName().getName() + "!");
            }
        }
    }

    /**
     * Generates a XML Element for a given Object. This method can
     * handle return values as XML Elements, WSReturnObject and
     * Java basic data types.
     * 
     * @param      root       is the parent Element
     * @param       obj       is the object that is the return value
     * @param       elementName       is the name of the parent Element
     */
    protected static void generateSOAPXMLForObj(Element root, Object obj, String elementName)
            throws IllegalArgumentException, IllegalAccessException {
        if (obj == null)
            return;
        if (byte[].class.isAssignableFrom(obj.getClass())) {
            Element valueE = root.addElement(elementName);
            valueE.addAttribute("type", "xsd:" + getSOAPClassName(obj.getClass()));
            String tmp = Base64Encoder.encode((byte[]) obj);
            tmp = tmp.replaceAll("\\s", "");
            valueE.setText(tmp);
        }
        // return an array
        else if (obj.getClass().isArray()) {
            Element array = root.addElement((elementName.equals("element") ? "Array" : elementName));
            String arrayType = "xsd:" + getSOAPClassName(obj.getClass());
            arrayType = arrayType.replaceFirst("\\[\\]", "[" + Array.getLength(obj) + "]");

            array.addAttribute("type", "soap:Array");
            array.addAttribute("soap:arrayType", arrayType);
            for (int i = 0; i < Array.getLength(obj); i++) {
                generateSOAPXMLForObj(array, Array.get(obj, i), "element");
            }
        } else {
            Element objectE = root.addElement(elementName);
            if (obj instanceof Element)
                objectE.add((Element) obj);
            else if (obj instanceof WSReturnObject) {
                Field[] fields = obj.getClass().getFields();
                for (int i = 0; i < fields.length; i++) {
                    WSValueName tmp = fields[i].getAnnotation(WSValueName.class);
                    String name;
                    if (tmp != null)
                        name = tmp.value();
                    else
                        name = "field" + i;
                    generateSOAPXMLForObj(objectE, fields[i].get(obj), name);
                }
            } else {
                objectE.addAttribute("type", "xsd:" + getSOAPClassName(obj.getClass()));
                objectE.addText("" + obj);
            }
        }
    }

    protected static String getSOAPClassName(Class<?> c) {
        Class<?> cTmp = getClass(c);
        if (byte[].class.isAssignableFrom(c)) {
            return "base64Binary";
        } else if (WSReturnObject.class.isAssignableFrom(cTmp)) {
            return c.getSimpleName();
        } else {
            String ret = c.getSimpleName().toLowerCase();

            if (cTmp == Integer.class)
                ret = ret.replaceAll("integer", "int");
            else if (cTmp == Character.class)
                ret = ret.replaceAll("character", "char");

            return ret;
        }
    }

    protected static Class<?> getClass(Class<?> c) {
        if (c != null && c.isArray()) {
            return getClass(c.getComponentType());
        }
        return c;
    }
}