Package com.dbxml.labrador.xmlrpc

Source Code of com.dbxml.labrador.xmlrpc.XMLRPCHandler$State

package com.dbxml.labrador.xmlrpc;

/*
* The dbXML Labrador Software License, Version 1.0
*
*
* Copyright (c) 2003 The dbXML Group, L.L.C.  All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in
*    the documentation and/or other materials provided with the
*    distribution.
*
* 3. The end-user documentation included with the redistribution,
*    if any, must include the following acknowledgment:
*       "This product includes software developed by The
*        dbXML Group, L.L.C. (http://www.dbxml.com/)."
*    Alternately, this acknowledgment may appear in the software
*    itself, if and wherever such third-party acknowledgments normally
*    appear.
*
* 4. The names "Labrador" and "dbXML Group" must not be used to
*    endorse or promote products derived from this software without
*    prior written permission. For written permission, please contact
*    info@dbxml.com
*
* 5. Products derived from this software may not be called "Labrador",
*    nor may "Labrador" appear in their name, without prior written
*    permission of The dbXML Group, L.L.C..
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED.  IN NO EVENT SHALL THE DBXML GROUP, L.L.C. OR ITS
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* $Id: XMLRPCHandler.java,v 1.23 2004/07/28 17:08:42 bradford Exp $
*/

import com.dbxml.labrador.*;
import java.io.*;
import java.util.*;

import com.dbxml.labrador.exceptions.RequestException;
import com.dbxml.labrador.http.HTTP;
import com.dbxml.labrador.types.ArrayConversions;
import com.dbxml.labrador.types.Types;
import com.dbxml.labrador.types.Variant;
import com.dbxml.labrador.util.Base64;
import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.w3c.dom.Node;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.DefaultHandler;

/**
* XML-RPC Handler.
* <br /><br />
* @todo *Nothing* here has been optimized.
*
* There's a bunch of automatic conversion going on that can be
* unloaded into type-specific routines for performance.  Also,
* most of the conversion routines should probably be moved into
* a class in the util package.
*/

public final class XMLRPCHandler implements Handler {
   private static final String PROTOCOL = "XML-RPC";
   private static final String SERVER_ERROR = "500";

   public static final String PREFIX = "/xmlrpc/";

   private static final SAXParserFactory saxFactory = SAXParserFactory.newInstance();
   private static final SimpleDateFormat IDF = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss", new DateFormatSymbols(Locale.US));

   private static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
   private static final String METHOD_CALL = "methodCall";
   private static final String METHOD_NAME = "methodName";
   private static final String MEMBER = "member";
   private static final String NAME = "name";
   private static final String VALUE = "value";
   private static final String PARAMS = "params";
   private static final String PARAM = "param";
   private static final String DATA = "data";

   private static final String NIL = "nil";
   private static final String I4 = "i4";
   private static final String INT = "int";
   private static final String BOOLEAN = "boolean";
   private static final String STRING = "string";
   private static final String DOUBLE = "double";
   private static final String DATETIME = "dateTime.iso8601";
   private static final String BASE64 = "base64";
   private static final String STRUCT = "struct";
   private static final String ARRAY = "array";

   private static final String METHOD_RESPONSE = "methodResponse";

   private static final String FAULT = "fault";
   private static final String FAULT_CODE = "faultCode";
   private static final String FAULT_STRING = "faultString";

   private static final int STATE_INVALID = 0;
   private static final int STATE_FAULT = 1;
   private static final int STATE_START = 2;
   private static final int STATE_METHOD_CALL = 3;
   private static final int STATE_METHOD_NAME = 4;
   private static final int STATE_MEMBER = 5;
   private static final int STATE_NAME = 6;
   private static final int STATE_VALUE = 7;
   private static final int STATE_PARAMS = 8;
   private static final int STATE_PARAM = 9;
   private static final int STATE_DATA = 10;
   private static final int STATE_I4 = 11;
   private static final int STATE_INT = 12;
   private static final int STATE_BOOLEAN = 13;
   private static final int STATE_STRING = 14;
   private static final int STATE_DOUBLE = 15;
   private static final int STATE_DATETIME = 16;
   private static final int STATE_BASE64 = 17;
   private static final int STATE_STRUCT = 18;
   private static final int STATE_ARRAY = 19;
   private static final int STATE_NIL = 20;
   private static final int STATE_PCDATA = 21;

