blob: 516051059c565ecc45957036e255acf0da26c45a [file] [log] [blame]
// Copyright 2004, 2005 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.tapestry.pageload;
import org.apache.commons.logging.Log;
import org.apache.hivemind.ApplicationRuntimeException;
import org.apache.hivemind.ClassResolver;
import org.apache.hivemind.HiveMind;
import org.apache.hivemind.Location;
import org.apache.hivemind.service.ThreadLocale;
import org.apache.tapestry.*;
import org.apache.tapestry.asset.AssetSource;
import org.apache.tapestry.binding.BindingSource;
import org.apache.tapestry.engine.IPageLoader;
import org.apache.tapestry.resolver.ComponentSpecificationResolver;
import org.apache.tapestry.services.ComponentConstructor;
import org.apache.tapestry.services.ComponentConstructorFactory;
import org.apache.tapestry.services.ComponentPropertySource;
import org.apache.tapestry.services.ComponentTemplateLoader;
import org.apache.tapestry.spec.*;
import java.util.*;
/**
* Implementation of tapestry.page.PageLoader. Runs the process of building the
* component hierarchy for an entire page.
* <p>
* This implementation is not threadsafe, therefore the pooled service model
* must be used.
* </p>
*
* @author Howard Lewis Ship
*/
public class PageLoader implements IPageLoader {
private Log _log;
/** @since 4.0 */
private ComponentSpecificationResolver _componentResolver;
/** @since 4.0 */
private BindingSource _bindingSource;
/** @since 4.0 */
private ComponentTemplateLoader _componentTemplateLoader;
private List _inheritedBindingQueue = new ArrayList();
/** @since 4.0 */
private IComponentVisitor _establishDefaultParameterValuesVisitor;
private ComponentTreeWalker _establishDefaultParameterValuesWalker;
private ComponentTreeWalker _verifyRequiredParametersWalker;
private IComponentVisitor _eventConnectionVisitor;
private ComponentTreeWalker _eventConnectionWalker;
private IComponentVisitor _componentTypeVisitor;
/** @since 4.0 */
private ComponentConstructorFactory _componentConstructorFactory;
/** @since 4.0 */
private AssetSource _assetSource;
/**
* Used to find the correct Java component class for a page.
*
* @since 4.0
*/
private ComponentClassProvider _pageClassProvider;
/**
* Used to find the correct Java component class for a component (a similar
* process to resolving a page, but with slightly differen steps and
* defaults).
*
* @since 4.0
*/
private ComponentClassProvider _componentClassProvider;
/**
* Used to resolve meta-data properties related to a component.
*
* @since 4.0
*/
private ComponentPropertySource _componentPropertySource;
/**
* Tracks the current locale into which pages are loaded.
*
* @since 4.0
*/
private ThreadLocale _threadLocale;
/**
* The locale of the application, which is also the locale of the page being
* loaded.
*/
private Locale _locale;
/**
* Number of components instantiated, excluding the page itself.
*/
private int _count;
/**
* The recursion depth. A page with no components is zero. A component on a
* page is one.
*/
private int _depth;
/**
* The maximum depth reached while building the page.
*/
private int _maxDepth;
/** @since 4.0 */
private ClassResolver _classResolver;
/**
* As each component is constructed it is placed on to the component stack, when construction is finished it is pushed
* back off the stack. This helps in detecting component nesting and properly reporting errors.
*/
private Stack _componentStack = new Stack();
public void initializeService()
{
// Create the mechanisms for walking the component tree when it is
// complete
IComponentVisitor verifyRequiredParametersVisitor = new VerifyRequiredParametersVisitor();
_verifyRequiredParametersWalker =
new ComponentTreeWalker( new IComponentVisitor[] { verifyRequiredParametersVisitor });
_establishDefaultParameterValuesWalker =
new ComponentTreeWalker( new IComponentVisitor[] { _establishDefaultParameterValuesVisitor });
_eventConnectionWalker =
new ComponentTreeWalker( new IComponentVisitor[] { _eventConnectionVisitor, _componentTypeVisitor });
}
/**
* Binds properties of the component as defined by the container's
* specification.
* <p>
* This implementation is very simple, we will need a lot more sanity
* checking and eror checking in the final version.
*
* @param container
* The containing component. For a dynamic binding ({@link org.apache.tapestry.binding.ExpressionBinding})
* the property name is evaluated with the container as the root.
* @param component
* The contained component being bound.
* @param contained
* The contained component specification (from the container's
* {@link IComponentSpecification}).
* @param defaultBindingPrefix
* The default binding prefix to be used with the component.
*/
void bind(IComponent container, IComponent component,
IContainedComponent contained, String defaultBindingPrefix)
{
IComponentSpecification spec = component.getSpecification();
boolean formalOnly = !spec.getAllowInformalParameters();
if (contained.getInheritInformalParameters())
{
if (formalOnly)
throw new ApplicationRuntimeException(PageloadMessages.inheritInformalInvalidComponentFormalOnly(component),
component, contained.getLocation(), null);
IComponentSpecification containerSpec = container.getSpecification();
if (!containerSpec.getAllowInformalParameters())
throw new ApplicationRuntimeException(PageloadMessages.inheritInformalInvalidContainerFormalOnly(container, component),
component, contained.getLocation(), null);
IQueuedInheritedBinding queued = new QueuedInheritInformalBindings(component);
_inheritedBindingQueue.add(queued);
}
Iterator i = contained.getBindingNames().iterator();
while(i.hasNext())
{
String name = (String) i.next();
IParameterSpecification pspec = spec.getParameter(name);
boolean isFormal = pspec != null;
String parameterName = isFormal ? pspec.getParameterName() : name;
IBindingSpecification bspec = contained.getBinding(name);
// If not allowing informal parameters, check that each binding
// matches
// a formal parameter.
if (formalOnly && !isFormal)
throw new ApplicationRuntimeException(PageloadMessages.formalParametersOnly(component, name),
component, bspec.getLocation(), null);
// If an informal parameter that conflicts with a reserved name,
// then skip it.
if (!isFormal && spec.isReservedParameterName(name))
continue;
if (isFormal)
{
if (!name.equals(parameterName))
{
_log.warn(PageloadMessages.usedParameterAlias(contained, name, parameterName, bspec.getLocation()));
}
else if (pspec.isDeprecated())
_log.warn(PageloadMessages.deprecatedParameter(name, bspec.getLocation(), contained.getType()));
}
// The type determines how to interpret the value:
// As a simple static String
// As a nested property name (relative to the component)
// As the name of a binding inherited from the containing component.
// As the name of a public field
// As a script for a listener
BindingType type = bspec.getType();
// For inherited bindings, defer until later. This gives components
// a chance to setup bindings from static values and expressions in
// the template. The order of operations is tricky, template
// bindings
// come later. Note that this is a hold over from the Tapestry 3.0
// DTD
// and will some day no longer be supported.
if (type == BindingType.INHERITED)
{
QueuedInheritedBinding queued = new QueuedInheritedBinding(component, bspec.getValue(), parameterName);
_inheritedBindingQueue.add(queued);
continue;
}
String description = PageloadMessages.parameterName(name);
IBinding binding = convert(container, description, pspec, defaultBindingPrefix, bspec);
addBindingToComponent(component, parameterName, binding);
}
}
/**
* Adds a binding to the component, checking to see if there's a name
* conflict (an existing binding for the same parameter ... possibly because
* parameter names can be aliased).
*
* @param component
* to which the binding should be added
* @param parameterName
* the name of the parameter to bind, which should be a true
* name, not an alias
* @param binding
* the binding to add
* @throws ApplicationRuntimeException
* if a binding already exists
* @since 4.0
*/
static void addBindingToComponent(IComponent component, String parameterName, IBinding binding)
{
IBinding existing = component.getBinding(parameterName);
if (existing != null)
throw new ApplicationRuntimeException(PageloadMessages.duplicateParameter(parameterName, existing),
component, binding.getLocation(), null);
component.setBinding(parameterName, binding);
}
private IBinding convert(IComponent container, String description, IParameterSpecification param,
String defaultBindingType, IBindingSpecification spec)
{
Location location = spec.getLocation();
String bindingReference = spec.getValue();
return _bindingSource.createBinding(container, param, description,
bindingReference, defaultBindingType, location);
}
/**
* Sets up a component. This involves:
* <ul>
* <li>Instantiating any contained components.
* <li>Add the contained components to the container.
* <li>Setting up bindings between container and containees.
* <li>Construct the containees recursively.
* <li>Invoking
* {@link IComponent#finishLoad(IRequestCycle, IPageLoader, IComponentSpecification)}
* </ul>
*
* @param cycle
* the request cycle for which the page is being (initially)
* constructed
* @param page
* The page on which the container exists.
* @param container
* The component to be set up.
* @param containerSpec
* The specification for the container.
* @param namespace
* The namespace of the container
*/
private void constructComponent(IRequestCycle cycle, IPage page,
IComponent container, IComponentSpecification containerSpec,
INamespace namespace)
{
_depth++;
if (_depth > _maxDepth)
_maxDepth = _depth;
beginConstructComponent(container, containerSpec);
String defaultBindingPrefix = _componentPropertySource.getComponentProperty(container, TapestryConstants.DEFAULT_BINDING_PREFIX_NAME);
List ids = new ArrayList(containerSpec.getComponentIds());
int count = ids.size();
try
{
for(int i = 0; i < count; i++)
{
String id = (String) ids.get(i);
// Get the sub-component specification from the
// container's specification.
IContainedComponent contained = containerSpec.getComponent(id);
String type = contained.getType();
Location location = contained.getLocation();
_componentResolver.resolve(cycle, namespace, type, location);
IComponentSpecification componentSpecification = _componentResolver.getSpecification();
INamespace componentNamespace = _componentResolver.getNamespace();
// Instantiate the contained component.
IComponent component = instantiateComponent(page, container,
id, componentSpecification, _componentResolver.getType(), componentNamespace, contained);
// Add it, by name, to the container.
container.addComponent(component);
// Set up any bindings in the IContainedComponent specification
bind(container, component, contained, defaultBindingPrefix);
// Now construct the component recusively; it gets its chance
// to create its subcomponents and set their bindings.
constructComponent(cycle, page, component, componentSpecification, componentNamespace);
}
addAssets(container, containerSpec);
// Finish the load of the component; most components (which
// subclass BaseComponent) load their templates here.
// Properties with initial values will be set here (or the
// initial value will be recorded for later use in pageDetach().
// That may cause yet more components to be created, and more
// bindings to be set, so we defer some checking until later.
container.finishLoad(cycle, this, containerSpec);
// Have the component switch over to its active state.
container.enterActiveState();
}
catch (ApplicationRuntimeException ex)
{
throw ex;
}
catch (RuntimeException ex)
{
throw new ApplicationRuntimeException(PageloadMessages.unableToInstantiateComponent(container, ex),
container, null, ex);
} finally {
endConstructComponent(container);
}
_depth--;
}
/**
* Checks the component stack to ensure that the specified component hasn't been improperly nested
* and referenced recursively within itself.
*
* @param component
* The component to add to the current component stack and check for recursion.
* @param specification
* The specification of the specified component.
*/
void beginConstructComponent(IComponent component, IComponentSpecification specification)
{
// check recursion
int position = _componentStack.search(component);
if (position > -1)
{
Location location = specification.getLocation();
// try to get the more precise container position location that was referenced
// in the template to properly report the precise position of the recursive reference
IContainedComponent container = component.getContainedComponent();
if (container != null)
location = container.getLocation();
throw new ApplicationRuntimeException(PageloadMessages.recursiveComponent(component), location, null);
}
_componentStack.push(component);
}
/**
* Pops the current component off the stack.
*
* @param component
* The component that has just been constructed.
*/
void endConstructComponent(IComponent component)
{
_componentStack.pop();
}
/**
* Invoked to create an implicit component (one which is defined in the
* containing component's template, rather that in the containing
* component's specification).
*
* @see org.apache.tapestry.services.impl.ComponentTemplateLoaderImpl
* @since 3.0
*/
public IComponent createImplicitComponent(IRequestCycle cycle,
IComponent container, String componentId, String componentType,
Location location)
{
IPage page = container.getPage();
_componentResolver.resolve(cycle, container.getNamespace(), componentType, location);
INamespace componentNamespace = _componentResolver.getNamespace();
IComponentSpecification spec = _componentResolver.getSpecification();
IContainedComponent contained = new ContainedComponent();
contained.setLocation(location);
contained.setType(componentType);
IComponent result = instantiateComponent(page, container, componentId,
spec, _componentResolver.getType(), componentNamespace,
contained);
container.addComponent(result);
// Recusively build the component.
constructComponent(cycle, page, result, spec, componentNamespace);
return result;
}
/**
* Instantiates a component from its specification. We instantiate the
* component object, then set its specification, page, container and id.
*
* @param page
* The page component is to be attached to.
* @param container
* The containing component.
* @param id
* The components unique id
* @param spec
* The specification for the component
* @param type
* The type (ie Any / For / DirectLink)
* @param namespace
* Which namespace / library
* @param containedComponent
* Possible contained component.
*
* @return The instantiated component instance.
*
* @see org.apache.tapestry.AbstractComponent
*/
private IComponent instantiateComponent(IPage page, IComponent container,
String id, IComponentSpecification spec, String type,
INamespace namespace, IContainedComponent containedComponent)
{
ComponentClassProviderContext context = new ComponentClassProviderContext(type, spec, namespace);
String className = _componentClassProvider.provideComponentClassName(context);
if (HiveMind.isBlank(className))
className = BaseComponent.class.getName();
else
{
Class componentClass = _classResolver.findClass(className);
if (!IComponent.class.isAssignableFrom(componentClass))
throw new ApplicationRuntimeException(PageloadMessages.classNotComponent(componentClass),
container, spec.getLocation(), null);
if (IPage.class.isAssignableFrom(componentClass))
throw new ApplicationRuntimeException(PageloadMessages.pageNotAllowed(id),
container, spec.getLocation(), null);
}
ComponentConstructor cc = _componentConstructorFactory.getComponentConstructor(spec, className);
IComponent result = (IComponent) cc.newInstance();
result.setNamespace(namespace);
result.setPage(page);
result.setContainer(container);
result.setId(id);
result.setContainedComponent(containedComponent);
result.setLocation(containedComponent.getLocation());
_count++;
return result;
}
/**
* Instantitates a page from its specification.
*
* @param name
* the unqualified, simple, name for the page
* @param namespace
* the namespace containing the page's specification
* @param spec
* the page's specification We instantiate the page object, then
* set its specification, names and locale.
*
* @return The instantiated page instance.
*
* @see org.apache.tapestry.IEngine
* @see org.apache.tapestry.event.ChangeObserver
*/
private IPage instantiatePage(String name, INamespace namespace, IComponentSpecification spec)
{
Location location = spec.getLocation();
ComponentClassProviderContext context = new ComponentClassProviderContext(name, spec, namespace);
String className = _pageClassProvider.provideComponentClassName(context);
Class pageClass = _classResolver.findClass(className);
if (!IPage.class.isAssignableFrom(pageClass))
throw new ApplicationRuntimeException(PageloadMessages.classNotPage(pageClass), location, null);
String pageName = namespace.constructQualifiedName(name);
ComponentConstructor cc = _componentConstructorFactory.getComponentConstructor(spec, className);
IPage result = (IPage) cc.newInstance();
result.setNamespace(namespace);
result.setPageName(pageName);
result.setPage(result);
result.setLocale(_locale);
result.setLocation(location);
return result;
}
public IPage loadPage(String name, INamespace namespace,
IRequestCycle cycle, IComponentSpecification specification)
{
IPage page = null;
_count = 0;
_depth = 0;
_maxDepth = 0;
_componentStack.clear();
_locale = _threadLocale.getLocale();
try
{
page = instantiatePage(name, namespace, specification);
// The page is now attached to the engine and request cycle; some
// code
// inside the page's finishLoad() method may require this.
// TAPESTRY-763
page.attach(cycle.getEngine(), cycle);
constructComponent(cycle, page, page, specification, namespace);
// Walk through the complete component tree to set up the default
// parameter values.
_establishDefaultParameterValuesWalker.walkComponentTree(page);
establishInheritedBindings();
// Walk through the complete component tree to ensure that required
// parameters are bound
_verifyRequiredParametersWalker.walkComponentTree(page);
// connect @EventListener style client side events
_eventConnectionWalker.walkComponentTree(page);
}
finally
{
_locale = null;
_inheritedBindingQueue.clear();
}
if (_log.isDebugEnabled())
_log.debug("Loaded page " + page + " with " + _count + " components (maximum depth " + _maxDepth + ")");
return page;
}
/** @since 4.0 */
public void loadTemplateForComponent(IRequestCycle cycle, ITemplateComponent component)
{
_componentTemplateLoader.loadTemplate(cycle, component);
}
private void establishInheritedBindings()
{
_log.debug("Establishing inherited bindings");
int count = _inheritedBindingQueue.size();
for(int i = 0; i < count; i++)
{
IQueuedInheritedBinding queued = (IQueuedInheritedBinding) _inheritedBindingQueue.get(i);
queued.connect();
}
}
private void addAssets(IComponent component, IComponentSpecification specification)
{
List names = specification.getAssetNames();
if (names.isEmpty()) return;
Iterator i = names.iterator();
while(i.hasNext())
{
String name = (String) i.next();
IAssetSpecification assetSpec = specification.getAsset(name);
IAsset asset = _assetSource.findAsset(assetSpec.getLocation().getResource(), specification,
assetSpec.getPath(), _locale, assetSpec.getLocation());
component.addAsset(name, asset);
}
}
public void setLog(Log log)
{
_log = log;
}
public void setComponentResolver(ComponentSpecificationResolver resolver)
{
_componentResolver = resolver;
}
public void setBindingSource(BindingSource bindingSource)
{
_bindingSource = bindingSource;
}
public void setComponentTemplateLoader(ComponentTemplateLoader componentTemplateLoader)
{
_componentTemplateLoader = componentTemplateLoader;
}
public void setEstablishDefaultParameterValuesVisitor(IComponentVisitor establishDefaultParameterValuesVisitor)
{
_establishDefaultParameterValuesVisitor = establishDefaultParameterValuesVisitor;
}
public void setEventConnectionVisitor(IComponentVisitor eventConnectionVisitor)
{
_eventConnectionVisitor = eventConnectionVisitor;
}
public void setComponentTypeVisitor(IComponentVisitor visitor)
{
_componentTypeVisitor = visitor;
}
public void setComponentConstructorFactory(ComponentConstructorFactory componentConstructorFactory)
{
_componentConstructorFactory = componentConstructorFactory;
}
public void setAssetSource(AssetSource assetSource)
{
_assetSource = assetSource;
}
public void setPageClassProvider(ComponentClassProvider pageClassProvider)
{
_pageClassProvider = pageClassProvider;
}
public void setClassResolver(ClassResolver classResolver)
{
_classResolver = classResolver;
}
public void setComponentClassProvider(ComponentClassProvider componentClassProvider)
{
_componentClassProvider = componentClassProvider;
}
public void setThreadLocale(ThreadLocale threadLocale)
{
_threadLocale = threadLocale;
}
public void setComponentPropertySource(ComponentPropertySource componentPropertySource)
{
_componentPropertySource = componentPropertySource;
}
}