0001: // Copyright 2006, 2007 The Apache Software Foundation
0002: //
0003: // Licensed under the Apache License, Version 2.0 (the "License");
0004: // you may not use this file except in compliance with the License.
0005: // You may obtain a copy of the License at
0006: //
0007: // http://www.apache.org/licenses/LICENSE-2.0
0008: //
0009: // Unless required by applicable law or agreed to in writing, software
0010: // distributed under the License is distributed on an "AS IS" BASIS,
0011: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0012: // See the License for the specific language governing permissions and
0013: // limitations under the License.
0014:
0015: package org.apache.tapestry.internal.structure;
0016:
0017: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newCaseInsensitiveMap;
0018: import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
0019: import static org.apache.tapestry.ioc.internal.util.Defense.notBlank;
0020:
0021: import java.util.Iterator;
0022: import java.util.List;
0023: import java.util.Locale;
0024: import java.util.Map;
0025:
0026: import org.apache.commons.logging.Log;
0027: import org.apache.tapestry.Binding;
0028: import org.apache.tapestry.Block;
0029: import org.apache.tapestry.BlockNotFoundException;
0030: import org.apache.tapestry.ComponentEventHandler;
0031: import org.apache.tapestry.ComponentResources;
0032: import org.apache.tapestry.Link;
0033: import org.apache.tapestry.MarkupWriter;
0034: import org.apache.tapestry.Renderable;
0035: import org.apache.tapestry.dom.Element;
0036: import org.apache.tapestry.internal.InternalComponentResources;
0037: import org.apache.tapestry.internal.TapestryInternalUtils;
0038: import org.apache.tapestry.internal.services.ComponentEventImpl;
0039: import org.apache.tapestry.internal.services.EventImpl;
0040: import org.apache.tapestry.internal.services.Instantiator;
0041: import org.apache.tapestry.internal.util.NotificationEventHandler;
0042: import org.apache.tapestry.ioc.BaseLocatable;
0043: import org.apache.tapestry.ioc.Location;
0044: import org.apache.tapestry.ioc.internal.util.InternalUtils;
0045: import org.apache.tapestry.ioc.internal.util.TapestryException;
0046: import org.apache.tapestry.ioc.services.TypeCoercer;
0047: import org.apache.tapestry.model.ComponentModel;
0048: import org.apache.tapestry.model.ParameterModel;
0049: import org.apache.tapestry.runtime.Component;
0050: import org.apache.tapestry.runtime.ComponentEvent;
0051: import org.apache.tapestry.runtime.Event;
0052: import org.apache.tapestry.runtime.PageLifecycleListener;
0053: import org.apache.tapestry.runtime.RenderCommand;
0054: import org.apache.tapestry.runtime.RenderQueue;
0055: import org.apache.tapestry.services.ComponentMessagesSource;
0056:
0057: /**
0058: * Implements {@link org.apache.tapestry.internal.structure.PageElement} and
0059: * {@link org.apache.tapestry.internal.InternalComponentResources}, and represents a component
0060: * within an overall page. Much of a component page element's behavior is delegated to user code,
0061: * via a {@link org.apache.tapestry.runtime.Component} instance.
0062: * <p>
0063: * Once instantiated, a ComponentPageElementImpl should be registered as a
0064: * {@link org.apache.tapestry.internal.structure.Page}. This could be done inside the constructors,
0065: * but that tends to complicate unit tests, so its done by
0066: * {@link org.apache.tapestry.internal.services.PageElementFactoryImpl}.
0067: * <p>
0068: */
0069: public class ComponentPageElementImpl extends BaseLocatable implements
0070: ComponentPageElement, PageLifecycleListener {
0071: private static final ComponentCallback CONTAINING_PAGE_DID_ATTACH = new ComponentCallback() {
0072: public void run(Component component) {
0073: component.containingPageDidAttach();
0074: }
0075: };
0076:
0077: private static final ComponentCallback CONTAINING_PAGE_DID_DETACH = new ComponentCallback() {
0078: public void run(Component component) {
0079: component.containingPageDidDetach();
0080: }
0081: };
0082:
0083: private static final ComponentCallback CONTAINING_PAGE_DID_LOAD = new ComponentCallback() {
0084: public void run(Component component) {
0085: component.containingPageDidLoad();
0086: }
0087: };
0088:
0089: private static final ComponentCallback POST_RENDER_CLEANUP = new ComponentCallback() {
0090: public void run(Component component) {
0091: component.postRenderCleanup();
0092: }
0093: };
0094:
0095: // For the momement, every component will have a template, even if it consists of
0096: // just a page element to queue up a BeforeRenderBody phase.
0097:
0098: private static void pushElements(RenderQueue queue,
0099: List<PageElement> list) {
0100: int count = size(list);
0101: for (int i = count - 1; i >= 0; i--)
0102: queue.push(list.get(i));
0103: }
0104:
0105: private static int size(List<?> list) {
0106: return list == null ? 0 : list.size();
0107: }
0108:
0109: private static class RenderPhaseEventHandler implements
0110: ComponentEventHandler {
0111: private boolean _result = true;
0112:
0113: private List<RenderCommand> _commands;
0114:
0115: boolean getResult() {
0116: return _result;
0117: }
0118:
0119: public boolean handleResult(Object result, Component component,
0120: String methodDescription) {
0121: if (result instanceof Boolean) {
0122: _result = (Boolean) result;
0123: return true; // abort other handler methods
0124: }
0125:
0126: if (result instanceof RenderCommand) {
0127: RenderCommand command = (RenderCommand) result;
0128:
0129: add(command);
0130:
0131: return false; // do not abort!
0132: }
0133:
0134: if (result instanceof Renderable) {
0135: final Renderable renderable = (Renderable) result;
0136:
0137: RenderCommand wrapper = new RenderCommand() {
0138: public void render(MarkupWriter writer,
0139: RenderQueue queue) {
0140: renderable.render(writer);
0141: }
0142: };
0143:
0144: add(wrapper);
0145:
0146: return false;
0147: }
0148:
0149: throw new TapestryException(StructureMessages
0150: .wrongEventResultType(methodDescription,
0151: Boolean.class), component, null);
0152: }
0153:
0154: private void add(RenderCommand command) {
0155: if (_commands == null)
0156: _commands = newList();
0157:
0158: _commands.add(command);
0159: }
0160:
0161: public void queueCommands(RenderQueue queue) {
0162: if (_commands == null)
0163: return;
0164:
0165: for (RenderCommand command : _commands)
0166: queue.push(command);
0167: }
0168: };
0169:
0170: private final RenderCommand _afterRender = new RenderCommand() {
0171: public void render(final MarkupWriter writer, RenderQueue queue) {
0172: RenderPhaseEventHandler handler = new RenderPhaseEventHandler();
0173: final Event event = new EventImpl(handler);
0174:
0175: ComponentCallback callback = new ComponentCallback() {
0176: public void run(Component component) {
0177: component.afterRender(writer, event);
0178: }
0179: };
0180:
0181: invoke(true, callback);
0182:
0183: if (!handler.getResult())
0184: queue.push(_beginRender);
0185:
0186: handler.queueCommands(queue);
0187: }
0188:
0189: @Override
0190: public String toString() {
0191: return phaseToString("AfterRender");
0192: }
0193: };
0194:
0195: private final RenderCommand _afterRenderBody = new RenderCommand() {
0196: public void render(final MarkupWriter writer, RenderQueue queue) {
0197: RenderPhaseEventHandler handler = new RenderPhaseEventHandler();
0198: final Event event = new EventImpl(handler);
0199:
0200: ComponentCallback callback = new ComponentCallback() {
0201: public void run(Component component) {
0202: component.afterRenderBody(writer, event);
0203: }
0204: };
0205:
0206: invoke(true, callback);
0207:
0208: if (!handler.getResult())
0209: queue.push(_beforeRenderBody);
0210:
0211: handler.queueCommands(queue);
0212: }
0213:
0214: @Override
0215: public String toString() {
0216: return phaseToString("AfterRenderBody");
0217: }
0218: };
0219:
0220: private final RenderCommand _afterRenderTemplate = new RenderCommand() {
0221: public void render(final MarkupWriter writer,
0222: final RenderQueue queue) {
0223: RenderPhaseEventHandler handler = new RenderPhaseEventHandler();
0224: final Event event = new EventImpl(handler);
0225:
0226: ComponentCallback callback = new ComponentCallback() {
0227: public void run(Component component) {
0228: component.afterRenderTemplate(writer, event);
0229: }
0230: };
0231:
0232: invoke(true, callback);
0233:
0234: if (!handler.getResult())
0235: queue.push(_beforeRenderTemplate);
0236:
0237: handler.queueCommands(queue);
0238: }
0239:
0240: @Override
0241: public String toString() {
0242: return phaseToString("AfterRenderTemplate");
0243: }
0244: };
0245:
0246: private final RenderCommand _beforeRenderBody = new RenderCommand() {
0247: public void render(final MarkupWriter writer, RenderQueue queue) {
0248: RenderPhaseEventHandler handler = new RenderPhaseEventHandler();
0249: final Event event = new EventImpl(handler);
0250:
0251: ComponentCallback callback = new ComponentCallback() {
0252: public void run(Component component) {
0253: component.beforeRenderBody(writer, event);
0254: }
0255: };
0256:
0257: invoke(false, callback);
0258:
0259: queue.push(_afterRenderBody);
0260:
0261: if (handler.getResult())
0262: pushElements(queue, _body);
0263:
0264: handler.queueCommands(queue);
0265: }
0266:
0267: @Override
0268: public String toString() {
0269: return phaseToString("BeforeRenderBody");
0270: }
0271: };
0272:
0273: private final RenderCommand _beforeRenderTemplate = new RenderCommand() {
0274: public void render(final MarkupWriter writer,
0275: final RenderQueue queue) {
0276: final RenderPhaseEventHandler handler = new RenderPhaseEventHandler();
0277: final Event event = new EventImpl(handler);
0278:
0279: ComponentCallback callback = new ComponentCallback() {
0280: public void run(Component component) {
0281: component.beforeRenderTemplate(writer, event);
0282: }
0283: };
0284:
0285: invoke(false, callback);
0286:
0287: queue.push(_afterRenderTemplate);
0288:
0289: if (handler.getResult())
0290: pushElements(queue, _template);
0291:
0292: handler.queueCommands(queue);
0293: }
0294:
0295: @Override
0296: public String toString() {
0297: return phaseToString("BeforeRenderTemplate");
0298: }
0299: };
0300:
0301: private final RenderCommand _beginRender = new RenderCommand() {
0302: public void render(final MarkupWriter writer,
0303: final RenderQueue queue) {
0304: RenderPhaseEventHandler handler = new RenderPhaseEventHandler();
0305: final Event event = new EventImpl(handler);
0306:
0307: ComponentCallback callback = new ComponentCallback() {
0308: public void run(Component component) {
0309: component.beginRender(writer, event);
0310: }
0311: };
0312:
0313: invoke(false, callback);
0314:
0315: queue.push(_afterRender);
0316:
0317: // If the component has no template whatsoever, then a
0318: // renderBody element is added as the lone element of the component's template.
0319: // So every component will have a non-empty template.
0320:
0321: if (handler.getResult())
0322: queue.push(_beforeRenderTemplate);
0323:
0324: handler.queueCommands(queue);
0325: }
0326:
0327: @Override
0328: public String toString() {
0329: return phaseToString("BeginRender");
0330: }
0331: };
0332:
0333: private Map<String, Block> _blocks;
0334:
0335: private List<PageElement> _body;
0336:
0337: private Map<String, ComponentPageElement> _children;
0338:
0339: private final String _elementName;
0340:
0341: private final RenderCommand _cleanupRender = new RenderCommand() {
0342: public void render(final MarkupWriter writer, RenderQueue queue) {
0343: RenderPhaseEventHandler handler = new RenderPhaseEventHandler();
0344: final Event event = new EventImpl(handler);
0345:
0346: ComponentCallback callback = new ComponentCallback() {
0347: public void run(Component component) {
0348: component.cleanupRender(writer, event);
0349: }
0350: };
0351:
0352: invoke(true, callback);
0353:
0354: if (handler.getResult()) {
0355: _rendering = false;
0356:
0357: Element current = writer.getElement();
0358:
0359: if (current != _elementAtSetup)
0360: throw new TapestryException(StructureMessages
0361: .unbalancedElements(_completeId),
0362: getLocation(), null);
0363:
0364: _elementAtSetup = null;
0365:
0366: invoke(false, POST_RENDER_CLEANUP);
0367:
0368: // NOW and only now the component is done rendering and fully cleaned up. Decrement
0369: // the page's dirty count. If the entire render goes well, then the page will be
0370: // clean and can be stored into the pool for later reuse.
0371:
0372: _page.decrementDirtyCount();
0373: } else {
0374: queue.push(_setupRender);
0375: }
0376:
0377: handler.queueCommands(queue);
0378: }
0379:
0380: @Override
0381: public String toString() {
0382: return phaseToString("CleanupRender");
0383: }
0384: };
0385:
0386: private final String _completeId;
0387:
0388: // The user-provided class, with runtime code enhancements. In a component with mixins, this
0389: // is the component to which the mixins are attached.
0390: private final Component _coreComponent;
0391:
0392: private final ComponentMessagesSource _messagesSource;
0393:
0394: /**
0395: * Component lifecycle instances for all mixins; the core component is added to this list during
0396: * page load. This is only used in the case that a component has mixins (in which case, the core
0397: * component is listed last).
0398: */
0399: private List<Component> _components = null;
0400:
0401: private final ComponentPageElement _container;
0402:
0403: private final InternalComponentResources _coreResources;
0404:
0405: private final String _id;
0406:
0407: private boolean _loaded;
0408:
0409: /** Map from mixin name to resources for the mixin. Created when first mixin is added. */
0410: private Map<String, InternalComponentResources> _mixinsByShortName;
0411:
0412: private final String _nestedId;
0413:
0414: private final Page _page;
0415:
0416: private boolean _rendering;
0417:
0418: private Element _elementAtSetup;
0419:
0420: private final RenderCommand _setupRender = new RenderCommand() {
0421: public void render(final MarkupWriter writer, RenderQueue queue) {
0422: // TODO: Check for recursive rendering.
0423:
0424: _rendering = true;
0425:
0426: _elementAtSetup = writer.getElement();
0427:
0428: RenderPhaseEventHandler handler = new RenderPhaseEventHandler();
0429: final Event event = new EventImpl(handler);
0430:
0431: ComponentCallback callback = new ComponentCallback() {
0432: public void run(Component component) {
0433: component.setupRender(writer, event);
0434: }
0435: };
0436:
0437: invoke(false, callback);
0438:
0439: queue.push(_cleanupRender);
0440:
0441: if (handler.getResult())
0442: queue.push(_beginRender);
0443:
0444: handler.queueCommands(queue);
0445: }
0446:
0447: @Override
0448: public String toString() {
0449: return phaseToString("SetupRender");
0450: }
0451: };
0452:
0453: // We know that, at the very least, there will be an element to force the component to render
0454: // its body,
0455: // so there's no reason to wait to initialize the list.
0456:
0457: private final List<PageElement> _template = newList();
0458:
0459: private final TypeCoercer _typeCoercer;
0460:
0461: /**
0462: * Constructor for other components embedded within the root component or at deeper levels of
0463: * the hierarchy.
0464: *
0465: * @param page
0466: * ultimately containing this component
0467: * @param container
0468: * component immediately containing this component (may be null for a root component)
0469: * @param id
0470: * unique (within the container) id for this component (may be null for a root
0471: * component)
0472: * @param elementName
0473: * the name of the element which represents this component in the template, or null
0474: * for <comp> element or a page component
0475: * @param instantiator
0476: * used to create the new component instance and access the component's model
0477: * @param typeCoercer
0478: * used when coercing parameter values
0479: * @param messagesSource
0480: * Provides access to the component's message catalog
0481: * @param location
0482: * location of the element (within a template), used as part of exception reporting
0483: */
0484:
0485: public ComponentPageElementImpl(Page page,
0486: ComponentPageElement container, String id,
0487: String elementName, Instantiator instantiator,
0488: TypeCoercer typeCoercer,
0489: ComponentMessagesSource messagesSource, Location location) {
0490: super (location);
0491:
0492: _page = page;
0493: _container = container;
0494: _id = id;
0495: _elementName = elementName;
0496: _typeCoercer = typeCoercer;
0497: _messagesSource = messagesSource;
0498:
0499: ComponentResources containerResources = container == null ? null
0500: : container.getComponentResources();
0501:
0502: _coreResources = new InternalComponentResourcesImpl(this ,
0503: containerResources, instantiator, _typeCoercer,
0504: _messagesSource);
0505:
0506: _coreComponent = _coreResources.getComponent();
0507:
0508: String pageName = _page.getLogicalName();
0509:
0510: // A page (really, the root component of a page) does not have a container.
0511:
0512: if (container == null) {
0513: _completeId = pageName;
0514: _nestedId = null;
0515: } else {
0516: String caselessId = id.toLowerCase();
0517:
0518: String parentNestedId = container.getNestedId();
0519:
0520: // The root element has no nested id.
0521: // The children of the root element have an id.
0522:
0523: if (parentNestedId == null) {
0524: _nestedId = caselessId;
0525: _completeId = pageName + ":" + caselessId;
0526: } else {
0527: _nestedId = parentNestedId + "." + caselessId;
0528: _completeId = container.getCompleteId() + "."
0529: + caselessId;
0530: }
0531: }
0532: }
0533:
0534: /**
0535: * Constructor for the root component of a page.
0536: */
0537: public ComponentPageElementImpl(Page page,
0538: Instantiator instantiator, TypeCoercer typeCoercer,
0539: ComponentMessagesSource messagesSource) {
0540: this (page, null, null, null, instantiator, typeCoercer,
0541: messagesSource, null);
0542: }
0543:
0544: public void addEmbeddedElement(ComponentPageElement child) {
0545: if (_children == null)
0546: _children = newCaseInsensitiveMap();
0547:
0548: String childId = child.getId();
0549:
0550: ComponentPageElement existing = _children.get(childId);
0551: if (existing != null)
0552: throw new TapestryException(StructureMessages
0553: .duplicateChildComponent(this , childId), child,
0554: null);
0555:
0556: _children.put(childId, child);
0557: }
0558:
0559: public void addMixin(Instantiator instantiator) {
0560: if (_mixinsByShortName == null) {
0561: _mixinsByShortName = newCaseInsensitiveMap();
0562: _components = newList();
0563: }
0564:
0565: String mixinClassName = instantiator.getModel()
0566: .getComponentClassName();
0567: String mixinName = TapestryInternalUtils
0568: .lastTerm(mixinClassName);
0569:
0570: InternalComponentResourcesImpl resources = new InternalComponentResourcesImpl(
0571: this , _coreResources, instantiator, _typeCoercer,
0572: _messagesSource);
0573:
0574: // TODO: Check for name collision?
0575:
0576: _mixinsByShortName.put(mixinName, resources);
0577:
0578: _components.add(resources.getComponent());
0579: }
0580:
0581: public void bindParameter(String parameterName, Binding binding) {
0582: // Maybe should use colon here? Depends on what works best in the template,
0583: // don't want to lock this out as just
0584: int dotx = parameterName.lastIndexOf('.');
0585:
0586: if (dotx > 0) {
0587: String mixinName = parameterName.substring(0, dotx);
0588: InternalComponentResources mixinResources = InternalUtils
0589: .get(_mixinsByShortName, mixinName);
0590:
0591: if (mixinResources == null)
0592: throw new TapestryException(StructureMessages
0593: .missingMixinForParameter(_completeId,
0594: mixinName, parameterName), binding,
0595: null);
0596:
0597: String simpleName = parameterName.substring(dotx + 1);
0598:
0599: mixinResources.bindParameter(simpleName, binding);
0600: return;
0601: }
0602:
0603: InternalComponentResources informalParameterResources = null;
0604:
0605: // Does it match a formal parameter name of the core component? That takes precedence
0606:
0607: if (_coreResources.getComponentModel().getParameterModel(
0608: parameterName) != null) {
0609: _coreResources.bindParameter(parameterName, binding);
0610: return;
0611: }
0612:
0613: for (String mixinName : InternalUtils
0614: .sortedKeys(_mixinsByShortName)) {
0615: InternalComponentResources resources = _mixinsByShortName
0616: .get(mixinName);
0617: if (resources.getComponentModel().getParameterModel(
0618: parameterName) != null) {
0619: resources.bindParameter(parameterName, binding);
0620: return;
0621: }
0622:
0623: if (informalParameterResources == null
0624: && resources.getComponentModel()
0625: .getSupportsInformalParameters())
0626: informalParameterResources = resources;
0627: }
0628:
0629: // An informal parameter
0630:
0631: if (informalParameterResources == null
0632: && _coreResources.getComponentModel()
0633: .getSupportsInformalParameters())
0634: informalParameterResources = _coreResources;
0635:
0636: // For the moment, informal parameters accumulate in the core component's resources, but
0637: // that will likely change.
0638:
0639: if (informalParameterResources != null)
0640: informalParameterResources.bindParameter(parameterName,
0641: binding);
0642: }
0643:
0644: public void addToBody(PageElement element) {
0645: if (_body == null)
0646: _body = newList();
0647:
0648: _body.add(element);
0649: }
0650:
0651: public void addToTemplate(PageElement element) {
0652: _template.add(element);
0653: }
0654:
0655: private void addUnboundParameterNames(String prefix,
0656: List<String> unbound, InternalComponentResources resource) {
0657: ComponentModel model = resource.getComponentModel();
0658:
0659: for (String name : model.getParameterNames()) {
0660: if (resource.isBound(name))
0661: continue;
0662:
0663: ParameterModel parameterModel = model
0664: .getParameterModel(name);
0665:
0666: if (parameterModel.isRequired()) {
0667: String fullName = prefix == null ? name : prefix + "."
0668: + name;
0669:
0670: unbound.add(fullName);
0671: }
0672: }
0673: }
0674:
0675: public void containingPageDidAttach() {
0676: invoke(false, CONTAINING_PAGE_DID_ATTACH);
0677: }
0678:
0679: public void containingPageDidDetach() {
0680: invoke(false, CONTAINING_PAGE_DID_DETACH);
0681: }
0682:
0683: public void containingPageDidLoad() {
0684: // If this component has mixins, add the core component to the end of the list, after the
0685: // mixins.
0686:
0687: if (_components != null) {
0688: List<Component> ordered = newList();
0689:
0690: Iterator<Component> i = _components.iterator();
0691:
0692: // Add all the normal components to the final list.
0693:
0694: while (i.hasNext()) {
0695: Component mixin = i.next();
0696:
0697: if (mixin.getComponentResources().getComponentModel()
0698: .isMixinAfter())
0699: continue;
0700:
0701: ordered.add(mixin);
0702:
0703: // Remove from list, leaving just the late executing mixins
0704:
0705: i.remove();
0706: }
0707:
0708: ordered.add(_coreComponent);
0709:
0710: // Add the remaining, late executing mixins
0711:
0712: ordered.addAll(_components);
0713:
0714: _components = ordered;
0715: }
0716:
0717: _loaded = true;
0718:
0719: // For some parameters, bindings (from defaults) are provided inside the callback method, so
0720: // that is invoked first, before we check for unbound parameters.
0721:
0722: invoke(false, CONTAINING_PAGE_DID_LOAD);
0723:
0724: verifyRequiredParametersAreBound();
0725: }
0726:
0727: /**
0728: * Delegates to the
0729: * {@link Page#createActionLink(Element, ComponentPageElement, String, boolean, Object[]) the containing page}.
0730: * Why the extra layer? Trying to avoid some unwanted injection (of LinkFactory, into every
0731: * component page element).
0732: */
0733: public Link createActionLink(String action, boolean forForm,
0734: Object... context) {
0735: return _page.createActionLink(this , action, forForm, context);
0736: }
0737:
0738: public Link createPageLink(String pageName, boolean override,
0739: Object... context) {
0740: return _page.createPageLink(pageName, override, context);
0741: }
0742:
0743: public void enqueueBeforeRenderBody(RenderQueue queue) {
0744: // If no body, then no beforeRenderBody or afterRenderBody
0745:
0746: if (_body != null)
0747: queue.push(_beforeRenderBody);
0748: }
0749:
0750: public String getCompleteId() {
0751: return _completeId;
0752: }
0753:
0754: public Component getComponent() {
0755: return _coreComponent;
0756: }
0757:
0758: public InternalComponentResources getComponentResources() {
0759: return _coreResources;
0760: }
0761:
0762: public ComponentPageElement getContainerElement() {
0763: return _container;
0764: }
0765:
0766: public Page getContainingPage() {
0767: return _page;
0768: }
0769:
0770: public ComponentPageElement getEmbeddedElement(String embeddedId) {
0771: ComponentPageElement embeddedElement = InternalUtils.get(
0772: _children, embeddedId.toLowerCase());
0773:
0774: if (embeddedElement == null)
0775: throw new TapestryException(StructureMessages
0776: .noSuchComponent(this , embeddedId), this , null);
0777:
0778: return embeddedElement;
0779: }
0780:
0781: public Object getFieldChange(String fieldName) {
0782: return _page.getFieldChange(this , fieldName);
0783: }
0784:
0785: public String getId() {
0786: return _id;
0787: }
0788:
0789: public Log getLog() {
0790: return _coreResources.getLog();
0791: }
0792:
0793: public Component getMixinByClassName(String mixinClassName) {
0794: Component result = null;
0795:
0796: if (_mixinsByShortName != null) {
0797: for (InternalComponentResources resources : _mixinsByShortName
0798: .values()) {
0799: if (resources.getComponentModel()
0800: .getComponentClassName().equals(mixinClassName)) {
0801: result = resources.getComponent();
0802: break;
0803: }
0804: }
0805: }
0806:
0807: if (result == null)
0808: throw new TapestryException(StructureMessages.unknownMixin(
0809: _completeId, mixinClassName), getLocation(), null);
0810:
0811: return result;
0812: }
0813:
0814: public String getNestedId() {
0815: return _nestedId;
0816: }
0817:
0818: public boolean handleEvent(ComponentEvent event) {
0819: // Simple case: no mixins
0820:
0821: if (_components == null)
0822: return _coreComponent.handleComponentEvent(event);
0823:
0824: // Otherwise, iterate over mixins + core component
0825:
0826: boolean result = false;
0827:
0828: for (Component component : _components) {
0829: result |= component.handleComponentEvent(event);
0830:
0831: if (event.isAborted())
0832: break;
0833: }
0834:
0835: return result;
0836: }
0837:
0838: public boolean hasFieldChange(String fieldName) {
0839: return getFieldChange(fieldName) != null;
0840: }
0841:
0842: /**
0843: * Invokes a callback on the component instances (the core component plus any mixins).
0844: *
0845: * @param reverse
0846: * if true, the callbacks are in the reverse of the normal order (this is associated
0847: * with AfterXXX phases)
0848: * @param callback
0849: * the object to receive each component instance
0850: */
0851: private void invoke(boolean reverse, ComponentCallback callback) {
0852: try { // Optimization: In the most general case (just the one component, no mixins)
0853: // invoke the callback on the component and be done ... no iterators, no nothing.
0854:
0855: if (_components == null) {
0856: callback.run(_coreComponent);
0857: return;
0858: }
0859:
0860: Iterator<Component> i = reverse ? InternalUtils
0861: .reverseIterator(_components) : _components
0862: .iterator();
0863:
0864: while (i.hasNext())
0865: callback.run(i.next());
0866: } catch (Exception ex) {
0867: throw new TapestryException(ex.getMessage(), getLocation(),
0868: ex);
0869: }
0870: }
0871:
0872: public boolean isLoaded() {
0873: return _loaded;
0874: }
0875:
0876: public boolean isRendering() {
0877: return _rendering;
0878: }
0879:
0880: public void persistFieldChange(ComponentResources resources,
0881: String fieldName, Object newValue) {
0882: // While loading the page (i.e., when setting field defaults), ignore these
0883: // changes.
0884:
0885: if (_loaded)
0886: _page.persistFieldChange(resources, fieldName, newValue);
0887: }
0888:
0889: /** Generate a toString() for the inner classes that represent render phases. */
0890: private String phaseToString(String phaseName) {
0891: return String.format("%s[%s]", phaseName, _completeId);
0892: }
0893:
0894: /** Pushes the SetupRender phase state onto the queue. */
0895: public final void render(MarkupWriter writer, RenderQueue queue) {
0896: // TODO: An error if the _render flag is already set (recursive rendering not
0897: // allowed or advisable).
0898:
0899: // Once we start rendering, the page is considered dirty, until we cleanup post render.
0900:
0901: _page.incrementDirtyCount();
0902:
0903: queue.push(_setupRender);
0904: }
0905:
0906: @Override
0907: public String toString() {
0908: return String.format("ComponentPageElement[%s]", _completeId);
0909: }
0910:
0911: public boolean triggerEvent(String eventType, Object[] context,
0912: ComponentEventHandler handler) {
0913: boolean result = false;
0914:
0915: // Provide a default handler for when the provided handler is null.
0916:
0917: if (handler == null)
0918: handler = new NotificationEventHandler(eventType,
0919: _completeId);
0920:
0921: ComponentPageElement component = this ;
0922: String componentId = "";
0923:
0924: while (component != null) {
0925: ComponentEvent event = new ComponentEventImpl(eventType,
0926: componentId, context, handler, _typeCoercer);
0927:
0928: result |= component.handleEvent(event);
0929:
0930: if (event.isAborted())
0931: return result;
0932:
0933: // On each bubble up, make the event appear to come from the previous component
0934: // in which the event was triggered.
0935:
0936: componentId = component.getId();
0937:
0938: component = component.getContainerElement();
0939: }
0940:
0941: return result;
0942: }
0943:
0944: private void verifyRequiredParametersAreBound() {
0945: List<String> unbound = newList();
0946:
0947: addUnboundParameterNames(null, unbound, _coreResources);
0948:
0949: for (String name : InternalUtils.sortedKeys(_mixinsByShortName))
0950: addUnboundParameterNames(name, unbound, _mixinsByShortName
0951: .get(name));
0952:
0953: if (unbound.isEmpty())
0954: return;
0955:
0956: throw new TapestryException(StructureMessages
0957: .missingParameters(unbound, this ), this , null);
0958: }
0959:
0960: public Locale getLocale() {
0961: return _page.getLocale();
0962: }
0963:
0964: public String getElementName() {
0965: return _elementName;
0966: }
0967:
0968: public void queueRender(RenderQueue queue) {
0969: queue.push(this );
0970: }
0971:
0972: public Block getBlock(String id) {
0973: Block result = findBlock(id);
0974:
0975: if (result == null)
0976: throw new BlockNotFoundException(StructureMessages
0977: .blockNotFound(_completeId, id), getLocation());
0978:
0979: return result;
0980: }
0981:
0982: public Block findBlock(String id) {
0983: notBlank(id, "id");
0984:
0985: return InternalUtils.get(_blocks, id);
0986: }
0987:
0988: public void addBlock(String blockId, Block block) {
0989: if (_blocks == null)
0990: _blocks = newCaseInsensitiveMap();
0991:
0992: if (_blocks.containsKey(blockId))
0993: throw new TapestryException(StructureMessages
0994: .duplicateBlock(this , blockId), block, null);
0995:
0996: _blocks.put(blockId, block);
0997: }
0998:
0999: public String getDefaultBindingPrefix(String parameterName) {
1000: int dotx = parameterName.lastIndexOf('.');
1001:
1002: if (dotx > 0) {
1003: String mixinName = parameterName.substring(0, dotx);
1004: InternalComponentResources mixinResources = InternalUtils
1005: .get(_mixinsByShortName, mixinName);
1006:
1007: if (mixinResources == null)
1008: throw new TapestryException(StructureMessages
1009: .missingMixinForParameter(_completeId,
1010: mixinName, parameterName), null, null);
1011:
1012: String simpleName = parameterName.substring(dotx + 1);
1013:
1014: ParameterModel pm = mixinResources.getComponentModel()
1015: .getParameterModel(simpleName);
1016:
1017: return pm != null ? pm.getDefaultBindingPrefix() : null;
1018: }
1019:
1020: // A formal parameter of the core component?
1021:
1022: ParameterModel pm = _coreResources.getComponentModel()
1023: .getParameterModel(parameterName);
1024:
1025: if (pm != null)
1026: return pm.getDefaultBindingPrefix();
1027:
1028: // Search for mixin that it is a formal parameter of
1029:
1030: for (String mixinName : InternalUtils
1031: .sortedKeys(_mixinsByShortName)) {
1032: InternalComponentResources resources = _mixinsByShortName
1033: .get(mixinName);
1034:
1035: pm = resources.getComponentModel().getParameterModel(
1036: parameterName);
1037:
1038: if (pm != null)
1039: return pm.getDefaultBindingPrefix();
1040: }
1041:
1042: // Not a formal parameter of the core component or any mixin.
1043:
1044: return null;
1045: }
1046:
1047: public String getPageName() {
1048: return _page.getLogicalName();
1049: }
1050:
1051: }
|