blob: c35c5144b823307f409626bbebb7243b304b1eeb [file] [log] [blame]
// Copyright 2006, 2007, 2008 The Apache Software Foundation
//
// 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.apache.tapestry5.internal.transform;
import javassist.CtClass;
import javassist.Loader;
import javassist.LoaderClassPath;
import org.apache.tapestry5.Binding;
import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.internal.InternalComponentResources;
import org.apache.tapestry5.internal.services.Instantiator;
import org.apache.tapestry5.internal.services.InternalClassTransformation;
import org.apache.tapestry5.internal.services.InternalClassTransformationImpl;
import org.apache.tapestry5.internal.test.InternalBaseTestCase;
import org.apache.tapestry5.internal.transform.components.DefaultParameterBindingMethodComponent;
import org.apache.tapestry5.internal.transform.components.DefaultParameterComponent;
import org.apache.tapestry5.internal.transform.components.ParameterComponent;
import org.apache.tapestry5.ioc.internal.services.ClassFactoryClassPool;
import org.apache.tapestry5.ioc.internal.services.ClassFactoryImpl;
import org.apache.tapestry5.ioc.internal.services.PropertyAccessImpl;
import org.apache.tapestry5.ioc.services.ClassFactory;
import org.apache.tapestry5.ioc.services.PropertyAccess;
import org.apache.tapestry5.model.MutableComponentModel;
import org.apache.tapestry5.runtime.Component;
import org.apache.tapestry5.services.BindingSource;
import org.slf4j.Logger;
import org.testng.annotations.AfterClass;
import org.testng.annotations.Test;
/**
* There's no point in trying to unit test the code generated by {@link org.apache.tapestry5.internal.transform.ParameterWorker}.
* Instead, we excercize ParameterWorker, and test that the generated code works correctly in a number of scenarios.
*/
public class ParameterWorkerTest extends InternalBaseTestCase
{
private final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
private PropertyAccess access = new PropertyAccessImpl();
/**
* Accessed by DefaultParameterBindingMethodComponent.
*/
public static Binding _binding;
@AfterClass
public void cleanup()
{
access = null;
_binding = null;
}
@Test
public void page_load_behavior() throws Exception
{
InternalComponentResources resources = mockInternalComponentResources();
assertNotNull(setupForIntegrationTest(resources));
}
@Test
public void invariant_object_retained_after_detach() throws Exception
{
InternalComponentResources resources = mockInternalComponentResources();
Component component = setupForIntegrationTest(resources);
// On first invocation, the resources are queried.
String value = "To be in Tapestry in the spring time ...";
train_isLoaded(resources, true);
train_isBound(resources, "invariantObject", true);
train_readParameter(resources, "invariantObject", String.class, value);
replay();
assertSame(access.get(component, "invariantObject"), value);
verify();
// No further training needed here.
replay();
// Still cached ...
assertSame(access.get(component, "invariantObject"), value);
component.postRenderCleanup();
// Still cached ...
assertSame(access.get(component, "invariantObject"), value);
component.containingPageDidDetach();
// Still cached ...
assertSame(access.get(component, "invariantObject"), value);
verify();
}
@Test
public void invariant_primitive_retained_after_detach() throws Exception
{
InternalComponentResources resources = mockInternalComponentResources();
Component component = setupForIntegrationTest(resources);
// On first invocation, the resources are queried.
long value = 123456;
train_isLoaded(resources, true);
train_isBound(resources, "invariantPrimitive", true);
train_readParameter(resources, "invariantPrimitive", Long.class, value);
replay();
assertEquals(access.get(component, "invariantPrimitive"), value);
verify();
// No further training needed here.
replay();
// Still cached ...
assertEquals(access.get(component, "invariantPrimitive"), value);
component.postRenderCleanup();
// Still cached ...
assertEquals(access.get(component, "invariantPrimitive"), value);
verify();
}
/**
* This actually checks several things: <ul> <li>Changing a parameter property before the page loads doesn't update
* the binding</li> <li>Changing a parameter property changes the property AND the default value for the
* property</li> <li>Unbound parameters to do not attempt to read or update their bindings (they'll be
* optional)</li> </ul>
*
* @throws Exception
*/
@Test
public void changes_before_load_become_defaults_and_dont_update_bindings() throws Exception
{
InternalComponentResources resources = mockInternalComponentResources();
Component component = setupForIntegrationTest(resources);
train_isLoaded(resources, false);
replay();
assertNull(access.get(component, "object"));
verify();
train_isLoaded(resources, false);
replay();
access.set(component, "object", "new-default");
verify();
train_isLoaded(resources, false);
replay();
assertEquals(access.get(component, "object"), "new-default");
verify();
trainForPageDidLoad(resources);
replay();
component.containingPageDidLoad();
verify();
// For the set ...
train_isLoaded(resources, true);
train_isBound(resources, "object", false);
train_isRendering(resources, false);
// For the first read ...
train_isLoaded(resources, true);
train_isBound(resources, "object", false);
// For the second read (after postRenderCleanup) ...
train_isLoaded(resources, true);
train_isBound(resources, "object", false);
replay();
access.set(component, "object", "new-value");
assertEquals(access.get(component, "object"), "new-value");
component.postRenderCleanup();
assertEquals(access.get(component, "object"), "new-default");
verify();
}
@Test
public void cached_object_read() throws Exception
{
InternalComponentResources resources = mockInternalComponentResources();
Component component = setupForIntegrationTest(resources);
train_isLoaded(resources, true);
train_isBound(resources, "object", true);
train_readParameter(resources, "object", String.class, "first");
train_isRendering(resources, false);
replay();
assertEquals(access.get(component, "object"), "first");
verify();
// Keeps re-reading the parameter when not rendering.
train_isLoaded(resources, true);
train_isBound(resources, "object", true);
train_readParameter(resources, "object", String.class, "second");
train_isRendering(resources, false);
replay();
assertEquals(access.get(component, "object"), "second");
verify();
// Now, when rendering is active, the value is cached
train_isLoaded(resources, true);
train_isBound(resources, "object", true);
train_readParameter(resources, "object", String.class, "third");
train_isRendering(resources, true);
replay();
assertEquals(access.get(component, "object"), "third");
// Does not cause readParameter() to be invoked:
assertEquals(access.get(component, "object"), "third");
verify();
train_isLoaded(resources, true);
train_isBound(resources, "object", true);
train_readParameter(resources, "object", String.class, "fourth");
train_isRendering(resources, false);
replay();
component.postRenderCleanup();
assertEquals(access.get(component, "object"), "fourth");
verify();
}
@Test
public void cached_object_write() throws Exception
{
InternalComponentResources resources = mockInternalComponentResources();
Component component = setupForIntegrationTest(resources);
train_isLoaded(resources, true);
train_isBound(resources, "object", true);
resources.writeParameter("object", "first");
train_isRendering(resources, false);
train_isLoaded(resources, true);
train_isBound(resources, "object", true);
train_readParameter(resources, "object", String.class, "second");
train_isRendering(resources, false);
replay();
access.set(component, "object", "first");
assertEquals(access.get(component, "object"), "second");
verify();
// Now try during rendering ...
train_isLoaded(resources, true);
train_isBound(resources, "object", true);
resources.writeParameter("object", "third");
train_isRendering(resources, true);
replay();
access.set(component, "object", "third");
assertEquals(access.get(component, "object"), "third");
verify();
// And the cached value is lost after rendering is complete.
train_isLoaded(resources, true);
train_isBound(resources, "object", true);
train_readParameter(resources, "object", String.class, "fourth");
train_isRendering(resources, false);
replay();
component.postRenderCleanup();
assertEquals(access.get(component, "object"), "fourth");
verify();
}
@Test
public void cached_primitive_write() throws Exception
{
InternalComponentResources resources = mockInternalComponentResources();
Component component = setupForIntegrationTest(resources);
train_isLoaded(resources, true);
train_isBound(resources, "primitive", true);
resources.writeParameter("primitive", 321);
train_isRendering(resources, false);
train_isLoaded(resources, true);
train_isBound(resources, "primitive", true);
train_readParameter(resources, "primitive", Integer.class, 123);
train_isRendering(resources, false);
replay();
access.set(component, "primitive", 321);
assertEquals(access.get(component, "primitive"), 123);
verify();
// Now try during rendering ...
train_isLoaded(resources, true);
train_isBound(resources, "primitive", true);
resources.writeParameter("primitive", 567);
train_isRendering(resources, true);
replay();
access.set(component, "primitive", 567);
assertEquals(access.get(component, "primitive"), 567);
verify();
// And the cached value is lost after rendering is complete.
train_isLoaded(resources, true);
train_isBound(resources, "primitive", true);
train_readParameter(resources, "primitive", Integer.class, 890);
train_isRendering(resources, false);
replay();
component.postRenderCleanup();
assertEquals(access.get(component, "primitive"), 890);
verify();
}
@Test
public void uncached_object_read() throws Exception
{
InternalComponentResources resources = mockInternalComponentResources();
Component component = setupForIntegrationTest(resources);
// Notice no check for isRendering() since that is irrelevant to uncached parameters.
// Also note difference between field name and parameter name, due to Parameter.name() being
// specified.
train_isLoaded(resources, true);
train_isBound(resources, "uncached", true);
train_readParameter(resources, "uncached", String.class, "first");
train_isLoaded(resources, true);
train_isBound(resources, "uncached", true);
train_readParameter(resources, "uncached", String.class, "second");
replay();
assertEquals(access.get(component, "uncachedObject"), "first");
assertEquals(access.get(component, "uncachedObject"), "second");
verify();
}
protected void train_isBound(InternalComponentResources resources, String parameterName, boolean isBound)
{
expect(resources.isBound(parameterName)).andReturn(isBound);
}
@Test
public void uncached_object_write() throws Exception
{
InternalComponentResources resources = mockInternalComponentResources();
Component component = setupForIntegrationTest(resources);
// Notice no check for isRendering() since that is irrelevant to uncached parameters.
// Also note difference between field name and parameter name, due to Parameter.name() being
// specified.
train_isLoaded(resources, true);
train_isBound(resources, "uncached", true);
resources.writeParameter("uncached", "first");
train_isLoaded(resources, true);
train_isBound(resources, "uncached", true);
train_readParameter(resources, "uncached", String.class, "second");
replay();
access.set(component, "uncachedObject", "first");
assertEquals(access.get(component, "uncachedObject"), "second");
verify();
}
@Test
public void parameter_with_default() throws Exception
{
final BindingSource source = mockBindingSource();
final InternalComponentResources resources = mockInternalComponentResources();
final Binding binding = mockBinding();
String boundValue = "howdy!";
final Logger logger = mockLogger();
MutableComponentModel model = mockMutableComponentModel(logger);
model.addParameter("value", false, BindingConstants.PROP);
Runnable phaseTwoTraining = new Runnable()
{
public void run()
{
train_isBound(resources, "value", false);
expect(source.newBinding("default value", resources, BindingConstants.PROP,
"literal:greeting")).andReturn(binding);
resources.bindParameter("value", binding);
train_isInvariant(resources, "value", true);
stub_isDebugEnabled(logger, false);
}
};
Component component = setupForIntegrationTest(resources, logger, DefaultParameterComponent.class.getName(),
model, source, phaseTwoTraining);
train_isLoaded(resources, true);
train_isBound(resources, "value", true);
train_readParameter(resources, "value", String.class, boundValue);
stub_isDebugEnabled(logger, false);
replay();
assertEquals(access.get(component, "value"), boundValue);
verify();
}
@Test
public void default_binding_method() throws Exception
{
BindingSource source = mockBindingSource();
final InternalComponentResources resources = mockInternalComponentResources();
_binding = mockBinding();
String boundValue = "yowza!";
final Logger logger = mockLogger();
MutableComponentModel model = mockMutableComponentModel(logger);
model.addParameter("value", false, BindingConstants.PROP);
Runnable phaseTwoTraining = new Runnable()
{
public void run()
{
train_isBound(resources, "value", false);
// How can this happen? Only if the generated code invokes defaultValue().
resources.bindParameter("value", _binding);
train_isInvariant(resources, "value", true);
stub_isDebugEnabled(logger, false);
}
};
Component component = setupForIntegrationTest(resources, logger,
DefaultParameterBindingMethodComponent.class.getName(), model,
source, phaseTwoTraining);
train_isLoaded(resources, true);
train_isBound(resources, "value", true);
train_readParameter(resources, "value", String.class, boundValue);
stub_isDebugEnabled(logger, false);
replay();
assertEquals(access.get(component, "value"), boundValue);
verify();
}
protected final void train_isRendering(InternalComponentResources resources, boolean rendering)
{
expect(resources.isRendering()).andReturn(rendering);
}
protected final <T> void train_readParameter(InternalComponentResources resources, String parameterName,
Class<T> expectedType, T value)
{
expect(resources.readParameter(parameterName, expectedType.getName())).andReturn(value);
}
/**
* This is for the majority of tests.
*/
private Component setupForIntegrationTest(final InternalComponentResources resources) throws Exception
{
final Logger logger = mockLogger();
MutableComponentModel model = mockMutableComponentModel(logger);
model.addParameter("invariantObject", false, BindingConstants.PROP);
model.addParameter("invariantPrimitive", false, BindingConstants.PROP);
model.addParameter("object", false, BindingConstants.PROP);
model.addParameter("primitive", true, BindingConstants.PROP);
model.addParameter("uncached", false, BindingConstants.LITERAL);
Runnable phaseTwoTraining = new Runnable()
{
public void run()
{
trainForPageDidLoad(resources);
stub_isDebugEnabled(logger, false);
}
};
stub_isDebugEnabled(logger, false);
return setupForIntegrationTest(resources, logger, ParameterComponent.class.getName(), model,
mockBindingSource(), phaseTwoTraining);
}
private Component setupForIntegrationTest(InternalComponentResources resources, Logger logger,
String componentClassName, MutableComponentModel model,
BindingSource source, Runnable phaseTwoTraining) throws Exception
{
ClassFactoryClassPool pool = new ClassFactoryClassPool(contextClassLoader);
Loader loader = new TestPackageAwareLoader(contextClassLoader, pool);
pool.appendClassPath(new LoaderClassPath(loader));
ClassFactory cf = new ClassFactoryImpl(loader, pool, logger);
CtClass ctClass = pool.get(componentClassName);
replay();
InternalClassTransformation transformation = new InternalClassTransformationImpl(cf, ctClass, null, model,
null);
new ParameterWorker(source).transform(transformation, model);
verify();
phaseTwoTraining.run();
replay();
transformation.finish();
Instantiator instantiator = transformation.createInstantiator();
Component component = instantiator.newInstance(resources);
component.containingPageDidLoad();
verify();
return component;
}
private void trainForPageDidLoad(InternalComponentResources resources)
{
train_isInvariant(resources, "invariantObject", true);
train_isInvariant(resources, "invariantPrimitive", true);
train_isInvariant(resources, "object", false);
train_isInvariant(resources, "primitive", false);
train_isInvariant(resources, "uncached", false);
}
protected final void train_isInvariant(InternalComponentResources resources, String parameterName,
boolean invariant)
{
expect(resources.isInvariant(parameterName)).andReturn(invariant);
}
}