/**
 * Copyright (c) 2000-present 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.portal.struts;

import com.liferay.petra.string.StringPool;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.json.JSONFactoryUtil;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.security.access.control.AccessControlUtil;
import com.liferay.portal.kernel.security.auth.AuthTokenUtil;
import com.liferay.portal.kernel.security.auth.PrincipalException;
import com.liferay.portal.kernel.servlet.HttpHeaders;
import com.liferay.portal.kernel.servlet.ServletContextPool;
import com.liferay.portal.kernel.util.ClassUtil;
import com.liferay.portal.kernel.util.ContentTypes;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.ParamUtil;
import com.liferay.portal.kernel.util.PortalUtil;
import com.liferay.portal.kernel.util.SetUtil;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.kernel.util.WebKeys;
import com.liferay.portal.servlet.SharedSessionServletRequest;
import com.liferay.portal.util.PropsValues;

import java.io.OutputStream;

import java.util.Set;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

/**
 * @author Ming-Gih Lam
 * @author Brian Wing Shun Chan
 * @author Tomas Polesovsky
 */
public abstract class JSONAction extends Action {

	@Override
	public ActionForward execute(
			ActionMapping actionMapping, ActionForm actionForm,
			HttpServletRequest request, HttpServletResponse response)
		throws Exception {

		if (rerouteExecute(request, response)) {
			return null;
		}

		String callback = ParamUtil.getString(request, "callback");

		String json = null;

		try {
			checkAuthToken(request);

			json = getJSON(actionMapping, actionForm, request, response);

			if (Validator.isNotNull(callback)) {
				StringBundler sb = new StringBundler(5);

				sb.append("/**/");
				sb.append(callback);
				sb.append(StringPool.OPEN_PARENTHESIS);
				sb.append(json);
				sb.append(StringPool.CLOSE_PARENTHESIS);

				json = sb.toString();
			}
		}
		catch (PrincipalException pe) {
			_log.error(pe.getMessage());

			PortalUtil.sendError(
				HttpServletResponse.SC_FORBIDDEN, pe, request, response);

			return null;
		}
		catch (SecurityException se) {
			if (_log.isWarnEnabled()) {
				_log.warn(se.getMessage());
			}

			if (PropsValues.JSON_SERVICE_SERIALIZE_THROWABLE) {
				json = JSONFactoryUtil.serializeThrowable(se);
			}
			else {
				PortalUtil.sendError(
					HttpServletResponse.SC_INTERNAL_SERVER_ERROR, se, request,
					response);

				return null;
			}
		}
		catch (Exception e) {
			_log.error(e.getMessage());

			PortalUtil.sendError(
				HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e, request,
				response);

			return null;
		}

		boolean refresh = ParamUtil.getBoolean(request, "refresh");

		if (refresh) {
			return actionMapping.findForward(ActionConstants.COMMON_REFERER);
		}
		else if (Validator.isNotNull(json)) {
			response.setCharacterEncoding(StringPool.UTF8);
			response.setContentType(ContentTypes.APPLICATION_JSON);
			response.setHeader(
				HttpHeaders.CACHE_CONTROL,
				HttpHeaders.CACHE_CONTROL_NO_CACHE_VALUE);

			try (OutputStream outputStream = response.getOutputStream()) {
				byte[] bytes = json.getBytes(StringPool.UTF8);

				outputStream.write(bytes);
			}
		}

		return null;
	}

	public abstract String getJSON(
			ActionMapping actionMapping, ActionForm actionForm,
			HttpServletRequest request, HttpServletResponse response)
		throws Exception;

	public void setServletContext(ServletContext servletContext) {
		_servletContext = servletContext;
	}

	protected void checkAuthToken(HttpServletRequest request)
		throws PortalException {

		String authType = GetterUtil.getString(request.getAuthType());

		// Support for the legacy JSON API at /c/portal/json_service

		if (AccessControlUtil.getAccessControlContext() == null) {
			if (authType.equals(HttpServletRequest.BASIC_AUTH) ||
				authType.equals(HttpServletRequest.DIGEST_AUTH)) {

				return;
			}
		}
		else {

			// The new web service should only check auth tokens when the user
			// is authenticated using portal session cookies

			if (!authType.equals(HttpServletRequest.FORM_AUTH)) {
				return;
			}
		}

		if (PropsValues.JSON_SERVICE_AUTH_TOKEN_ENABLED) {
			if (!AccessControlUtil.isAccessAllowed(request, _hostsAllowed)) {
				AuthTokenUtil.checkCSRFToken(request, getCSRFOrigin(request));
			}
		}
	}

	protected String getCSRFOrigin(HttpServletRequest request) {
		return ClassUtil.getClassName(this);
	}

	protected String getReroutePath() {
		return null;
	}

	protected boolean rerouteExecute(
			HttpServletRequest request, HttpServletResponse response)
		throws Exception {

		String reroutePath = getReroutePath();

		if (Validator.isNull(reroutePath)) {
			return false;
		}

		String requestServletContextName = ParamUtil.getString(
			request, "servletContextName");

		if (Validator.isNull(requestServletContextName)) {
			return false;
		}

		ServletContext servletContext = _servletContext;

		if (servletContext == null) {
			servletContext = (ServletContext)request.getAttribute(WebKeys.CTX);
		}

		String servletContextName = GetterUtil.getString(
			servletContext.getServletContextName());

		if (servletContextName.equals(requestServletContextName)) {
			return false;
		}

		ServletContext requestServletContext = ServletContextPool.get(
			requestServletContextName);

		if (requestServletContext == null) {
			return false;
		}

		RequestDispatcher requestDispatcher =
			requestServletContext.getRequestDispatcher(reroutePath);

		if (requestDispatcher == null) {
			return false;
		}

		requestDispatcher.forward(
			new SharedSessionServletRequest(request, true), response);

		return true;
	}

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

	private final Set<String> _hostsAllowed = SetUtil.fromArray(
		PropsValues.JSON_SERVICE_AUTH_TOKEN_HOSTS_ALLOWED);
	private ServletContext _servletContext;

}