blob: 2543060f316c61f145584afa6991a943ed88f8cb [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.services.impl;
import org.apache.commons.logging.Log;
import org.apache.hivemind.ApplicationRuntimeException;
import org.apache.hivemind.Location;
import org.apache.tapestry.*;
import org.apache.tapestry.binding.BindingConstants;
import org.apache.tapestry.binding.BindingSource;
import org.apache.tapestry.binding.LiteralBinding;
import org.apache.tapestry.engine.IPageLoader;
import org.apache.tapestry.parse.*;
import org.apache.tapestry.services.TemplateSource;
import org.apache.tapestry.spec.IComponentSpecification;
import org.apache.tapestry.spec.IContainedComponent;
import org.apache.tapestry.spec.IParameterSpecification;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* Contains the logic from {@link org.apache.tapestry.services.impl.ComponentTemplateLoaderImpl},
* which creates one of these instances to process the request. This is necessary because the
* service must be re-entrant (because templates can contain components that have templates).
*
*/
public class ComponentTemplateLoaderLogic
{
private Log _log;
private IPageLoader _pageLoader;
private IRequestCycle _requestCycle;
private ITemplateComponent _loadComponent;
private BindingSource _bindingSource;
private IComponent[] _stack;
private int _stackx;
private IComponent _activeComponent = null;
private Set _seenIds = new HashSet();
public ComponentTemplateLoaderLogic(Log log, IPageLoader pageLoader, BindingSource bindingSource)
{
_log = log;
_pageLoader = pageLoader;
_bindingSource = bindingSource;
}
public void loadTemplate(IRequestCycle requestCycle, ITemplateComponent loadComponent,
ComponentTemplate template)
{
_requestCycle = requestCycle;
_loadComponent = loadComponent;
process(template);
}
private void process(ComponentTemplate template)
{
int count = template.getTokenCount();
_stack = new IComponent[count];
for (int i = 0; i < count; i++)
{
TemplateToken token = template.getToken(i);
TokenType type = token.getType();
if (type == TokenType.TEXT)
{
process((TextToken) token);
continue;
}
if (type == TokenType.OPEN)
{
process((OpenToken) token);
continue;
}
if (type == TokenType.CLOSE)
{
process((CloseToken) token);
continue;
}
if (type == TokenType.LOCALIZATION)
{
process((LocalizationToken) token);
}
}
// This is also pretty much unreachable, and the message is kind of out
// of date, too.
if (_stackx != 0)
throw new ApplicationRuntimeException(Tapestry.getMessage("BaseComponent.unbalance-open-tags"),
_loadComponent, null, null);
checkAllComponentsReferenced();
}
/**
* Adds the token (which implements {@link IRender}) to the active component (using
* {@link IComponent#addBody(IRender)}), or to this component
* {@link org.apache.tapestry.BaseComponent#addOuter(IRender)}.
* <p>
* A check is made that the active component allows a body.
*/
private void process(TextToken token)
{
if (_activeComponent == null)
{
_loadComponent.addOuter(token);
return;
}
if (!_activeComponent.getSpecification().getAllowBody())
throw createBodylessComponentException(_activeComponent);
_activeComponent.addBody(token);
}
private void process(OpenToken token)
{
String id = token.getId();
IComponent component = null;
String componentType = token.getComponentType();
if (componentType == null)
component = getEmbeddedComponent(id);
else
{
checkForDuplicateId(id, token.getLocation());
component = createImplicitComponent(id, componentType, token.getLocation());
}
// Make sure the template contains each component only once.
if (_seenIds.contains(id))
throw new ApplicationRuntimeException(ImplMessages.multipleComponentReferences(_loadComponent,id),
_loadComponent, token.getLocation(), null);
_seenIds.add(id);
if (_activeComponent == null)
_loadComponent.addOuter(component);
else
{
// Note: this code may no longer be reachable (because the
// template parser does this check first).
if (!_activeComponent.getSpecification().getAllowBody())
throw createBodylessComponentException(_activeComponent);
_activeComponent.addBody(component);
}
addTemplateBindings(component, token);
_stack[_stackx++] = _activeComponent;
_activeComponent = component;
}
private void checkForDuplicateId(String id, Location location)
{
if (id == null)
return;
IContainedComponent cc = _loadComponent.getSpecification().getComponent(id);
if (cc != null)
throw new ApplicationRuntimeException(ImplMessages.dupeComponentId(id, cc),
_loadComponent, location, null);
}
private IComponent createImplicitComponent(String id, String componentType, Location location)
{
return _pageLoader.createImplicitComponent(
_requestCycle,
_loadComponent,
id,
componentType,
location);
}
private IComponent getEmbeddedComponent(String id)
{
return _loadComponent.getComponent(id);
}
private void process(CloseToken token)
{
// Again, this is pretty much impossible to reach because
// the template parser does a great job.
if (_stackx <= 0)
throw new ApplicationRuntimeException(ImplMessages.unbalancedCloseTags(),
_loadComponent, token.getLocation(), null);
// Null and forget the top element on the stack.
_stack[_stackx--] = null;
_activeComponent = _stack[_stackx];
}
private void process(LocalizationToken token)
{
IRender render = new LocalizedStringRender(_loadComponent, token);
if (_activeComponent == null)
_loadComponent.addOuter(render);
else
_activeComponent.addBody(render);
}
/**
* Adds bindings based on attributes in the template.
*/
void addTemplateBindings(IComponent component, OpenToken token)
{
// sets the html tag name used to specify the component
component.setTemplateTagName(token.getTag());
IComponentSpecification spec = component.getSpecification();
Map attributes = token.getAttributesMap();
if (attributes != null)
{
Iterator i = attributes.entrySet().iterator();
while (i.hasNext())
{
Map.Entry entry = (Map.Entry) i.next();
String attributeName = (String) entry.getKey();
String value = (String) entry.getValue();
IParameterSpecification pspec = spec.getParameter(attributeName);
String parameterName = pspec == null ? attributeName : pspec.getParameterName();
if (!attributeName.equals(parameterName))
_log.warn(ImplMessages.usedTemplateParameterAlias(token, attributeName, parameterName));
String description = ImplMessages.templateParameterName(parameterName);
// Values in a template are always literal, unless prefixed.
IBinding binding = _bindingSource.createBinding(
_loadComponent,
pspec,
description,
value,
BindingConstants.LITERAL_PREFIX,
token.getLocation());
addBinding(component, spec, parameterName, binding);
}
}
// if the component defines a templateTag parameter and
// there is no established binding for that parameter,
// add a static binding carrying the template tag
if (spec.getParameter(TemplateSource.TEMPLATE_TAG_PARAMETER_NAME) != null
&& component.getBinding(TemplateSource.TEMPLATE_TAG_PARAMETER_NAME) == null)
{
IBinding binding = _bindingSource.createBinding(
component,
TemplateSource.TEMPLATE_TAG_PARAMETER_NAME,
token.getTag(),
BindingConstants.LITERAL_PREFIX,
token.getLocation());
addBinding(component, spec, TemplateSource.TEMPLATE_TAG_PARAMETER_NAME, binding);
}
}
/**
* Adds an expression binding, checking for errors related to reserved and informal parameters.
* <p>
* It is an error to specify expression bindings in both the specification and the template.
*/
private void addBinding(IComponent component, IComponentSpecification spec,
String name, IBinding binding)
{
// If matches a formal parameter name, allow it to be set
// unless there's already a binding.
boolean valid = validate(component, spec, name, binding);
if (valid)
component.setBinding(name, binding);
}
private boolean validate(IComponent component, IComponentSpecification spec,
String name, IBinding binding)
{
// TODO: This is ugly! Need a better/smarter way, even if we have to extend BindingSource
// to tell us.
boolean isLiteral = binding instanceof LiteralBinding;
boolean isBound = component.getBinding(name) != null;
boolean isFormal = spec.getParameter(name) != null;
if (!isFormal)
{
if (!spec.getAllowInformalParameters())
{
// Again; if informal parameters are disallowed, ignore literal bindings, as they
// are there as placeholders or for WYSIWYG.
if (isLiteral)
return false;
throw new ApplicationRuntimeException(ImplMessages.templateBindingForInformalParameter(_loadComponent, name, component),
component, binding.getLocation(), null);
}
// If the name is reserved (matches a formal parameter
// or reserved name, caselessly), then skip it.
if (spec.isReservedParameterName(name))
{
// Final case for literals: if they conflict with a reserved name, they are ignored.
// Again, there for WYSIWYG.
if (isLiteral)
return false;
throw new ApplicationRuntimeException(ImplMessages.templateBindingForReservedParameter(_loadComponent, name, component),
component, binding.getLocation(), null);
}
}
// So, at this point it doesn't matter if the parameter is a formal parameter or
// an informal parameter. The binding (if any) in the specification takes precendence
// over the template. Literal bindings that conflict are considered to be there for WYSIWYG
// purposes. Non-literal bindings that conflict with a specification binding are an
// error.
if (isBound)
{
// Literal bindings in the template that conflict with bound parameters
// from the spec are silently ignored.
if (isLiteral)
return false;
throw new ApplicationRuntimeException(ImplMessages.dupeTemplateBinding(
name,
component,
_loadComponent), component, binding.getLocation(), null);
}
return true;
}
private void checkAllComponentsReferenced()
{
// First, contruct a modifiable copy of the ids of all expected components
// (that is, components declared in the specification).
Map components = _loadComponent.getComponents();
Set ids = components.keySet();
// If the seen ids ... ids referenced in the template, matches
// all the ids in the specification then we're fine.
if (_seenIds.containsAll(ids))
return;
// Create a modifiable copy. Remove the ids that are referenced in
// the template. The remainder are worthy of note.
ids = new HashSet(ids);
ids.removeAll(_seenIds);
_log.warn(ImplMessages.missingComponentSpec(_loadComponent, ids));
}
private ApplicationRuntimeException createBodylessComponentException(IComponent component)
{
return new ApplicationRuntimeException(ImplMessages.bodylessComponent(), component, null, null);
}
}