/**
 * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of the Liferay Enterprise
 * Subscription License ("License"). You may not use this file except in
 * compliance with the License. You can obtain a copy of the License by
 * contacting Liferay, Inc. See the License for the specific language governing
 * permissions and limitations under the License, including but not limited to
 * distribution rights of the Software.
 *
 *
 *
 */

package com.liferay.util.axis;

import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.servlet.BufferCacheServletResponse;
import com.liferay.portal.kernel.servlet.ServletResponseUtil;
import com.liferay.portal.kernel.servlet.UncommittedServletResponse;
import com.liferay.portal.kernel.util.ContentTypes;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.ReflectionUtil;
import com.liferay.portal.kernel.util.ServerDetector;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.kernel.xml.Document;
import com.liferay.portal.kernel.xml.UnsecureSAXReaderUtil;

import java.io.IOException;

import java.lang.reflect.Field;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.axis.utils.cache.MethodCache;

/**
 * @author Brian Wing Shun Chan
 */
public class AxisServlet extends org.apache.axis.transport.http.AxisServlet {

	@Override
	public void destroy() {
		if (ServerDetector.isWebLogic()) {
			doDestroy();
		}
		else {
			DestroyThread destroyThread = new DestroyThread();

			destroyThread.start();

			try {
				destroyThread.join();
			}
			catch (InterruptedException ie) {
				throw new RuntimeException(ie);
			}

			Exception e = destroyThread.getException();

			if (e != null) {
				if (e instanceof RuntimeException) {
					throw (RuntimeException)e;
				}
				else {
					throw new RuntimeException(e);
				}
			}
		}
	}

	@Override
	public void init(ServletConfig servletConfig) throws ServletException {
		_servletConfig = servletConfig;

		StringBundler sb = new StringBundler(9);

		sb.append("<complexType abstract=\"true\" name=\"OrderByComparator\">");
		sb.append("<sequence><element name=\"ascending\" ");
		sb.append("type=\"xsd:boolean\"/><element name=\"orderBy\" ");
		sb.append("nillable=\"true\" type=\"soapenc:string\"/><element ");
		sb.append("name=\"orderByConditionFields\" nillable=\"true\" ");
		sb.append("type=\"impl:ArrayOf_xsd_string\"/><element ");
		sb.append("name=\"orderByFields\" nillable=\"true\" ");
		sb.append("type=\"impl:ArrayOf_xsd_string\"/></sequence>");
		sb.append("</complexType>");

		_correctOrderByComparator = sb.toString();

		sb = new StringBundler(5);

		sb.append("<complexType name=\"ArrayOf_xsd_long\"><complexContent>");
		sb.append("<restriction base=\"soapenc:Array\"><attribute ");
		sb.append("ref=\"soapenc:arrayType\" ");
		sb.append("wsdl:arrayType=\"soapenc:long[]\"/></restriction>");
		sb.append("</complexContent></complexType>");

		_correctLongArray = sb.toString();

		sb = new StringBundler(5);

		sb.append("<complexType name=\"ArrayOf_xsd_string\"><complexContent>");
		sb.append("<restriction base=\"soapenc:Array\"><attribute ");
		sb.append("ref=\"soapenc:arrayType\" ");
		sb.append("wsdl:arrayType=\"soapenc:string[]\"/></restriction>");
		sb.append("</complexContent></complexType>");

		_correctStringArray = sb.toString();

		sb = new StringBundler(2);

		sb.append("<complexType name=\"OrderByComparator\"><simpleContent>");
		sb.append("<extension/></simpleContent></complexType>");

		_incorrectOrderByComparator = sb.toString();

		sb = new StringBundler(2);

		sb.append("<complexType name=\"ArrayOf_xsd_long\"><simpleContent>");
		sb.append("<extension/></simpleContent></complexType>");

		_incorrectLongArray = sb.toString();

		sb = new StringBundler(2);

		sb.append("<complexType name=\"ArrayOf_xsd_string\"><simpleContent>");
		sb.append("<extension/></simpleContent></complexType>");

		_incorrectStringArray = sb.toString();

		if (ServerDetector.isResin() || ServerDetector.isWebLogic()) {
			doInit();
		}
		else {
			InitThread initThread = new InitThread();

			initThread.start();

			try {
				initThread.join();
			}
			catch (InterruptedException ie) {
				throw new ServletException(ie);
			}

			Exception e = initThread.getException();

			if (e != null) {
				if (e instanceof ServletException) {
					throw (ServletException)e;
				}
				else {
					throw new ServletException(e);
				}
			}
		}
	}

