/*
* Copyright (c) 2001-2016, Inversoft Inc., All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*/
package org.primeframework.mvc.action;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Set;
import org.primeframework.mvc.NotImplementedException;
import org.primeframework.mvc.parameter.DefaultParameterParser;
import org.primeframework.mvc.parameter.InternalParameters;
import org.primeframework.mvc.servlet.HTTPMethod;
import org.primeframework.mvc.servlet.ServletTools;
import org.primeframework.mvc.workflow.WorkflowChain;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import com.google.inject.Inject;
/**
* This class is the default implementation of the ActionMappingWorkflow. During the perform method, this class
* determines the action that will be invoked based on the URI and put it into the ActionInvocationStore.
*
* @author Brian Pontarelli
*/
public class DefaultActionMappingWorkflow implements ActionMappingWorkflow {
private final static Logger logger = LoggerFactory.getLogger(DefaultActionMappingWorkflow.class);
private final ActionInvocationStore actionInvocationStore;
private final ActionMapper actionMapper;
private final HttpServletRequest request;
private final HttpServletResponse response;
@Inject(optional = true) private MetricRegistry metricRegistry;
@Inject
public DefaultActionMappingWorkflow(HttpServletRequest request, HttpServletResponse response,
ActionInvocationStore actionInvocationStore, ActionMapper actionMapper) {
this.request = request;
this.response = response;
this.actionInvocationStore = actionInvocationStore;
this.actionMapper = actionMapper;
}
/**
* Processes the request URI, loads the action configuration, creates the action and stores the invocation in the
* request.
*
* @param chain The workflow chain.
* @throws IOException If the chain throws an exception.
* @throws ServletException If the chain throws an exception.
*/
public void perform(WorkflowChain chain) throws IOException, ServletException {
// First, see if they hit a different button
String uri = determineURI();
if (logger.isDebugEnabled()) {
logger.debug("METHOD: " + request.getMethod() + "; URI: " + uri);
}
HTTPMethod method = HTTPMethod.valueOf(request.getMethod().toUpperCase());
boolean executeResult = InternalParameters.is(request, InternalParameters.EXECUTE_RESULT);
ActionInvocation actionInvocation = actionMapper.map(method, uri, executeResult);
// This case is a redirect because they URI maps to something new and there isn't an action associated with it. For
// example, this is how the index handling works.
if (!actionInvocation.uri().equals(uri) && actionInvocation.action == null) {
response.sendRedirect(actionInvocation.uri());
return;
}
// Keep the current action in the store if an exception is thrown so that it can be used from the error workflow
actionInvocationStore.setCurrent(actionInvocation);
// Anyone downstream should understand it is possible for the method to be null in the ActionInvocation
if (actionInvocation.action != null && actionInvocation.method == null) {
Class<?> actionClass = actionInvocation.configuration.actionClass;
logger.warn("The action class [" + actionClass + "] does not have a valid execute method for the " +
"HTTP method [" + method + "]");
throw new NotImplementedException();
}
// Start the timer and grab a meter for errors
Timer.Context timer = null;
Meter errorMeter = null;
if (metricRegistry != null && actionInvocation.action != null) {
timer = metricRegistry.timer("prime-mvc.[" + actionInvocation.uri() + "].requests").time();
errorMeter = metricRegistry.meter("prime-mvc.[" + actionInvocation.uri() + "].errors");
}
try {
chain.continueWorkflow();
// We need to leave the action in the store because it might be used by the Error Workflow
actionInvocationStore.removeCurrent();
} catch (ServletException | IOException | RuntimeException | Error e) {
if (errorMeter != null) {
errorMeter.mark();
}
throw e;
} finally {
if (timer != null) {
timer.stop();
}
}
}
@SuppressWarnings("unchecked")
private String determineURI() {
String uri = null;
Set<String> keys = request.getParameterMap().keySet();
for (String key : keys) {
if (key.startsWith(DefaultParameterParser.ACTION_PREFIX)) {
String actionParameterName = key.substring(4);
String actionParameterValue = request.getParameter(key);
if (request.getParameter(actionParameterName) != null && actionParameterValue.trim().length() > 0) {
uri = actionParameterValue;
// Handle relative URIs
if (!uri.startsWith("/")) {
String requestURI = ServletTools.getRequestURI(request);
int index = requestURI.lastIndexOf("/");
if (index >= 0) {
uri = requestURI.substring(0, index) + "/" + uri;
}
}
}
}
}
if (uri == null) {
uri = ServletTools.getRequestURI(request);
if (!uri.startsWith("/")) {
uri = "/" + uri;
}
}
return uri;
}
}