/*
* Copyright (c) 2012, 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.http.HttpServletRequest;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.primeframework.mvc.action.ActionInvocation;
import org.primeframework.mvc.action.ActionInvocationStore;
import org.primeframework.mvc.config.MVCConfiguration;
import org.primeframework.mvc.parameter.ParameterParser.Parameters.Struct;
import org.primeframework.mvc.parameter.fileupload.FileInfo;
import org.primeframework.mvc.util.RequestKeys;
import com.google.inject.Inject;
/**
* This class is the default parameter parser. It pulls all of the parameters from the request and puts them into groups
* that are as follows:
*
* <table>
* <tr><th>Name</th><th>Description</th></tr>
* <tr><td>Pre</td><td>Pre-Parameters that get set into the action before any other parameters. These parameters are annotated with @PreParameter</td></tr>
* <tr><td>Optional</td><td>Optional parameters that might not exist on the action and if setting them fails all errors are ignored regardless of the configuration. These include image submit button parameters</td></tr>
* <tr><td>File</td><td>File upload parameters.</td></tr>
* <tr><td>Required</td><td>Everything else. If any of these parameters don't exist on the action an exception will be thrown if configuration is set to disallow unknown parameters</td></tr>
* </table>
*
* @author Brian Pontarelli
*/
public class DefaultParameterParser implements ParameterParser {
public static final String CHECKBOX_PREFIX = "__cb_";
public static final String RADIOBUTTON_PREFIX = "__rb_";
public static final String ACTION_PREFIX = "__a_";
private final MVCConfiguration configuration;
private final ActionInvocationStore actionInvocationStore;
private final HttpServletRequest request;
@Inject
public DefaultParameterParser(MVCConfiguration configuration, ActionInvocationStore actionInvocationStore,
HttpServletRequest request) {
this.configuration = configuration;
this.actionInvocationStore = actionInvocationStore;
this.request = request;
}
@Override
@SuppressWarnings("unchecked")
public Parameters parse() {
Map<String, String[]> parameters = request.getParameterMap();
Parameters result = new Parameters();
// Grab the files from the request
addFiles(result);
// Pull out the check box, radio button and action parameter
if (!parameters.isEmpty()) {
Map<String, String[]> checkBoxes = new HashMap<String, String[]>();
Map<String, String[]> radioButtons = new HashMap<String, String[]>();
Set<String> actions = new HashSet<String>();
separateParameters(parameters, result, checkBoxes, radioButtons, actions);
preParameters(result);
// Remove all the existing checkbox, radio and action keys
checkBoxes.keySet().removeAll(result.optional.keySet());
checkBoxes.keySet().removeAll(result.required.keySet());
radioButtons.keySet().removeAll(result.optional.keySet());
radioButtons.keySet().removeAll(result.required.keySet());
// Add back in any left overs
addUncheckedValues(checkBoxes, result);
addUncheckedValues(radioButtons, result);
// Remove actions from the parameter as they should be ignored right now
result.optional.keySet().removeAll(actions);
result.required.keySet().removeAll(actions);
}
return result;
}
private void preParameters(Parameters result) {
ActionInvocation actionInvocation = actionInvocationStore.getCurrent();
for (String name : actionInvocation.configuration.preParameterMembers.keySet()) {
Struct struct = result.optional.remove(name);
if (struct == null) {
struct = result.required.remove(name);
}
if (struct != null) {
result.pre.put(name, struct);
}
}
}
private void separateParameters(Map<String, String[]> parameters, Parameters result, Map<String, String[]> checkBoxes, Map<String, String[]> radioButtons, Set<String> actions) {
for (String key : parameters.keySet()) {
if (InternalParameters.isInternalParameter(key)) {
continue;
}
boolean optional = (key.endsWith(".x") || key.endsWith(".y"));
if (key.startsWith(CHECKBOX_PREFIX)) {
checkBoxes.put(key.substring(CHECKBOX_PREFIX.length()), parameters.get(key));
} else if (key.startsWith(RADIOBUTTON_PREFIX)) {
radioButtons.put(key.substring(RADIOBUTTON_PREFIX.length()), parameters.get(key));
} else if (key.startsWith(ACTION_PREFIX)) {
actions.add(key.substring(ACTION_PREFIX.length()));
} else {
int index = key.indexOf("@");
String parameter = (index > 0) ? key.substring(0, index) : key;
Struct s;
if (optional) {
s = result.optional.get(parameter);
} else {
s = result.required.get(parameter);
}
if (s == null) {
s = new Struct();
if (optional) {
result.optional.put(parameter, s);
} else {
result.required.put(parameter, s);
}
}
if (index > 0) {
s.attributes.put(key.substring(index + 1), parameters.get(key)[0]);
} else {
// If the ignore empty parameters flag is set, which IS NOT the default, this
// block will only ever add the values to the structure if they contain at least
// one non-empty String.
String[] values = parameters.get(parameter);
if (!configuration.ignoreEmptyParameters() || !empty(values)) {
s.values = values;
}
}
}
}
}
@SuppressWarnings("unchecked")
protected void addFiles(Parameters result) {
Map<String, List<FileInfo>> fileInfos = (Map<String, List<FileInfo>>) request.getAttribute(RequestKeys.FILE_ATTRIBUTE);
if (fileInfos != null) {
result.files.putAll(fileInfos);
}
}
protected void addUncheckedValues(Map<String, String[]> map, Parameters parameters) {
for (String key : map.keySet()) {
String[] values = map.get(key);
// Only add the values if there is a single one and it is empty, which denotes that they
// want to set null into the action, or the values are multiple and they is one non-empty
// value in the bunch. The second case occurs when they are using multiple checkboxes or
// radio buttons for the same name. This will cause multiple hidden inputs and they should
// all either have a unchecked value or should all be empty. If they are all empty, then
// null should be put into the object.
if ((values != null && values.length == 1 && values[0].equals("")) || empty(values)) {
parameters.required.put(key, new Struct());
} else {
parameters.required.put(key, new Struct(values));
}
}
}
protected boolean empty(String[] values) {
if (values != null && values.length > 0) {
for (String value : values) {
if (!value.equals("")) {
return false;
}
}
}
return true;
}
}