Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion api/src/org/labkey/api/action/BaseApiAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import org.labkey.api.view.UnauthorizedException;
import org.labkey.api.view.ViewContext;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
Expand Down Expand Up @@ -339,7 +340,7 @@ private FormAndErrors<FORM> defaultPopulateForm() throws Exception
{
saveRequestedApiVersion(getViewContext().getRequest(), null);

BindException errors = defaultBindParameters(getCommand(), getPropertyValues());
BindException errors = defaultBindParameters(getPropertyValues());
FORM form = (FORM)errors.getTarget();

return new FormAndErrors<>(form, errors);
Expand Down Expand Up @@ -460,6 +461,16 @@ private FormAndErrors<FORM> populateJSONObjectForm() throws Exception
}
saveRequestedApiVersion(getViewContext().getRequest(), jsonObj);

// Records are immutable, so we can't instantiate the form up front and populate it; instead collect the JSON
// properties and construct the record via defaultBindParameters(). Note: record forms can't implement
// ApiJsonForm since that relies on mutating an existing instance.
if (getCommandClass().isRecord())
{
PropertyValues values = null == jsonObj ? new MutablePropertyValues() : new JsonPropertyValues(jsonObj);
BindException errors = defaultBindParameters(values);
return new FormAndErrors<>((FORM)errors.getTarget(), errors);
}

FORM form = getCommand();
BindException errors = populateForm(jsonObj, form);
return new FormAndErrors<>(form, errors);
Expand Down
49 changes: 28 additions & 21 deletions api/src/org/labkey/api/action/BaseViewAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.labkey.api.audit.TransactionAuditProvider;
import org.labkey.api.data.Container;
import org.labkey.api.data.ConvertHelper;
import org.labkey.api.data.ObjectFactory;
import org.labkey.api.security.User;
import org.labkey.api.util.HelpTopic;
import org.labkey.api.util.HttpUtil;
Expand Down Expand Up @@ -78,6 +79,7 @@
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public abstract class BaseViewAction<FORM> extends PermissionCheckableAction implements Validator, HasPageConfig, ContainerUser
{
Expand Down Expand Up @@ -122,43 +124,36 @@ protected BaseViewAction()
setCommandClass(typeBest);
}


protected abstract String getCommandClassMethodName();


protected BaseViewAction(@NotNull Class<? extends FORM> commandClass)
{
setCommandClass(commandClass);
}


public void setProperties(PropertyValues pvs)
{
_pvs = pvs;
}


public void setProperties(Map<?,?> m)
{
_pvs = new MutablePropertyValues(m);
}


/* Doesn't guarantee non-null, non-empty */
public Object getProperty(String key, String d)
{
PropertyValue pv = _pvs.getPropertyValue(key);
return pv == null ? d : pv.getValue();
}


public Object getProperty(Enum<?> key)
{
PropertyValue pv = _pvs.getPropertyValue(key.name());
return pv == null ? null : pv.getValue();
}


public Object getProperty(String key)
{
PropertyValue pv = _pvs.getPropertyValue(key);
Expand All @@ -170,7 +165,6 @@ public PropertyValues getPropertyValues()
return _pvs;
}


