package com.dbxml.db.client.xmlrpc;

/*
 * dbXML - Native XML Database
 * Copyright (C) 1999-2004  The dbXML Group, L.L.C.
 *
 * This program 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 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: ConnectionManager.java,v 1.7 2004/02/13 01:42:31 bradford Exp $
 */

import com.dbxml.db.client.dbXMLClient;
import com.dbxml.db.server.dbXML;
import com.dbxml.util.dbXMLException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.apache.xmlrpc.XmlRpc;
import org.apache.xmlrpc.XmlRpcClient;
import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.secure.SecureXmlRpcClient;

/**
 * ConnectionManager manages XML-RPC connections
 */

public abstract class ConnectionManager {
   private static final String[] EmptyStrings = new String[0];
   private static final List EmptyList = new ArrayList(0);

   private static final String ResponseMsg = "Server returned HTTP response code: ";
   private static final Map ResponseCodes = new HashMap();
   static {
      ResponseCodes.put("100", "Continue");
      ResponseCodes.put("101", "Switching Protocols");

      ResponseCodes.put("200", "OK");
      ResponseCodes.put("201", "Created");
      ResponseCodes.put("202", "Accepted");

      ResponseCodes.put("203", "Non-Authoritative Information");
      ResponseCodes.put("204", "No Content");
      ResponseCodes.put("205", "Reset Content");
      ResponseCodes.put("206", "Partial Content");

      ResponseCodes.put("300", "Multiple Choices");
      ResponseCodes.put("301", "Moved Permanently");
      ResponseCodes.put("302", "Found");
      ResponseCodes.put("303", "See Other");
      ResponseCodes.put("304", "Not Modified");
      ResponseCodes.put("305", "Use Proxy");
      ResponseCodes.put("307", "Temporary Redirect");

      ResponseCodes.put("400", "Bad Request");
      ResponseCodes.put("401", "Authorization Required");
      ResponseCodes.put("402", "Payment Required");
      ResponseCodes.put("403", "Forbidden");
      ResponseCodes.put("404", "Not Found");
      ResponseCodes.put("405", "Method Not Allowed");
      ResponseCodes.put("406", "Not Acceptable");
      ResponseCodes.put("407", "Proxy Authentication Required");
      ResponseCodes.put("408", "Request Timeout");
      ResponseCodes.put("409", "Conflict");
      ResponseCodes.put("410", "Gone");
      ResponseCodes.put("411", "Length Required");
      ResponseCodes.put("412", "Precondition Failed");
      ResponseCodes.put("413", "Request Entity Too Large");
      ResponseCodes.put("414", "URI Too Long");
      ResponseCodes.put("415", "Unsupported Media Type");
      ResponseCodes.put("416", "Requested Range Not Satisfiable");
      ResponseCodes.put("417", "Expectation Failed");

      ResponseCodes.put("500", "Internal Server Error");
      ResponseCodes.put("501", "Not Implemented");
      ResponseCodes.put("502", "Bad Gateway");
      ResponseCodes.put("503", "Service Unavailable");
      ResponseCodes.put("504", "Gateway Timeout");
      ResponseCodes.put("505", "HTTP Version Not Supported");
   }

   public static final String CONNECTION = "connection";
   public static final String SECURE = "secure";
   public static final String STANDARD = "standard";

   private XmlRpcClient client;
   private String clientPath;

   private Map props;

   static {
      try {
         XmlRpc.setEncoding("UTF8");
         XmlRpc.setKeepAlive(true);
      }
      catch ( Exception e ) {
         e.printStackTrace(System.err);
         System.exit(1);
      }
   }

   public ConnectionManager() {
      props = new HashMap();
      props.put(dbXMLClient.HOST, dbXML.DEFAULT_HOST);
      props.put(dbXMLClient.PORT, Integer.toString(dbXML.DEFAULT_PORT));
   }

   public ConnectionManager(Map props) {
      this.props = props;
   }

   public ConnectionManager(dbXMLClient dbxmlClient) {
      this.props = dbxmlClient.getProperties();
   }

   public final void setProperty(String name, String value) {
      props.put(name, value);
   }

   public final String getProperty(String name) {
      return (String)props.get(name);
   }

   public final String[] listProperties() {
      return (String[])props.keySet().toArray(EmptyStrings);
   }

   public final Map getProperties() {
      return props;
   }

   public final void setClientPath(String clientPath) {
      this.clientPath = clientPath;
   }

   public final String getClientPath() {
      return clientPath;
   }

   public final XmlRpcClient getXmlRpcClient() throws MalformedURLException {
      if ( client == null ) {
         String clientPath = this.clientPath;
         if ( !clientPath.startsWith("/") )
            clientPath = '/' + clientPath;

         String connection = getProperty(CONNECTION);

         String host = getProperty(dbXMLClient.HOST);
         if ( host == null )
            host = getProperty(dbXMLClient.ALT_HOST);
         String port = getProperty(dbXMLClient.PORT);
         if ( port == null )
            port = getProperty(dbXMLClient.ALT_PORT);

         String baseURI ="http://" + host + ":" + port + "/xmlrpc";
         if ( connection != null ) {
            if ( connection.equals(SECURE) ) {
               baseURI ="https://" + host + ":" + port + "/xmlrpc";
               client = new SecureXmlRpcClient(baseURI + clientPath);
            }
            else
               client = new XmlRpcClient(baseURI + clientPath);
         }
         else
            client = new XmlRpcClient(baseURI + clientPath);

         String user = getProperty(dbXMLClient.USER);
         if ( user == null )
            user = getProperty(dbXMLClient.ALT_USER);

         String pass = getProperty(dbXMLClient.PASS);
         if ( pass == null )
            pass = getProperty(dbXMLClient.ALT_PASS);

         if ( user != null && user.length() > 0 && pass != null && pass.length() > 0 )
            client.setBasicAuthentication(user, pass);
      }
      return client;
   }

   public final Object execute(String method, Object[] args) throws dbXMLException {
      try {
         XmlRpcClient rpc = getXmlRpcClient();
         List l;
         if ( args != null && args.length > 0 ) {
            l = new ArrayList(args.length);
            for ( int i = 0; i < args.length; i++ )
               l.add(args[i]);
         }
         else
            l = EmptyList;
         return rpc.execute(method, l);
      }
      catch ( IOException e ) {
         String msg = e.getMessage();
         if ( msg.startsWith(ResponseMsg) ) {
            StringTokenizer st = new StringTokenizer(msg.substring(ResponseMsg.length()));
            String code = st.nextToken();
            String message = (String)ResponseCodes.get(code);
            if ( message == null )
               message = "Unknown Error";
            throw new dbXMLException("HTTP "+code+" ("+message+")", e);
         }
         else
            throw new dbXMLException(e);
      }
      catch ( XmlRpcException e ) {
         throw new dbXMLException(e.code, e);
      }
   }

   public final String executeString(String method, Object[] args) throws dbXMLException {
      return (String)execute(method, args);
   }

   public final byte[] executeBinary(String method, Object[] args) throws dbXMLException {
      return (byte[])execute(method, args);
   }

   public final boolean executeBoolean(String method, Object[] args) throws dbXMLException {
      return ((Boolean)execute(method, args)).booleanValue();
   }

   public final int executeInt(String method, Object[] args) throws dbXMLException {
      return ((Integer)execute(method, args)).intValue();
   }

   public final String[] executeList(String method, Object[] args) throws dbXMLException {
      List l = (List)execute(method, args);
      return (String[])l.toArray(EmptyStrings);
   }

   public final Map executeMap(String method, Object[] args) throws dbXMLException {
      return (Map)execute(method, args);
   }
}