   private static final State[] states = {
      new State(STATE_INVALID),
      new State(STATE_FAULT),
      new State(STATE_START, new int[]{STATE_METHOD_CALL}),
      new State(STATE_METHOD_CALL, METHOD_CALL,
                new int[]{STATE_METHOD_NAME, STATE_PARAMS}),
      new State(STATE_METHOD_NAME, METHOD_NAME, new int[]{STATE_PCDATA}),
      new State(STATE_MEMBER, MEMBER,
                new int[]{STATE_NAME, STATE_VALUE}),
      new State(STATE_NAME, NAME, new int[]{STATE_PCDATA}),
      new State(STATE_VALUE, VALUE,
                new int[]{STATE_I4, STATE_INT, STATE_BOOLEAN, STATE_STRING,
                          STATE_DOUBLE, STATE_DATETIME, STATE_BASE64,
                          STATE_STRUCT, STATE_ARRAY, STATE_PCDATA, STATE_NIL}),
      new State(STATE_PARAMS, PARAMS, new int[]{STATE_PARAM}),
      new State(STATE_PARAM, PARAM, new int[]{STATE_VALUE}),
      new State(STATE_DATA, DATA, new int[]{STATE_VALUE}),
      new State(STATE_I4, I4, new int[]{STATE_PCDATA}),
      new State(STATE_INT, INT, new int[]{STATE_PCDATA}),
      new State(STATE_BOOLEAN, BOOLEAN, new int[]{STATE_PCDATA}),
      new State(STATE_STRING, STRING, new int[]{STATE_PCDATA}),
      new State(STATE_DOUBLE, DOUBLE, new int[]{STATE_PCDATA}),
      new State(STATE_DATETIME, DATETIME, new int[]{STATE_PCDATA}),
      new State(STATE_BASE64, BASE64, new int[]{STATE_PCDATA}),
      new State(STATE_STRUCT, STRUCT, new int[]{STATE_MEMBER}),
      new State(STATE_ARRAY, ARRAY, new int[]{STATE_DATA}),
      new State(STATE_NIL, NIL),
      new State(STATE_PCDATA)
   };

   public XMLRPCHandler() {
   }

   public String getProtocol() {
      return PROTOCOL;
   }

   public boolean isRequestValid(Request request) {
      return request.hasContent() && request.getPath().startsWith(PREFIX);
   }

   public ID getInstanceID(Request request) {
      return new ID(request.getPath().substring(PREFIX.length() - 1));
   }

   public void processRequest(Request request, Response response, Instance instance) throws RequestException {
      try {
         InputStreamReader isr = new InputStreamReader(request.getInputStream(), "UTF8");
         BufferedReader br = new BufferedReader(isr, 4096);
         InputSource is = new InputSource(br);

         DefaultHandler handler = new SAXHandler(request, response, instance);
         SAXParser sp = saxFactory.newSAXParser();
         sp.parse(is, handler);
      }
      catch ( Exception e ) {
         processError(response, SERVER_ERROR, e.getMessage());
      }
   }

   public void processError(Response response, String code, String message) {
      try {
         response.setHeader(HTTP.HEADER_CONTENT_TYPE, Headers.TYPE_TEXT_XML);

         OutputStream os = response.getOutputStream();
         OutputStreamWriter osw = new OutputStreamWriter(os, "UTF8");
         BufferedWriter bw = new BufferedWriter(osw, 4096);
         PrintWriter pw = new PrintWriter(bw);

         pw.println(XML_HEADER);
         pw.println("<"+METHOD_RESPONSE+"><"+FAULT+">");

         Map m = new HashMap();
         m.put(FAULT_CODE, code);
         m.put(FAULT_STRING, message);
         writeVariant(pw, new Variant(m));

         pw.println("</"+FAULT+"></"+METHOD_RESPONSE+">");
         pw.flush(); // Is this needed?
         pw.close(); // Is this needed?
         response.close();
      }
      catch ( Exception e ) {
         /** @todo What do we do here? */
      }
   }

   protected DefaultHandler createSAXHandler(Request request, Response response, Instance instance) {
      return new SAXHandler(request, response, instance);
   }

