/*
* JBoss, Home of Professional Open Source
* Copyright ${year}, Red Hat, Inc. and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.richfaces.component;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.faces.component.ContextCallback;
import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.component.UINamingContainer;
import javax.faces.component.html.HtmlForm;
import javax.faces.component.html.HtmlInputText;
import javax.faces.component.html.HtmlOutputText;
import javax.faces.component.visit.VisitCallback;
import javax.faces.component.visit.VisitContext;
import javax.faces.component.visit.VisitHint;
import javax.faces.component.visit.VisitResult;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.PhaseId;
import javax.faces.event.ValueChangeEvent;
import javax.faces.event.ValueChangeListener;
import javax.faces.model.ListDataModel;
import org.ajax4jsf.component.IterationStateHolder;
import org.ajax4jsf.model.ExtendedDataModel;
import org.ajax4jsf.model.SequenceDataModel;
import org.jboss.test.faces.AbstractFacesTest;
/**
* @author Nick Belaevski
*
*/
public class DataAdaptorTestCase extends AbstractFacesTest {
private static final String VAR_NAME = "item";
private static class TestCallback {
private int value;
public void handle() {
}
public void reset() {
value = 0;
}
public int getAndIncrement() {
return value++;
}
public int get() {
return value;
}
}
private MockDataAdaptor mockDataAdaptor;
private List<String> data;
private ExtendedDataModel<String> createDataModel() {
return new SequenceDataModel<String>(new ListDataModel<String>(new ArrayList<String>(data)));
}
private Object getVarValue() {
return facesContext.getApplication().evaluateExpressionGet(facesContext, MessageFormat.format("#'{'{0}'}'", VAR_NAME),
Object.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
data = Arrays.asList("a", "b", "c", "d");
setupFacesRequest();
mockDataAdaptor = new MockDataAdaptor();
mockDataAdaptor.setDataModel(createDataModel());
mockDataAdaptor.setVar(VAR_NAME);
facesContext.getViewRoot().getChildren().add(mockDataAdaptor);
}
@Override
public void tearDown() throws Exception {
super.tearDown();
data = null;
mockDataAdaptor = null;
}
private void resetCallbacks(TestCallback... callbacks) {
for (TestCallback callback : callbacks) {
callback.reset();
assertEquals(0, callback.get());
}
}
private UIComponent createCallbackComponent(final TestCallback callback) throws Exception {
return new HtmlOutputText() {
private void notifyCallbacks() {
callback.handle();
}
@Override
public void processDecodes(FacesContext context) {
super.processDecodes(context);
notifyCallbacks();
}
@Override
public void processValidators(FacesContext context) {
super.processValidators(context);
notifyCallbacks();
}
@Override
public void processUpdates(FacesContext context) {
super.processUpdates(context);
notifyCallbacks();
}
};
}
public void testProcessChildren() throws Exception {
TestCallback childCallback = new TestCallback() {
@Override
public void handle() {
assertEquals(getVarValue(), data.get(getAndIncrement()));
}
};
UIComponent child = createCallbackComponent(childCallback);
child.setId("child");
TestCallback facetCallback = new TestCallback() {
@Override
public void handle() {
assertEquals(getVarValue(), data.get(getAndIncrement()));
}
};
UIComponent facet = createCallbackComponent(facetCallback);
child.getFacets().put("f", facet);
facet.setId("facet");
TestCallback immediateFacetCallback = new TestCallback() {
@Override
public void handle() {
getAndIncrement();
assertNull(getVarValue());
}
};
UIComponent immediateFacet = createCallbackComponent(immediateFacetCallback);
immediateFacet.setId("immediateFacet");
mockDataAdaptor.getChildren().add(child);
mockDataAdaptor.getFacets().put("facet", immediateFacet);
mockDataAdaptor.processDecodes(facesContext);
assertEquals(data.size(), facetCallback.get());
assertEquals(data.size(), childCallback.get());
assertEquals(1, immediateFacetCallback.get());
resetCallbacks(childCallback, facetCallback, immediateFacetCallback);
mockDataAdaptor.processValidators(facesContext);
assertEquals(data.size(), facetCallback.get());
assertEquals(data.size(), childCallback.get());
assertEquals(1, immediateFacetCallback.get());
resetCallbacks(childCallback, facetCallback, immediateFacetCallback);
mockDataAdaptor.processUpdates(facesContext);
assertEquals(data.size(), facetCallback.get());
assertEquals(data.size(), childCallback.get());
assertEquals(1, immediateFacetCallback.get());
resetCallbacks(childCallback, facetCallback, immediateFacetCallback);
}
public void testSaveRestoreChildrenState() throws Exception {
HtmlForm form = new HtmlForm();
HtmlInputText input = new HtmlInputText();
IterationStateHolderComponent stateHolder = new IterationStateHolderComponent();
List<UIComponent> children = mockDataAdaptor.getChildren();
children.add(form);
form.getChildren().add(input);
form.getFacets().put("facet", stateHolder);
mockDataAdaptor.setRowKey(facesContext, Integer.valueOf(0));
assertFalse(form.isSubmitted());
assertNull(input.getSubmittedValue());
assertNull(input.getLocalValue());
assertTrue(input.isValid());
assertFalse(input.isLocalValueSet());
assertNull(stateHolder.getIterationState());
form.setSubmitted(true);
input.setSubmittedValue("user input");
input.setValue("component value");
input.setValid(false);
input.setLocalValueSet(true);
stateHolder.setIterationState("state");
mockDataAdaptor.setRowKey(facesContext, Integer.valueOf(1));
assertFalse(form.isSubmitted());
assertNull(input.getSubmittedValue());
assertNull(input.getLocalValue());
assertTrue(input.isValid());
assertFalse(input.isLocalValueSet());
assertNull(stateHolder.getIterationState());
input.setSubmittedValue("another input from user");
input.setValue("123");
assertTrue(input.isLocalValueSet());
stateHolder.setIterationState("456");
mockDataAdaptor.setRowKey(facesContext, Integer.valueOf(0));
assertTrue(form.isSubmitted());
assertEquals("user input", input.getSubmittedValue());
assertEquals("component value", input.getLocalValue());
assertFalse(input.isValid());
assertTrue(input.isLocalValueSet());
assertEquals("state", stateHolder.getIterationState());
mockDataAdaptor.setRowKey(facesContext, Integer.valueOf(1));
assertFalse(form.isSubmitted());
assertEquals("another input from user", input.getSubmittedValue());
assertEquals("123", input.getLocalValue());
assertTrue(input.isValid());
assertTrue(input.isLocalValueSet());
assertEquals("456", stateHolder.getIterationState());
mockDataAdaptor.setRowKey(facesContext, null);
assertFalse(form.isSubmitted());
assertNull(input.getSubmittedValue());
assertNull(input.getLocalValue());
assertTrue(input.isValid());
assertFalse(input.isLocalValueSet());
assertNull(stateHolder.getIterationState());
}
public void testSaveRestoreChildrenStateNestedDataAdaptors() throws Exception {
MockDataAdaptor childAdaptor = new MockDataAdaptor();
childAdaptor.setDataModel(createDataModel());
HtmlInputText input = new HtmlInputText();
mockDataAdaptor.getChildren().add(childAdaptor);
childAdaptor.getChildren().add(input);
Integer rowKey = Integer.valueOf(2);
Integer childKey = Integer.valueOf(1);
mockDataAdaptor.setRowKey(facesContext, rowKey);
childAdaptor.setRowKey(facesContext, childKey);
assertNull(input.getSubmittedValue());
assertNull(input.getLocalValue());
assertTrue(input.isValid());
assertFalse(input.isLocalValueSet());
input.setSubmittedValue("submittedValue");
input.setValue("value");
childAdaptor.setRowKey(facesContext, null);
mockDataAdaptor.setRowKey(facesContext, Integer.valueOf(3));
childAdaptor.setRowKey(facesContext, Integer.valueOf(0));
assertNull(input.getSubmittedValue());
assertNull(input.getLocalValue());
assertFalse(input.isLocalValueSet());
childAdaptor.setRowKey(facesContext, null);
mockDataAdaptor.setRowKey(facesContext, rowKey);
childAdaptor.setRowKey(facesContext, childKey);
assertEquals("submittedValue", input.getSubmittedValue());
assertEquals("value", input.getLocalValue());
assertTrue(input.isValid());
assertTrue(input.isLocalValueSet());
}
public void testEventsQueueing() throws Exception {
HtmlInputText input = new HtmlInputText();
final TestCallback testCallback = new TestCallback();
input.addValueChangeListener(new ValueChangeListener() {
public void processValueChange(ValueChangeEvent event) throws AbortProcessingException {
testCallback.getAndIncrement();
assertEquals(data.get(1), getVarValue());
}
});
mockDataAdaptor.getChildren().add(input);
mockDataAdaptor.setRowKey(facesContext, Integer.valueOf(1));
assertEquals(data.get(1), getVarValue());
new ValueChangeEvent(input, null, "testValue").queue();
mockDataAdaptor.setRowKey(facesContext, null);
facesContext.getViewRoot().broadcastEvents(facesContext, PhaseId.PROCESS_VALIDATIONS);
assertEquals(1, testCallback.get());
}
public void testInvokeOnComponent() throws Exception {
final HtmlInputText facet = new HtmlInputText();
final HtmlInputText child = new HtmlInputText();
mockDataAdaptor.getFacets().put("facet", facet);
mockDataAdaptor.getChildren().add(child);
mockDataAdaptor.setId("_data");
facet.setId("_facet");
child.setId("_child");
boolean invocationResult;
final TestCallback callback = new TestCallback();
invocationResult = mockDataAdaptor.invokeOnComponent(facesContext, "_data", new ContextCallback() {
public void invokeContextCallback(FacesContext context, UIComponent target) {
callback.getAndIncrement();
assertEquals(mockDataAdaptor, target);
assertEquals("_data", target.getClientId());
}
});
assertTrue(invocationResult);
assertEquals(1, callback.get());
callback.reset();
final char separatorChar = UINamingContainer.getSeparatorChar(facesContext);
invocationResult = mockDataAdaptor.invokeOnComponent(facesContext, "_data" + separatorChar + "_facet",
new ContextCallback() {
public void invokeContextCallback(FacesContext context, UIComponent target) {
callback.getAndIncrement();
assertEquals(facet, target);
assertEquals("_data" + separatorChar + "_facet", target.getClientId());
}
});
assertTrue(invocationResult);
assertEquals(1, callback.get());
callback.reset();
invocationResult = mockDataAdaptor.invokeOnComponent(facesContext, "_data" + separatorChar + "2" + separatorChar
+ "_child", new ContextCallback() {
public void invokeContextCallback(FacesContext context, UIComponent target) {
callback.getAndIncrement();
assertEquals(child, target);
assertEquals(data.get(2), getVarValue());
assertEquals("_data" + separatorChar + "2" + separatorChar + "_child", target.getClientId());
}
});
assertTrue(invocationResult);
assertEquals(1, callback.get());
callback.reset();
invocationResult = mockDataAdaptor.invokeOnComponent(facesContext, "_data" + separatorChar + "100" + separatorChar
+ "_child", new ContextCallback() {
public void invokeContextCallback(FacesContext context, UIComponent target) {
fail();
}
});
assertFalse(invocationResult);
invocationResult = mockDataAdaptor.invokeOnComponent(facesContext, "_data" + separatorChar + "nonExistentComponent",
new ContextCallback() {
public void invokeContextCallback(FacesContext context, UIComponent target) {
fail();
}
});
assertFalse(invocationResult);
}
public void testVisitChildren() throws Exception {
final HtmlInputText facet = new HtmlInputText();
final HtmlInputText child = new HtmlInputText();
mockDataAdaptor.getFacets().put("facet", facet);
mockDataAdaptor.getChildren().add(child);
mockDataAdaptor.setId("_data");
facet.setId("_facet");
child.setId("_child");
VisitContext fullVisitContext = VisitContext.createVisitContext(facesContext);
final char separatorChar = UINamingContainer.getSeparatorChar(facesContext);
final Set<String> idsToVisit = new HashSet<String>();
idsToVisit.add("_data" + separatorChar + "_facet");
idsToVisit.add("_data" + separatorChar + "0" + separatorChar + "_child");
idsToVisit.add("_data" + separatorChar + "2" + separatorChar + "_child");
VisitContext partialVisitContext = VisitContext.createVisitContext(facesContext, idsToVisit,
EnumSet.of(VisitHint.SKIP_UNRENDERED));
final TestCallback callback = new TestCallback();
mockDataAdaptor.visitTree(fullVisitContext, new VisitCallback() {
public VisitResult visit(VisitContext context, UIComponent target) {
callback.getAndIncrement();
assertNotNull(target);
return VisitResult.ACCEPT;
}
});
assertEquals(1 /* adaptor itself */+ 1 /* facet */+ data.size(), callback.get());
callback.reset();
mockDataAdaptor.visitTree(partialVisitContext, new VisitCallback() {
public VisitResult visit(VisitContext context, UIComponent target) {
callback.getAndIncrement();
assertNotNull(target);
assertTrue(idsToVisit.contains(target.getClientId()));
return VisitResult.ACCEPT;
}
});
assertEquals(idsToVisit.size(), callback.get());
callback.reset();
mockDataAdaptor.visitTree(fullVisitContext, new VisitCallback() {
public VisitResult visit(VisitContext context, UIComponent target) {
callback.getAndIncrement();
if (child.equals(target)
&& child.getClientId().equals("_data" + separatorChar + "1" + separatorChar + "_child")) {
return VisitResult.COMPLETE;
}
return VisitResult.ACCEPT;
}
});
assertEquals(1 /* data adaptor */+ 1 /* facet */+ 2 /* [0..1] children */, callback.get());
}
}
class IterationStateHolderComponent extends UIComponentBase implements IterationStateHolder {
private Object iterationState;
@Override
public String getFamily() {
return "test.Component";
}
public Object getIterationState() {
return iterationState;
}
public void setIterationState(Object state) {
iterationState = state;
}
}