/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2001-2004 Caucho Technology, Inc.  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 acknowlegement:
 *       "This product includes software developed by the
 *        Caucho Technology (http://www.caucho.com/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "Hessian", "Resin", and "Caucho" must not be used to
 *    endorse or promote products derived from this software without prior
 *    written permission. For written permission, please contact
 *    info@caucho.com.
 *
 * 5. Products derived from this software may not be called "Resin"
 *    nor may "Resin" appear in their names without prior written
 *    permission of Caucho Technology.
 *
 * 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 CAUCHO TECHNOLOGY 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.
 *
 * @author Scott Ferguson
 */

package com.ht.web.hessian;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.Writer;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.caucho.hessian.client.HessianConnection;
import com.caucho.hessian.client.HessianProxyFactory;
import com.caucho.hessian.client.HessianRuntimeException;
import com.caucho.hessian.io.AbstractHessianInput;
import com.caucho.hessian.io.AbstractHessianOutput;
import com.caucho.hessian.io.HessianDebugInputStream;
import com.caucho.hessian.io.HessianDebugOutputStream;
import com.caucho.hessian.io.HessianProtocolException;
import com.caucho.hessian.io.HessianRemote;
import com.caucho.services.server.AbstractSkeleton;

/**
 * Proxy implementation for Hessian clients. Applications will generally use
 * HessianProxyFactory to create proxy clients.
 */
public class MyHessianProxy implements InvocationHandler, Serializable {
	private static final Logger log = Logger.getLogger(MyHessianProxy.class
			.getName());

	private static List<String> cookies = null;
	protected HessianProxyFactory _factory;

	private WeakHashMap<Method, String> _mangleMap = new WeakHashMap<Method, String>();

	private Class<?> _type;
	private URL _url;

	/**
	 * Protected constructor for subclassing
	 */
	protected MyHessianProxy(URL url, HessianProxyFactory factory) {
		this(url, factory, null);
	}

	/**
	 * Protected constructor for subclassing
	 */
	protected MyHessianProxy(URL url, HessianProxyFactory factory, Class<?> type) {
		_factory = factory;
		_url = url;
		_type = type;
	}

	/**
	 * Returns the proxy's URL.
	 */
	public URL getURL() {
		return _url;
	}

	/**
	 * Handles the object invocation.
	 * 
	 * @param proxy
	 *            the proxy object to invoke
	 * @param method
	 *            the method to call
	 * @param args
	 *            the arguments to the proxy object
	 */
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		String mangleName;

		synchronized (_mangleMap) {
			mangleName = _mangleMap.get(method);
		}

		if (mangleName == null) {
			String methodName = method.getName();
			Class<?>[] params = method.getParameterTypes();

			// equals and hashCode are special cased
			if (methodName.equals("equals") && params.length == 1
					&& params[0].equals(Object.class)) {
				Object value = args[0];
				if (value == null || !Proxy.isProxyClass(value.getClass()))
					return Boolean.FALSE;

				Object proxyHandler = Proxy.getInvocationHandler(value);

				if (!(proxyHandler instanceof MyHessianProxy))
					return Boolean.FALSE;

				MyHessianProxy handler = (MyHessianProxy) proxyHandler;

				return new Boolean(_url.equals(handler.getURL()));
			} else if (methodName.equals("hashCode") && params.length == 0)
				return new Integer(_url.hashCode());
			else if (methodName.equals("getHessianType"))
				return proxy.getClass().getInterfaces()[0].getName();
			else if (methodName.equals("getHessianURL"))
				return _url.toString();
			else if (methodName.equals("toString") && params.length == 0)
				return "HessianProxy[" + _url + "]";

			if (!_factory.isOverloadEnabled())
				mangleName = method.getName();
			else
				mangleName = mangleName(method);

			synchronized (_mangleMap) {
				_mangleMap.put(method, mangleName);
			}
		}

		InputStream is = null;
		HessianConnection conn = null;

		try {
			if (log.isLoggable(Level.FINER))
				log.finer("Hessian[" + _url + "] calling " + mangleName);

			conn = sendRequest(mangleName, args);

			is = conn.getInputStream();

			if (log.isLoggable(Level.FINEST)) {
				PrintWriter dbg = new PrintWriter(new LogWriter(log));
				HessianDebugInputStream dIs = new HessianDebugInputStream(is,
						dbg);

				dIs.startTop2();

				is = dIs;
			}

			AbstractHessianInput in;

			int code = is.read();

			if (code == 'H') {
				int major = is.read();
				int minor = is.read();

				in = _factory.getHessian2Input(is);

				Object value = in.readReply(method.getReturnType());
				/*************** 文件传输bug修复 @see http://bugs.caucho.com/view.php?id=3655 ****************/
				if (value instanceof InputStream) {
					value = new ResultInputStream(conn, is, in,
							(InputStream) value);
					is = null;
					conn = null;
				}
				/*******************************/

				return value;
			} else if (code == 'r') {
				int major = is.read();
				int minor = is.read();

				in = _factory.getHessianInput(is);

				in.startReplyBody();

				Object value = in.readObject(method.getReturnType());

				if (value instanceof InputStream) {
					value = new ResultInputStream(conn, is, in,
							(InputStream) value);
					is = null;
					conn = null;
				} else
					in.completeReply();

				return value;
			} else
				throw new HessianProtocolException("'" + (char) code
						+ "' is an unknown code");
		} catch (HessianProtocolException e) {
			throw new HessianRuntimeException(e);
		} finally {
			try {
				if (is != null)
					is.close();
			} catch (Exception e) {
				log.log(Level.FINE, e.toString(), e);
			}

			try {
				if (conn != null)
					conn.destroy();
			} catch (Exception e) {
				log.log(Level.FINE, e.toString(), e);
			}
		}
	}

	protected String mangleName(Method method) {
		Class<?>[] param = method.getParameterTypes();

		if (param == null || param.length == 0)
			return method.getName();
		else
			return AbstractSkeleton.mangleName(method, false);
	}

	/**
	 * Sends the HTTP request to the Hessian connection.
	 */
	protected HessianConnection sendRequest(String methodName, Object[] args)
			throws IOException {
		HessianConnection conn = null;

		conn = _factory.getConnectionFactory().open(_url);
		boolean isValid = false;

		try {
			addRequestHeaders(conn);

			OutputStream os = null;

			try {
				os = conn.getOutputStream();
			} catch (Exception e) {
				throw new HessianRuntimeException(e);
			}

			if (log.isLoggable(Level.FINEST)) {
				PrintWriter dbg = new PrintWriter(new LogWriter(log));
				HessianDebugOutputStream dOs = new HessianDebugOutputStream(os,
						dbg);
				dOs.startTop2();
				os = dOs;
			}

			AbstractHessianOutput out = _factory.getHessianOutput(os);

			out.call(methodName, args);
			out.flush();

			conn.sendRequest();

			isValid = true;

			return conn;
		} finally {
			if (!isValid && conn != null)
				conn.destroy();
		}
	}

	/**
	 * Method that allows subclasses to add request headers such as cookies.
	 * Default implementation is empty.
	 */
	protected void addRequestHeaders(HessianConnection conn) {
		conn.addHeader("Content-Type", "x-application/hessian");

		String basicAuth = _factory.getBasicAuth();

		if (basicAuth != null)
			conn.addHeader("Authorization", basicAuth);

		/**** 自定义cookies处理 ****/
		if (cookies != null) {
			for (String cookieString : cookies) {
				//conn.addHeader(key, value)
				conn.addHeader("Cookie", cookieString);
			}
		}
		/**** 自定义cookies处理 ****/

	}

	/**
	 * Method that allows subclasses to parse response headers such as cookies.
	 * Default implementation is empty.
	 * 
	 * @param conn
	 */
	protected void parseResponseHeaders(URLConnection conn) {
		/**** 自定义cookies处理 ****/
		List<String> _cookies = conn.getHeaderFields().get("Set-Cookie");
		if (_cookies != null) {
			cookies = _cookies;
		}
		/**** 自定义cookies处理 ****/

	}

	public Object writeReplace() {
		return new HessianRemote(_type.getName(), _url.toString());
	}

	static class ResultInputStream extends InputStream {
		private HessianConnection _conn;
		private InputStream _connIs;
		private AbstractHessianInput _in;
		private InputStream _hessianIs;

		ResultInputStream(HessianConnection conn, InputStream is,
				AbstractHessianInput in, InputStream hessianIs) {
			_conn = conn;
			_connIs = is;
			_in = in;
			_hessianIs = hessianIs;
		}

		public int read() throws IOException {
			if (_hessianIs != null) {
				int value = _hessianIs.read();

				if (value < 0)
					close();

				return value;
			} else
				return -1;
		}

		public int read(byte[] buffer, int offset, int length)
				throws IOException {
			if (_hessianIs != null) {
				int value = _hessianIs.read(buffer, offset, length);

				if (value < 0)
					close();

				return value;
			} else
				return -1;
		}

		public void close() throws IOException {
			HessianConnection conn = _conn;
			_conn = null;

			InputStream connIs = _connIs;
			_connIs = null;

			AbstractHessianInput in = _in;
			_in = null;

			InputStream hessianIs = _hessianIs;
			_hessianIs = null;

			try {
				if (hessianIs != null)
					hessianIs.close();
			} catch (Exception e) {
				log.log(Level.FINE, e.toString(), e);
			}

			try {
				if (in != null) {
					in.completeReply();
					in.close();
				}
			} catch (Exception e) {
				log.log(Level.FINE, e.toString(), e);
			}

			try {
				if (connIs != null) {
					connIs.close();
				}
			} catch (Exception e) {
				log.log(Level.FINE, e.toString(), e);
			}

			try {
				if (conn != null) {
					conn.close();
				}
			} catch (Exception e) {
				log.log(Level.FINE, e.toString(), e);
			}
		}
	}

	static class LogWriter extends Writer {
		private Logger _log;
		private Level _level = Level.FINEST;
		private StringBuilder _sb = new StringBuilder();

		LogWriter(Logger log) {
			_log = log;
		}

		public void write(char ch) {
			if (ch == '\n' && _sb.length() > 0) {
				_log.fine(_sb.toString());
				_sb.setLength(0);
			} else
				_sb.append((char) ch);
		}

		public void write(char[] buffer, int offset, int length) {
			for (int i = 0; i < length; i++) {
				char ch = buffer[offset + i];

				if (ch == '\n' && _sb.length() > 0) {
					_log.log(_level, _sb.toString());
					_sb.setLength(0);
				} else
					_sb.append((char) ch);
			}
		}

		public void flush() {
		}

		public void close() {
			if (_sb.length() > 0)
				_log.log(_level, _sb.toString());
		}
	}
}