public static PropertyValues getPropertyValuesForFormBinding(PropertyValues pvs, @NotNull Predicate<String> allowBind)
{
if (null == pvs)
Expand All @@ -184,7 +178,6 @@ public static PropertyValues getPropertyValuesForFormBinding(PropertyValues pvs,
return ret;
}


/// Some characters can be mishandled by the browser in multipart/formdata requests (e.g. doublequote and backslask).
/// We support an encoding from fields to avoid these characters, see {@link PageFlowUtil#encodeFormName} and {@link PageFlowUtil#decodeFormName}.
static public class ViewActionParameterPropertyValues extends ServletRequestParameterPropertyValues
Expand Down Expand Up @@ -218,7 +211,6 @@ public ModelAndView handleRequest(@NotNull HttpServletRequest request, @NotNull
return handleRequest();
}


private void handleSpecialProperties()
{
_robot = PageFlowUtil.isRobotUserAgent(getViewContext().getRequest().getHeader("User-Agent"));
Expand Down Expand Up @@ -260,55 +252,47 @@ private boolean hasStringValue(String propertyName)

public abstract ModelAndView handleRequest() throws Exception;


@Override
public void setPageConfig(PageConfig page)
{
_pageConfig = page;
}


@Override
public Container getContainer()
{
return getViewContext().getContainer();
}


@Override
public User getUser()
{
return getViewContext().getUser();
}


@Override
public PageConfig getPageConfig()
{
return _pageConfig;
}


public void setTitle(String title)
{
assert null != getPageConfig() : "action not initialized property";
getPageConfig().setTitle(title);
}


public void setHelpTopic(String topicName)
{
setHelpTopic(new HelpTopic(topicName));
}


public void setHelpTopic(HelpTopic topic)
{
assert null != getPageConfig() : "action not initialized properly";
getPageConfig().setHelpTopic(topic);
}


protected Object newInstance(Class<?> c)
{
try
Expand All @@ -324,7 +308,6 @@ protected Object newInstance(Class<?> c)
}
}


protected @NotNull FORM getCommand(HttpServletRequest request) throws Exception
{
FORM command = (FORM) createCommand();
Expand All @@ -335,13 +318,11 @@ protected Object newInstance(Class<?> c)
return command;
}


protected @NotNull FORM getCommand() throws Exception
{
return getCommand(getViewContext().getRequest());
}


//
// PARAMETER BINDING
//
Expand All @@ -353,6 +334,32 @@ protected Object newInstance(Class<?> c)
return defaultBindParameters(form, getCommandName(), params);
}

/**
* Bind request parameters to the action's command class, dispatching to record binding when that class is a Java
* record. Records are immutable (no setters), so instead of instantiating the form and populating it via Spring data
* binding we collect the property values into a map and construct the record via its {@link ObjectFactory}. Callers
* that previously invoked {@code defaultBindParameters(getCommand(), params)} directly should prefer this method so
* they transparently gain record support.
*/
public @NotNull BindException defaultBindParameters(PropertyValues params) throws Exception
{
Class<?> commandClass = getCommandClass();
return commandClass.isRecord() ? bindParametersToRecord(commandClass, params) : defaultBindParameters(getCommand(), params);
}

// Simple binding for Java records: no support for binding errors, arrays, lists, etc.
public static <R> BindException bindParametersToRecord(Class<R> recordClass, PropertyValues pvs)
{
// Note: We don't support record-based forms implementing HasAllowBindParameter since we must populate all
// properties at record construction time and therefore can't invoke allowBindParameter() prior to that.
PropertyValues m = getPropertyValuesForFormBinding(pvs, HasAllowBindParameter.getDefaultPredicate());
ObjectFactory<R> factory = ObjectFactory.Registry.getFactory(recordClass);
Map<String, Object> map = m.stream()
.filter(pv -> pv.getValue() != null)
.collect(Collectors.toMap(PropertyValue::getName, PropertyValue::getValue));
R record = factory.fromMap(map);
return new NullSafeBindException(record, "Form");
}