	@Override
	public void service(
			HttpServletRequest request, HttpServletResponse response)
		throws IOException, ServletException {

		try {
			if (!_ready) {
				return;
			}

			BufferCacheServletResponse bufferCacheServletResponse =
				new BufferCacheServletResponse(response);

			super.service(request, bufferCacheServletResponse);

			String contentType = bufferCacheServletResponse.getContentType();

			response.setContentType(contentType);

			String content = bufferCacheServletResponse.getString();

			if (_fixContent) {
				if (contentType.contains(ContentTypes.TEXT_HTML)) {
					content = _HTML_TOP_WRAPPER.concat(content).concat(
						_HTML_BOTTOM_WRAPPER);
				}
				else if (contentType.contains(ContentTypes.TEXT_XML)) {
					content = fixXml(content);
				}
			}

			ServletResponseUtil.write(
				new UncommittedServletResponse(response),
				content.getBytes(StringPool.UTF8));
		}
		catch (IOException ioe) {
			throw ioe;
		}
		catch (ServletException se) {
			throw se;
		}
		catch (Exception e) {
			throw new ServletException(e);
		}
		finally {
			try {
				ThreadLocal<?> cache = (ThreadLocal<?>)_cacheField.get(null);

				if (cache != null) {
					cache.remove();
				}
			}
			catch (Exception e) {
				_log.error(e, e);
			}
		}
	}

	protected void doDestroy() {
		_ready = false;

		super.destroy();
	}

	protected void doInit() throws ServletException {
		super.init(_servletConfig);

		_fixContent = GetterUtil.getBoolean(
			_servletConfig.getInitParameter("fix-content"), true);

		_ready = true;
	}

	protected String fixXml(String xml) throws Exception {
		if (!xml.contains("<wsdl:definitions")) {
			return xml;
		}

		xml = StringUtil.replace(
			xml,
			new String[] {
				"\r\n", "\n", "  ", "> <", _incorrectOrderByComparator,
				_incorrectLongArray, _incorrectStringArray
			},
			new String[] {
				StringPool.BLANK, StringPool.BLANK, StringPool.BLANK, "><",
				_correctOrderByComparator, _correctLongArray,
				_correctStringArray
			});

		Document document = UnsecureSAXReaderUtil.read(xml);

		return document.formattedString();
	}

	private static final String _HTML_BOTTOM_WRAPPER = "</body></html>";

	private static final String _HTML_TOP_WRAPPER = "<html><body>";

	private static Log _log = LogFactoryUtil.getLog(AxisServlet.class);

	private static Field _cacheField;

	private String _correctLongArray;
	private String _correctOrderByComparator;
	private String _correctStringArray;
	private boolean _fixContent;
	private String _incorrectLongArray;
	private String _incorrectOrderByComparator;
	private String _incorrectStringArray;
	private boolean _ready;
	private ServletConfig _servletConfig;

	private class DestroyThread extends Thread {

		public DestroyThread() {
			setDaemon(true);
		}

		public Exception getException() {
			return _exception;
		}

		@Override
		public void run() {
			try {
				doDestroy();
			}
			catch (Exception e) {
				_exception = e;
			}
		}

		private Exception _exception;

	}

	private class InitThread extends Thread {

		public InitThread() {
			setDaemon(true);
		}

		public Exception getException() {
			return _exception;
		}

		@Override
		public void run() {
			try {
				doInit();
			}
			catch (Exception e) {
				_exception = e;
			}
		}

		private Exception _exception;

	}

	static {
		try {
			_cacheField = ReflectionUtil.getDeclaredField(
				MethodCache.class, "cache");
		}
		catch (Exception e) {
			_log.error(e, e);
		}
	}

}