   private void writeEscapedString(PrintWriter pw, String s) throws IOException {
      // Only Ampersands (&) and Less-Thans (<) really need to be
      // encoded in order to fool an XML parser.
      char[] c = s.toCharArray();
      int start = 0;
      int pos = 0;
      for ( ; pos < c.length; pos++ ) {
         switch ( c[pos] ) {
            case '<':
               if ( start < pos )
                  pw.write(c, start, pos - start);
               pw.write("&lt;");
               start = pos + 1;
               break;

            case '>':
               if ( pos > 1 && c[pos-1] == ']' && c[pos-2] == ']' ) {
                  if ( start < pos )
                     pw.write(c, start, pos - start);
                  pw.write("&gt;");
                  start = pos + 1;
               }
               break;

            case '&':
               if ( start < pos )
                  pw.write(c, start, pos - start);
               pw.write("&amp;");
               start = pos + 1;
               break;
         }
      }
      if ( start < pos )
         pw.write(c, start, pos - start);
   }

   private void writeValue(PrintWriter pw, Object o) throws IOException {
      if ( o != null && o.getClass().isArray() )
         writeArray(pw, o);
      else
         writeVariant(pw, new Variant(o));
   }

   private void writeArray(PrintWriter pw, Object o) throws IOException {
      if ( Types.typeOf(o) == Types.BYTE ) {
         // Byte Arrays are converted to Base64
         try {
            byte[] b = (byte[])o;
            ByteArrayInputStream bis = new ByteArrayInputStream(b);
            ByteArrayOutputStream bos = new ByteArrayOutputStream((b.length * 4) / 3);
            Base64.encode(bis, bos);

            pw.println("<"+VALUE+"><"+BASE64+">");
            pw.println(new String(bos.toByteArray()));
            pw.println("</"+BASE64+"></"+VALUE+">");
         }
         catch ( Exception e ) {
            /** @todo This */
            e.printStackTrace(System.err);
         }
      }
      else {
         List l = ArrayConversions.toList(o);
         writeVariant(pw, new Variant(l));
      }
   }

   private void writeVariant(PrintWriter pw, Variant v) throws IOException {
      if ( v.isNull() ) {
         pw.println("<"+VALUE+"><"+NIL+"/></"+VALUE+">");
         return;
      }

      pw.println("<"+VALUE+">");

      switch ( v.getType() ) {
         case Types.VOID:
         case Types.EMPTY:
            pw.println("<"+NIL+"/>");
            break;

         case Types.BOOLEAN:
            if ( v.getBoolean() )
               pw.println("<"+BOOLEAN+">1</"+BOOLEAN+">");
            else
               pw.println("<"+BOOLEAN+">0</"+BOOLEAN+">");
            break;

         case Types.BYTE:
         case Types.SHORT:
         case Types.INT:
            pw.println("<"+I4+">" + v.toString() + "</"+I4+">");
            break;

         case Types.LONG:
            pw.println("<"+INT+">" + v.toString() + "</"+INT+">");
            break;

         case Types.DOUBLE:
         case Types.FLOAT:
            pw.println("<"+DOUBLE+">" + v.toString() + "</"+DOUBLE+">");
            break;

         case Types.STRING:
         case Types.CHAR:
            pw.print("<"+STRING+">");
            writeEscapedString(pw, v.toString());
            pw.println("</"+STRING+">");
            break;

         case Types.NODE:
            pw.println("<"+STRING+">");
            // If XML-RPC actually supported an XML type, we could
            // serialize the Document directly to the stream.  Instead,
            // we have to use a home grown serializer.
            try {
               XMLDocWriter.write((Node)v.getObject(), pw);
            }
            catch ( Exception e ) {
               /** @todo This */
               e.printStackTrace(System.err);
            }
            pw.println();
            pw.println("</"+STRING+">");
            break;

         case Types.LIST:
            pw.println("<"+ARRAY+"><"+DATA+">");
            Iterator l = v.getList().iterator();
            while ( l.hasNext() )
               writeVariant(pw, new Variant(l.next()));
            pw.println("</"+DATA+"></"+ARRAY+">");
            break;

         case Types.MAP:
            pw.println("<"+STRUCT+">");
            Map m = v.getMap();
            Iterator k = m.keySet().iterator();
            while ( k.hasNext() ) {
               pw.println("<"+MEMBER+">");
               Object key = k.next();
               pw.print("<"+NAME+">");
               writeEscapedString(pw, key.toString());
               pw.println("</"+NAME+">");
               writeValue(pw, m.get(key));
               pw.println("</"+MEMBER+">");
            }
            pw.println("</"+STRUCT+">");
            break;

         case Types.DATE:
            pw.print("<"+DATETIME+">");
            pw.print(IDF.format(v.getDate()));
            pw.println("</"+DATETIME+">");
            break;

         case Types.OBJECT:
            writeValue(pw, v.getObject());
            break;
      }
      pw.println("</"+VALUE+">");
   }