public static @NotNull BindException defaultBindParameters(Object form, String commandName, PropertyValues params)
{
Expand Down
2 changes: 1 addition & 1 deletion api/src/org/labkey/api/action/ConfirmAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ protected String getCommandClassMethodName()

public BindException bindParameters(PropertyValues m) throws Exception
{
return defaultBindParameters(getCommand(), m);
return defaultBindParameters(m);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion api/src/org/labkey/api/action/FormApiAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ protected ModelAndView getPrintView(FORM form, BindException errors) throws Exce

protected BindException bindParameters(PropertyValues pvs) throws Exception
{
return SimpleViewAction.defaultBindParameters(getCommand(), getCommandName(), pvs);
return defaultBindParameters(pvs);
}

/**
Expand Down
21 changes: 1 addition & 20 deletions api/src/org/labkey/api/action/FormViewAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,18 @@
package org.labkey.api.action;

import org.jetbrains.annotations.NotNull;
import org.labkey.api.data.ObjectFactory;
import org.labkey.api.miniprofiler.MiniProfiler;
import org.labkey.api.miniprofiler.Timing;
import org.labkey.api.util.ExceptionUtil;
import org.labkey.api.util.URLHelper;
import org.labkey.api.view.HttpView;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.validation.ObjectError;
import org.springframework.web.servlet.ModelAndView;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
* Is this better than BaseCommandController? Probably not, but it understands TableViewForm.
Expand Down Expand Up @@ -149,22 +145,7 @@ protected String getCommandClassMethodName()

public BindException bindParameters(PropertyValues m) throws Exception
{
Class<?> commandClass = getCommandClass();
return commandClass.isRecord() ? defaultBindParametersToRecord(commandClass, m) : defaultBindParameters(getCommand(), m);
}

// Simple binding for Java records: no support for binding errors, arrays, lists, etc.
private <R> BindException defaultBindParametersToRecord(Class<R> recordClass, PropertyValues pvs)
{
// Note: We don't support record-based forms implementing HasAllowBindParameter since we must populate all
// properties at record construction time and therefore can't invoke allowBindParameter() prior to that.
PropertyValues m = getPropertyValuesForFormBinding(pvs, HasAllowBindParameter.getDefaultPredicate());
ObjectFactory<R> factory = ObjectFactory.Registry.getFactory(recordClass);
Map<String, Object> map = m.stream()
.filter(pv -> pv.getValue() != null)
.collect(Collectors.toMap(PropertyValue::getName, PropertyValue::getValue));
R record = factory.fromMap(map);
return new NullSafeBindException(record, "Form");
return defaultBindParameters(m);
}

@Override
Expand Down
3 changes: 0 additions & 3 deletions api/src/org/labkey/api/action/PermissionCheckableAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,6 @@ private void _checkActionPermissions(Set<Role> contextualRoles) throws Unauthori
throw new DeprecatedActionException(actionClass);
}


private void checkPermissionsAndTermsOfUse(Set<Role> contextualRoles, boolean isSendBasic) throws UnauthorizedException
{
checkActionPermissions(contextualRoles);
Expand All @@ -267,7 +266,6 @@ private void checkPermissionsAndTermsOfUse(Set<Role> contextualRoles, boolean is
verifyTermsOfUse(isSendBasic);
}


/**
* Check if terms of use are ever required for this request. If so, enumerate all the terms-of-use providers and ask
* each to verify terms are set via its custom mechanism. If a provider's terms are active and the user hasn't yet
Expand All @@ -289,7 +287,6 @@ protected void verifyTermsOfUse(boolean isSendBasic) throws RedirectException
provider.verifyTermsOfUse(context, isBasicAuth);
}


/**
* Actions may provide a set of {@link Role}s used during permission checking
* or null if no contextual roles apply.
Expand Down
2 changes: 1 addition & 1 deletion api/src/org/labkey/api/action/SimpleViewAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ protected String getCommandClassMethodName()

public BindException bindParameters(PropertyValues pvs) throws Exception
{
return defaultBindParameters(getCommand(), pvs);
return defaultBindParameters(pvs);
}

public void validate(FORM form, BindException errors)
Expand Down
6 changes: 2 additions & 4 deletions core/src/org/labkey/core/CoreController.java
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,6 @@
import org.labkey.api.security.permissions.ReadPermission;
import org.labkey.api.security.permissions.UpdatePermission;
import org.labkey.api.security.roles.FolderAdminRole;
import org.labkey.api.security.roles.ProjectAdminRole;
import org.labkey.api.security.roles.ReaderRole;
import org.labkey.api.security.roles.RoleManager;
import org.labkey.api.services.ServiceRegistry;
Expand Down Expand Up @@ -323,7 +322,7 @@ public ActionURL getManageQCStatesURL(@NotNull Container container)
}
}

abstract static class BaseStylesheetAction extends ExportAction
abstract static class BaseStylesheetAction extends ExportAction<Object>
{
@Override
public void checkPermissions() throws UnauthorizedException
Expand Down Expand Up @@ -2872,8 +2871,7 @@ public Object execute(CustomLabelForm form, BindException errors)
{
try
{
HashMap<String, String> labels =
JsonUtil.DEFAULT_MAPPER.readValue(form.getLabelsJson(), HashMap.class);
HashMap<String, String> labels = JsonUtil.DEFAULT_MAPPER.readValue(form.getLabelsJson(), HashMap.class);
_customLabelProvider.saveLabels(labels, getContainer(), getUser());
}
catch (Exception e)
Expand Down
Loading