/*
* Copyright (c) 2001-2015, 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.parameter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.primeframework.mvc.action.ActionInvocation;
import org.primeframework.mvc.action.ActionInvocationStore;
import org.primeframework.mvc.action.config.ActionConfiguration;
import org.primeframework.mvc.workflow.WorkflowChain;
import com.google.inject.Inject;
/**
* This class implements the URIParameterWorkflow using patterns derived from the WADL specification.
* <p>
* The base URI for the action is fixed based on the package and class name. However, everything after the base can be
* set into properties or fields of the action class using the WADL pattern here. The pattern is like this:
* <p>
* <pre>
* {id}
* </pre>
* <p>
* If the classes base URI is /admin/user/edit, the full specification for the URI that action can handle would be:
* <p>
* <pre>
* /admin/user/edit/{id}
* </pre>
* <p>
* If the URI is <strong>/admin/user/edit/42</strong>, the value of 42 would be set into the action's
* <strong>id</strong> property or field.
* <p>
* The difference between the standard WADL specification pattern and Prime is that Prime allows you to capture all of
* the URI parameters or just what is left over after everything else has been handled using a special notation. This
* notation is:
* <p>
* <pre>
* {id}/{*theRest}
* </pre>
* <p>
* If the URI is <strong>/admin/user/edit/42/foo/bar</strong>, the value of 42 would be set into the action's
* <strong>id</strong> property or field and a List of Strings will be set into the property or field named
* <strong>theRest</strong>. The property that handles the all or the remaining parameters must be of type
* Collection<String> or List<String>.
*
* @author Brian Pontarelli
*/
public class DefaultURIParameterWorkflow implements URIParameterWorkflow {
private final ActionInvocationStore actionInvocationStore;
private final HttpServletRequest request;
@Inject
public DefaultURIParameterWorkflow(HttpServletRequest request, ActionInvocationStore actionInvocationStore) {
this.request = request;
this.actionInvocationStore = actionInvocationStore;
}
/**
* Fetches the ActionInvocation and if there are parameters and the action configuration exists, it means that this
* action can accept the parameters. This loops over the pattern from the ActionConfiguration and based on the
* pattern, it pulls the values from the URI parameters List in the ActionInvocation and sets them into the action
* class properties or fields using the ExpressionEvaluator.
*
* @param workflowChain Called after processing.
* @throws IOException If the WorkflowChain throws this exception.
* @throws ServletException If the WorkflowChain throws this exception.
*/
public void perform(WorkflowChain workflowChain) throws IOException, ServletException {
ActionInvocation actionInvocation = actionInvocationStore.getCurrent();
LinkedList<String> parameters = new LinkedList<String>(actionInvocation.uriParameters);
ActionConfiguration actionConfiguration = actionInvocation.configuration;
if (!parameters.isEmpty() && actionConfiguration != null) {
Map<String, String[]> uriParameters = new HashMap<>();
String[] patternParts = actionConfiguration.patternParts;
for (String patternPart : patternParts) {
// If there are no more URI parameters, we are finished
if (parameters.isEmpty()) {
break;
}
if (patternPart.startsWith("{*")) {
// Stuff the rest of the list into the property
String name = patternPart.substring(2, patternPart.length() - 1);
uriParameters.put(name, unescape(parameters).toArray(new String[parameters.size()]));
} else if (patternPart.startsWith("{")) {
// Stuff this parameter into the property
String name = patternPart.substring(1, patternPart.length() - 1);
String value = unescape(parameters.removeFirst());
uriParameters.put(name, new String[]{value});
} else {
// Pop the value off
parameters.removeFirst();
}
}
HttpServletRequestWrapper wrapper = (HttpServletRequestWrapper) request;
HttpServletRequest old = (HttpServletRequest) wrapper.getRequest();
wrapper.setRequest(new ParameterHttpServletRequest(old, uriParameters));
}
workflowChain.continueWorkflow();
}
/**
* Unescape all parameters provided in the list.
*
* @param parameters The list of parameters to unescape.
* @return The list of unescaped parameters.
*/
private List<String> unescape(List<String> parameters) {
for (int i = 0; i < parameters.size(); i++) {
parameters.set(i, unescape(parameters.get(i)));
}
return parameters;
}
/**
* Return the unescaped parameter.
*
* @param string The parameter to unescape
* @return
*/
private String unescape(String string) {
return string.replace("%20", " ");
}
}