   /**
    * SAXHandler
    */

   private class SAXHandler extends DefaultHandler {
      public Request request;
      public Response response;
      public Instance instance;

      public String methodName;
      public List params;

      public Stack stack = new Stack();
      public StackInfo info = new StackInfo(STATE_START);

      public SAXHandler(Request request, Response response, Instance instance) {
         this.request = request;
         this.response = response;
         this.instance = instance;
      }

      public void startElement(String namespaceURI, String localName, String qName, Attributes atts) {
         int newState = STATE_INVALID;

         State s = states[info.state];
         if ( s.states != null )
            for ( int i = 0; i < s.states.length; i++ )
               if ( qName.equals(states[s.states[i]].element) )
                  newState = s.states[i];

         switch ( newState ) {
            case STATE_METHOD_CALL:
               {
                  methodName = null;
                  params = null;
                  stack.push(info);
                  info = new StackInfo(newState);
                  break;
               }

            case STATE_STRUCT:
               {
                  stack.push(info);
                  info = new StackInfo(newState, new HashMap());
                  break;
               }

            case STATE_MEMBER:
               {
                  stack.push(info);
                  info = new StackInfo(newState, new NameValue());
                  break;
               }

            case STATE_ARRAY:
               {
                  stack.push(info);
                  info = new StackInfo(newState, new ArrayList());
                  break;
               }

            case STATE_INVALID:
               {
                  StringBuffer sb = new StringBuffer("Invalid Parse State: " + qName);
                  if ( s.element != null )
                     sb.append(" in " + s.element);
                  processError(response, SERVER_ERROR, sb.toString());
                  break;
               }

            case STATE_FAULT:
               {
                  break;
               }

            default:
               {
                  stack.push(info);
                  info = new StackInfo(newState);
                  break;
               }
         }
      }

      public void endElement(String namespaceURI, String localName, String qName) {
         Object obj;
         // If we've just been parsing a String, pop the state
         boolean wasString = info.state == STATE_PCDATA;
         if ( wasString ) {
            obj = info.sb.toString();
            info = (StackInfo)stack.pop();
         }
         else
            obj = info.obj;

         switch ( info.state ) {
            case STATE_METHOD_NAME:
               {
                  obj = ((String)obj).trim();
                  methodName = (String)obj;
                  break;
               }

            case STATE_MEMBER:
               {
                  StackInfo struct = (StackInfo)stack.peek();
                  NameValue nv = (NameValue)obj;
                  ((Map)struct.obj).put(nv.name, nv.value);
                  obj = struct.obj;
                  break;
               }

            case STATE_PARAM:
               {
                  if ( params == null )
                     params = new ArrayList();
                  params.add(obj);
                  break;
               }

            case STATE_DATA:
               {
                  StackInfo array = (StackInfo)stack.peek();
                  ((List)array.obj).add(obj);
                  break;
               }

            case STATE_NAME:
               {
                  StackInfo struct = (StackInfo)stack.peek();
                  NameValue nv = (NameValue)struct.obj;
                  nv.name = (String)obj;
                  obj = nv;
                  break;
               }

            case STATE_VALUE:
               {
                  StackInfo struct = (StackInfo)stack.peek();
                  if ( struct.state == STATE_MEMBER ) {
                     NameValue nv = (NameValue)struct.obj;
                     nv.value = (String)obj;
                     obj = nv;
                  }
                  break;
               }

            case STATE_INT:
               {
                  obj = new Integer(((String)obj).trim());
                  break;
               }

            case STATE_I4:
               {
                  obj = new Long(((String)obj).trim());
                  break;
               }

            case STATE_BOOLEAN:
               {
                  obj = new Boolean(((String)obj).trim().equalsIgnoreCase("1"));
                  break;
               }

            case STATE_DOUBLE:
               {
                  obj = new Double(((String)obj).trim());
                  break;
               }

            case STATE_BASE64:
               {
                  try {
                     // Base-64 is converted into a byte array
                     String s = ((String)obj).trim();
                     StringBufferInputStream sbis = new StringBufferInputStream(s);
                     ByteArrayOutputStream bos = new ByteArrayOutputStream((s.length() * 3) / 4);
                     Base64.decode(sbis, bos);
                     obj = bos.toByteArray();
                  }
                  catch ( Exception e ) {
                     /** @todo This */
                     e.printStackTrace(System.err);
                  }
                  break;
               }

            case STATE_DATETIME:
               {
                  try {
                     obj = IDF.parse(((String)obj).trim());
                  }
                  catch ( Exception e ) {
                     /** @todo This */
                     e.printStackTrace(System.err);
                  }
                  break;
               }

            case STATE_NIL:
               {
                  obj = null;
                  break;
               }
         }

         info = (StackInfo)stack.pop();
         info.obj = obj;
      }

