All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.appslandia.plum.base.ActionInvoker Maven / Gradle / Ivy

// The MIT License (MIT)
// Copyright © 2015 AppsLandia. All rights reserved.

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

package com.appslandia.plum.base;

import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ElementKind;
import javax.validation.Path;

import com.appslandia.common.base.Out;
import com.appslandia.common.base.Params;
import com.appslandia.common.formatters.Formatter;
import com.appslandia.common.formatters.FormatterProvider;
import com.appslandia.common.json.JsonProcessor;
import com.appslandia.common.utils.AssertUtils;
import com.appslandia.common.utils.ExceptionUtils;
import com.appslandia.common.utils.ReflectionUtils;
import com.appslandia.plum.utils.ServletUtils;

/**
 *
 * @author Loc Ha
 *
 */
@ApplicationScoped
public class ActionInvoker {

	@Inject
	protected FormatterProvider formatterProvider;

	@Inject
	protected ModelBinder modelBinder;

	@Inject
	protected JsonProcessor jsonProcessor;

	public Object invokeAction(HttpServletRequest request, HttpServletResponse response, RequestContext requestContext, Object controller) throws Exception {
		List paramDescs = requestContext.getActionDesc().getParamDescs();
		Object[] actionArgs = new Object[paramDescs.size()];

		for (int index = 0; index < actionArgs.length; index++) {
			ParamDesc paramDesc = paramDescs.get(index);

			if (paramDesc.getParameter().getType() == HttpServletRequest.class) {
				actionArgs[index] = request;
				continue;
			}
			if (paramDesc.getParameter().getType() == HttpServletResponse.class) {
				actionArgs[index] = response;
				continue;
			}
			if (paramDesc.getParameter().getType() == RequestAccessor.class) {
				actionArgs[index] = new RequestAccessor(request);
				continue;
			}
			if (paramDesc.getParameter().getType() == RequestContext.class) {
				actionArgs[index] = requestContext;
				continue;
			}
			if (paramDesc.getParameter().getType() == ModelState.class) {
				actionArgs[index] = ServletUtils.getModelState(request);
				continue;
			}
			// @Model
			if (paramDesc.getModel() != null) {
				Object model = null;
				if (paramDesc.getModel().value() == Model.Source.PARAMETER) {
					model = ReflectionUtils.newInstance(paramDesc.getParameter().getType());
					String[] excludes = paramDesc.getModel().excludes();
					if (excludes.length == 0) {
						this.modelBinder.bindModel(request, model, null);
					} else {
						this.modelBinder.bindModel(request, model, p -> Arrays.stream(excludes).anyMatch(path -> p.equals(path)));
					}
				} else {
					model = this.jsonProcessor.read(request.getReader(), paramDesc.getParameter().getType());
					if (model != null) {
						this.modelBinder.validateModel(model, ServletUtils.getModelState(request), requestContext.getResources());
					}
				}
				actionArgs[index] = model;
				continue;
			}
			// Formatter
			Class valueType = ModelBinder.getValueType(paramDesc.getParameter());
			Formatter formatter = this.formatterProvider.findFormatter(paramDesc.getFormatter(), valueType);
			AssertUtils.assertNotNull(formatter);

			Out msgKey = new Out<>();
			Object parsedValue = ModelBinder.parseValue(request.getParameter(paramDesc.getParamName()), valueType, msgKey, formatter, requestContext.getFormatProvider());
			actionArgs[index] = (paramDesc.getParameter().getType() != Out.class) ? parsedValue : new Out(parsedValue);

			if (msgKey.value != null) {
				ServletUtils.addError(request, paramDesc.getParamName(), msgKey.value, getMsgParams(paramDesc, requestContext.getResources()));
			}
			if (paramDesc.isPathParam()) {
				if ((parsedValue == null) || (msgKey.value != null)) {
					throw new NotFoundException(Resources.ERROR_NOT_FOUND, requestContext.getResources());
				}
			}
		}
		// Invoke Action
		try {
			return requestContext.getActionDesc().getMethod().invoke(controller, actionArgs);
		} catch (Exception ex) {
			Exception he = (ex instanceof InvocationTargetException) ? ExceptionUtils.tryUnwrap((InvocationTargetException) ex) : ex;

			if (he instanceof ConstraintViolationException) {
				handleConstraintViolationException(request, requestContext, (ConstraintViolationException) he);
			}
			throw he;
		}
	}

	protected void handleConstraintViolationException(HttpServletRequest request, RequestContext requestContext, ConstraintViolationException ex) throws Exception {
		boolean isPathParamError = false;

		for (Object error : ex.getConstraintViolations()) {
			ConstraintViolation violation = (ConstraintViolation) error;

			Path.Node paramNode = getParamNode(violation.getPropertyPath());
			AssertUtils.assertNotNull(paramNode);

			ParamDesc paramDesc = requestContext.getActionDesc().getParamDescs().stream().filter(p -> p.getParameter().getName().equals(paramNode.getName())).findFirst().get();
			ServletUtils.addError(request, paramDesc.getParamName(), ModelBinder.getMsgKey(violation), getMsgParams(violation, paramDesc, requestContext.getResources()));

			if (paramDesc.isPathParam()) {
				isPathParamError = true;
			}
		}
		if (isPathParamError) {
			throw new NotFoundException(Resources.ERROR_NOT_FOUND, requestContext.getResources(), ex);
		} else {
			throw new BadRequestException(Resources.ERROR_BAD_REQUEST, requestContext.getResources(), ex);
		}
	}

	protected String getParamDisplayName(ParamDesc paramDesc, Resources resources) {
		return paramDesc.getParamName();
	}

	private Map getMsgParams(ParamDesc paramDesc, Resources resources) {
		return new Params(1).put(Resources.PARAM_FIELD_NAME, getParamDisplayName(paramDesc, resources));
	}

	private Map getMsgParams(ConstraintViolation violation, ParamDesc paramDesc, Resources resources) {
		Map map = ModelBinder.buildMsgParams(violation.getConstraintDescriptor());
		map.put(Resources.PARAM_FIELD_NAME, getParamDisplayName(paramDesc, resources));
		return map;
	}

	private static Path.Node getParamNode(Path path) {
		Iterator iter = path.iterator();
		while (iter.hasNext()) {
			Path.Node node = iter.next();
			if (node.getKind() == ElementKind.PARAMETER) {
				return node;
			}
		}
		return null;
	}
}