Skip to content
Merged
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
32 changes: 21 additions & 11 deletions api/src/main/java/jakarta/faces/component/UIInput.java
Original file line number Diff line number Diff line change
Expand Up @@ -962,17 +962,20 @@ public void setValidatorMessage(String validatorMessage)
@Override
public boolean isValid()
{
TransientStateHelper tsh = getTransientStateHelper(false);
return tsh == null || (Boolean) tsh.getTransient(PropertyKeys.valid, Boolean.TRUE);
Object value = getStateHelper().get(PropertyKeys.valid);
if (value != null)
{
return (Boolean) value;
}
return true;
}

@Override
public void setValid(boolean valid)
{
SimpleTransientStateHelper tsh = (SimpleTransientStateHelper) getTransientStateHelper(!valid);
if (tsh != null)
if (getStateHelper().get(PropertyKeys.valid) != null || !valid)
{
tsh.setOrRemoveTransient(PropertyKeys.valid, valid ? null : Boolean.FALSE);
getStateHelper().put(PropertyKeys.valid, valid );
}
}

Expand All @@ -985,17 +988,20 @@ public void setValid(boolean valid)
@Override
public boolean isLocalValueSet()
{
TransientStateHelper tsh = getTransientStateHelper(false);
return tsh != null && (Boolean) tsh.getTransient(PropertyKeys.localValueSet, Boolean.FALSE);
Object value = getStateHelper().get(PropertyKeys.localValueSet);
if (value != null)
{
return (Boolean) value;
}
return false;
}

@Override
public void setLocalValueSet(boolean localValueSet)
{
SimpleTransientStateHelper tsh = (SimpleTransientStateHelper) getTransientStateHelper(localValueSet);
if (tsh != null)
if (getStateHelper().get(PropertyKeys.localValueSet) != null || localValueSet)
{
tsh.setOrRemoveTransient(PropertyKeys.localValueSet, localValueSet ? Boolean.TRUE : null);
getStateHelper().put(PropertyKeys.localValueSet, localValueSet );
}
}

Expand Down Expand Up @@ -1071,7 +1077,11 @@ enum PropertyKeys
private static final Object[] INITIAL_STATE_PROPERTIES = new
Object[]{
UIOutput.PropertyKeys.value,
null
null,
UIInput.PropertyKeys.localValueSet,
false,
UIInput.PropertyKeys.valid,
true
};

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ public void saveState(EditableValueHolder evh)
public void restoreState(EditableValueHolder evh)
{
// UIInput.setValue always calls setLocalValueSet(true) per spec, even when value is null.
// When the snapshot has localValueSet=false that forces a transient-state add followed
// immediately by a remove — wasteful in tight UIRepeat loops.
// When the snapshot has localValueSet=false that forces a StateHelper put followed
// immediately by an overwrite — wasteful in tight UIRepeat loops.
//
// Optimisation: skip setValue entirely when both the snapshot localValue and the
// component's current localValue are null — the StateHelper is already in the correct
Expand Down Expand Up @@ -115,7 +115,6 @@ public static EditableValueHolderState create(EditableValueHolder evh)

public static boolean isEmpty(EditableValueHolder evh)
{
// Check transient values first
return evh.isValid()
&& !evh.isLocalValueSet()
&& evh.getSubmittedValue() == null
Expand Down
116 changes: 116 additions & 0 deletions impl/src/test/java/jakarta/faces/component/UIInputPSSTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 jakarta.faces.component;

import jakarta.el.ValueExpression;
import jakarta.faces.event.PhaseId;

import org.apache.myfaces.test.base.junit.AbstractJsfTestCase;
import org.apache.myfaces.test.el.MockValueExpression;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

/**
* The partial state of a {@link UIInput}: the {@code valid} and {@code localValueSet} flags qualify the local value,
* so they are saved together with it, while the submitted value is per-request state and is not saved.
* <p>
* The state is saved during Render Response, as it is for the view state, because outside that phase
* {@link UIComponentBase#saveState} also saves the transient state.
*/
public class UIInputPSSTest extends AbstractJsfTestCase
{

@Override
@BeforeEach
public void setUp() throws Exception
{
super.setUp();
facesContext.setCurrentPhaseId(PhaseId.RENDER_RESPONSE);
}

/**
* A local value saved with the view must still be pushed to the model and cleared by {@link UIInput#updateModel}
* of a later request, so that the model value is rendered afterwards.
*/
@Test
public void testLocalValueSetIsSavedWithTheLocalValue()
{
UIInput input = createInput();
input.setValue("stale");

UIInput restored = restore(input.saveState(facesContext));
Assertions.assertTrue(restored.isLocalValueSet());

restored.updateModel(facesContext);
Assertions.assertFalse(restored.isLocalValueSet());
Assertions.assertEquals("stale", facesContext.getExternalContext().getRequestMap().get("value"));
Assertions.assertEquals("stale", restored.getValue());
Assertions.assertNull(restored.saveState(facesContext));
}

@Test
public void testInvalidIsSaved()
{
UIInput input = createInput();
input.setValid(false);

UIInput restored = restore(input.saveState(facesContext));
Assertions.assertFalse(restored.isValid());
}

@Test
public void testFlagsBackAtTheirDefaultsAddNothingToTheSavedState()
{
UIInput input = createInput();
input.setValid(false);
input.setValid(true);
input.setLocalValueSet(true);
input.setLocalValueSet(false);

Assertions.assertNull(input.saveState(facesContext));
}

@Test
public void testSubmittedValueIsNotSaved()
{
UIInput input = createInput();
input.setSubmittedValue("submitted");

UIInput restored = restore(input.saveState(facesContext));
Assertions.assertNull(restored.getSubmittedValue());
}

private UIInput createInput()
{
UIInput input = new UIInput();
input.setId("input");
ValueExpression expression = new MockValueExpression("#{requestScope.value}", String.class);
input.setValueExpression("value", expression);
input.markInitialState();
return input;
}

private UIInput restore(Object state)
{
UIInput restored = createInput();
restored.restoreState(facesContext, state);
return restored;
}
}
Loading