      public void characters(char ch[], int start, int length) {
         String val = new String(ch, start, length);

         if ( info.state != STATE_PCDATA && val.trim().length() > 0 ) {
            boolean validState = false;

            State s = states[info.state];
            if ( s.states != null )
               for ( int i = 0; i < s.states.length; i++ )
                  if ( s.states[i] == STATE_PCDATA )
                     validState = true;

            if ( validState ) {
               stack.push(info);
               info = new StackInfo(STATE_PCDATA);
            }
            else {
               stack.push(new StackInfo(STATE_FAULT));
               StringBuffer sb = new StringBuffer("Invalid Parse State");
               if ( s.element != null )
                  sb.append(": " + s.element);
               processError(response, SERVER_ERROR, sb.toString());
            }
         }

         if ( info.state == STATE_PCDATA ) {
            if ( info.sb == null )
               info.sb = new StringBuffer(length);
            info.sb.append(val);
         }
      }

      public void endDocument() {
         if ( info.state != STATE_FAULT ) {
            try {
               instance.setMethodName(methodName);
               for ( int i = 0; params != null && i < params.size(); i++ )
                  instance.setParameter(i, params.get(i));

               Object o = instance.invoke();

               response.setHeader(HTTP.HEADER_CONTENT_TYPE, Headers.TYPE_TEXT_XML);

               OutputStream os = response.getOutputStream();
               OutputStreamWriter osw = new OutputStreamWriter(os, "UTF8");
               BufferedWriter bw = new BufferedWriter(osw, 4096);
               PrintWriter pw = new PrintWriter(bw);

               pw.println(XML_HEADER);
               pw.println("<"+METHOD_RESPONSE+"><"+PARAMS+"><"+PARAM+">");

               writeValue(pw, o);

               pw.println("</"+PARAM+"></"+PARAMS+"></"+METHOD_RESPONSE+">");
               pw.flush(); // Is this needed?
               pw.close(); // Is this needed?
               response.close();
            }
            catch ( Exception e ) {
               processError(response, SERVER_ERROR, e.getMessage());
            }
         }
      }
   }

   /**
    * StackInfo
    */

   private class StackInfo {
      public int state;
      public Object obj;
      public StringBuffer sb;

      public StackInfo() {
      }

      public StackInfo(int state) {
         this.state = state;
      }

      public StackInfo(int state, Object obj) {
         this.state = state;
         this.obj = obj;
      }

      public int getState() {
         return state;
      }

      public Object getObject() {
         return obj;
      }

      public StringBuffer getStringBuffer() {
         return sb;
      }
   }


   /**
    * NameValue
    */

   private class NameValue {
      public String name;
      public Object value;

      public NameValue() {
      }

      public NameValue(String name, Object value) {
         this.name = name;
         this.value = value;
      }

      public String getName() {
         return name;
      }

      public Object getValue() {
         return value;
      }
   }


   /**
    * State
    */

   private static class State {
      public int id;
      public String element;
      public int[] states;

      public State() {
      }

      public State(int id, String element, int[] states) {
         this.id = id;
         this.element = element;
         this.states = states;
      }

      public State(int id, int[] states) {
         this.id = id;
         this.states = states;
      }

      public State(int id, String element) {
         this.id = id;
         this.element = element;
      }

      public State(int id) {
         this.id = id;
      }

      public int getID() {
         return id;
      }

      public String getElement() {
         return element;
      }

      public int[] getStates() {
         return states;
      }
   }
}
TOP

Related Classes of com.dbxml.labrador.xmlrpc.XMLRPCHandler$State

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.