/** * @param model embededded model defining the new component, from an {@link * org.apache.tapestry5.annotations.Component} annotation * @param loadingComponent the currently loading container component * @param newComponent the new child of the container whose parameters are being bound * @param newComponentBindings map of bindings for the new component (used to handle inheriting of * informal parameters) */ private void bindParametersFromModel( EmbeddedComponentModel model, ComponentPageElement loadingComponent, ComponentPageElement newComponent, Map<String, Binding> newComponentBindings) { for (String name : model.getParameterNames()) { String value = model.getParameterValue(name); String defaultBindingPrefix = determineDefaultBindingPrefix(newComponent, name, BindingConstants.PROP); Binding binding = findBinding( loadingComponent, newComponent, name, value, defaultBindingPrefix, newComponent.getLocation()); if (binding != null) { newComponent.bindParameter(name, binding); // So that the binding can be shared if inherited by a subcomponent newComponentBindings.put(name, binding); } } }
private void bindParameterFromTemplate(ComponentPageElement component, AttributeToken token) { String name = token.getName(); ComponentResources resources = component.getComponentResources(); // If already bound (i.e., from the component class, via @Component), then // ignore the value in the template. This may need improving to just ignore // the value if it is an unprefixed literal string. if (resources.isBound(name)) return; // Meta default of literal for the template. String defaultBindingPrefix = determineDefaultBindingPrefix(component, name, BindingConstants.LITERAL); Binding binding = findBinding( loadingElement, component, name, token.getValue(), defaultBindingPrefix, token.getLocation()); if (binding != null) { component.bindParameter(name, binding); Map<String, Binding> bindingMap = componentIdToBindingMap.get(component.getCompleteId()); bindingMap.put(name, binding); } }
private void parameter(ParameterToken token) { ComponentPageElement element = activeElementStack.peek(); String name = token.getName(); BlockImpl block = new BlockImpl( token.getLocation(), String.format("Parmeter %s of %s", name, element.getCompleteId())); Binding binding = new LiteralBinding("block parameter " + name, block, token.getLocation()); // TODO: Check that the t:parameter doesn't appear outside of an embedded component. element.bindParameter(name, binding); setupBlock(block); }
private void block(BlockToken token) { String id = token.getId(); // Don't use the page element factory here becauses we need something that is both Block and // BodyPageElement and don't want to use casts. String description = id == null ? String.format("Anonymous within %s", loadingElement.getCompleteId()) : String.format("%s within %s", id, loadingElement.getCompleteId()); BlockImpl block = new BlockImpl(token.getLocation(), description); if (id != null) loadingElement.addBlock(id, block); setupBlock(block); }
/** * Invoked when a component's end tag is reached, to check and process informal parameters as per * the {@link org.apache.tapestry5.model.EmbeddedComponentModel#getInheritInformalParameters()} * flag. * * @param loadingComponent the container component that was loaded * @param model * @param newComponent * @param newComponentBindings */ private void handleInformalParameters( ComponentPageElement loadingComponent, EmbeddedComponentModel model, ComponentPageElement newComponent, ComponentModel newComponentModel, Map<String, Binding> newComponentBindings) { Map<String, Binding> informals = loadingComponent.getInformalParameterBindings(); for (String name : informals.keySet()) { if (newComponentModel.getParameterModel(name) != null) continue; Binding binding = informals.get(name); newComponent.bindParameter(name, binding); newComponentBindings.put(name, binding); } }
/** * Creates a new binding, or returns an existing binding (or null) for the "inherit:" binding prefix. Mostly a * wrapper around {@link BindingSource#newBinding(String, ComponentResources, ComponentResources, String, String, * Location) * * @return the new binding, or an existing binding (if inherited), or null (if inherited, and the containing * parameter is not bound) */ private Binding findBinding( ComponentPageElement loadingComponent, ComponentPageElement component, String name, String value, String defaultBindingPrefix, Location location) { if (value.startsWith(INHERIT_PREFIX)) { String loadingParameterName = value.substring(INHERIT_PREFIX.length()); Map<String, Binding> loadingComponentBindingMap = componentIdToBindingMap.get(loadingComponent.getCompleteId()); // This may return null if the parameter is not bound in the loading component. Binding existing = loadingComponentBindingMap.get(loadingParameterName); if (existing == null) return null; String description = String.format( "InheritedBinding[parameter %s %s(inherited from %s of %s)]", name, component.getCompleteId(), loadingParameterName, loadingComponent.getCompleteId()); // This helps with debugging, and re-orients any thrown exceptions // to the location of the inherited binding, rather than the container component's // binding. return new InheritedBinding(description, existing, location); } return pageElementFactory.newBinding( name, loadingComponent.getComponentResources(), component.getComponentResources(), defaultBindingPrefix, value, location); }
private void attribute(AttributeToken token) { // This kind of bookkeeping is ugly, we probably should have distinct (if very similar) // tokens for attributes and for parameter bindings. if (addAttributesAsComponentBindings) { ComponentPageElement activeElement = activeElementStack.peek(); bindParameterFromTemplate(activeElement, token); return; } PageElement element = pageElementFactory.newAttributeElement(loadingElement.getComponentResources(), token); addToBody(element); }
private void startComponent(StartComponentToken token) { String elementName = token.getElementName(); // Initial guess: the type from the token (but this may be null in many cases). String embeddedType = token.getComponentType(); // This may be null for an anonymous component. String embeddedId = token.getId(); String embeddedComponentClassName = null; final EmbeddedComponentModel embeddedModel = embeddedId == null ? null : loadingComponentModel.getEmbeddedComponentModel(embeddedId); // We know that if embeddedId is null, embeddedType is not. if (embeddedId == null) embeddedId = generateEmbeddedId(embeddedType, idAllocator); if (embeddedModel != null) { String modelType = embeddedModel.getComponentType(); if (InternalUtils.isNonBlank(modelType) && embeddedType != null) throw new TapestryException( ServicesMessages.compTypeConflict(embeddedId, embeddedType, modelType), token, null); embeddedType = modelType; embeddedComponentClassName = embeddedModel.getComponentClassName(); } // We only have the embeddedModel if the embeddedId was specified. If embeddedType was ommitted // and if (InternalUtils.isBlank(embeddedType) && embeddedModel == null) throw new TapestryException( ServicesMessages.noTypeForEmbeddedComponent( embeddedId, loadingComponentModel.getComponentClassName()), token, null); final ComponentPageElement newComponent = pageElementFactory.newComponentElement( page, loadingElement, embeddedId, embeddedType, embeddedComponentClassName, elementName, token.getLocation()); addMixinsToComponent(newComponent, embeddedModel, token.getMixins()); final Map<String, Binding> newComponentBindings = CollectionFactory.newMap(); componentIdToBindingMap.put(newComponent.getCompleteId(), newComponentBindings); if (embeddedModel != null) bindParametersFromModel(embeddedModel, loadingElement, newComponent, newComponentBindings); addToBody(newComponent); // Remember to load the template for this new component componentQueue.push(newComponent); // Any attribute tokens that immediately follow should be // used to bind parameters. addAttributesAsComponentBindings = true; // Any attributes (including component parameters) that come up belong on this component. activeElementStack.push(newComponent); // Set things up so that content inside the component is added to the component's body. bodyPageElementStack.push(newComponent); // And clean that up when the end element is reached. final ComponentModel newComponentModel = newComponent.getComponentResources().getComponentModel(); // If the component was from an embedded @Component annotation, and it is inheritting informal // parameters, // and the component in question supports informal parameters, than get those inheritted // informal parameters ... // but later (this helps ensure that <t:parameter> elements that may provide informal parameters // are // visible when the informal parameters are copied to the child component). if (embeddedModel != null && embeddedModel.getInheritInformalParameters() && newComponentModel.getSupportsInformalParameters()) { final ComponentPageElement loadingElement = this.loadingElement; Runnable finalizer = new Runnable() { public void run() { handleInformalParameters( loadingElement, embeddedModel, newComponent, newComponentModel, newComponentBindings); } }; finalization.add(finalizer); } Runnable cleanup = new Runnable() { public void run() { // May need a separate queue for this, to execute at the very end of page loading. activeElementStack.pop(); bodyPageElementStack.pop(); } }; // The start tag is not added to the body of the component, so neither should // the end tag. configureEnd(true, cleanup); }
/** * Do you smell something? I'm smelling that this class needs to be redesigned to not need a * central method this large and hard to test. I think a lot of instance and local variables need * to be bundled up into some kind of process object. This code is effectively too big to be * tested except through integration testing. */ private void loadTemplateForComponent(final ComponentPageElement loadingElement) { this.loadingElement = loadingElement; loadingComponentModel = loadingElement.getComponentResources().getComponentModel(); String componentClassName = loadingComponentModel.getComponentClassName(); ComponentTemplate template = templateSource.getTemplate(loadingComponentModel, locale); // When the template for a component is missing, we pretend it consists of just a RenderBody // phase. Missing is not an error ... many component simply do not have a template. if (template.isMissing()) { this.loadingElement.addToTemplate(new RenderBodyElement(this.loadingElement)); return; } // Pre-allocate ids to avoid later name collisions. // Don't have a case-insensitive Set, so we'll make due with a Map Map<String, Boolean> embeddedIds = collectedEmbeddedComponentIds(loadingComponentModel); idAllocator.clear(); final Map<String, Location> componentIdsMap = template.getComponentIds(); for (String id : componentIdsMap.keySet()) { idAllocator.allocateId(id); embeddedIds.remove(id); } if (!embeddedIds.isEmpty()) throw new RuntimeException( ServicesMessages.embeddedComponentsNotInTemplate( embeddedIds.keySet(), componentClassName, template.getResource())); addAttributesAsComponentBindings = false; // The outermost elements of the template belong in the loading component's template list, // not its body list. This shunt allows everyone else to not have to make that decision, // they can add to the "body" and (if there isn't an active component), the shunt will // add the element to the component's template. BodyPageElement shunt = new BodyPageElement() { public void addToBody(PageElement element) { loadingElement.addToTemplate(element); } }; bodyPageElementStack.push(shunt); for (TemplateToken token : template.getTokens()) { switch (token.getTokenType()) { case TEXT: text((TextToken) token); break; case EXPANSION: expansion((ExpansionToken) token); break; case BODY: body(); break; case START_ELEMENT: startElement((StartElementToken) token); break; case START_COMPONENT: startComponent((StartComponentToken) token); break; case ATTRIBUTE: attribute((AttributeToken) token); break; case END_ELEMENT: endElement(); break; case COMMENT: comment((CommentToken) token); break; case BLOCK: block((BlockToken) token); break; case PARAMETER: parameter((ParameterToken) token); break; case DTD: dtd((DTDToken) token); break; case DEFINE_NAMESPACE_PREFIX: defineNamespacePrefix((DefineNamespacePrefixToken) token); break; case CDATA: cdata((CDATAToken) token); break; default: throw new IllegalStateException("Not implemented yet: " + token); } } // For neatness / symmetry: bodyPageElementStack.pop(); // the shunt // TODO: Check that all stacks are empty. That should never happen, as long // as the ComponentTemplate is valid. }
private void expansion(ExpansionToken token) { PageElement element = pageElementFactory.newExpansionElement(loadingElement.getComponentResources(), token); addToBody(element); }
/** * Determines the default binding prefix for a particular parameter. * * @param component the component which will have a parameter bound * @param parameterName the name of the parameter * @param informalParameterBindingPrefix the default to use for informal parameters * @return the binding prefix */ private String determineDefaultBindingPrefix( ComponentPageElement component, String parameterName, String informalParameterBindingPrefix) { String defaultBindingPrefix = component.getDefaultBindingPrefix(parameterName); return defaultBindingPrefix != null ? defaultBindingPrefix : informalParameterBindingPrefix; }
@Override public String toString() { return String.format("RenderBody[%s]", component.getNestedId()); }
public void render(MarkupWriter writer, RenderQueue queue) { component.enqueueBeforeRenderBody(queue); }