0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: package org.apache.wicket;
0018:
0019: import java.io.Serializable;
0020: import java.util.ArrayList;
0021: import java.util.Collections;
0022: import java.util.Iterator;
0023: import java.util.List;
0024: import java.util.Locale;
0025:
0026: import org.apache.wicket.ajax.AjaxRequestTarget;
0027: import org.apache.wicket.authorization.Action;
0028: import org.apache.wicket.authorization.AuthorizationException;
0029: import org.apache.wicket.authorization.IAuthorizationStrategy;
0030: import org.apache.wicket.authorization.UnauthorizedActionException;
0031: import org.apache.wicket.behavior.IBehavior;
0032: import org.apache.wicket.feedback.FeedbackMessage;
0033: import org.apache.wicket.markup.ComponentTag;
0034: import org.apache.wicket.markup.MarkupException;
0035: import org.apache.wicket.markup.MarkupStream;
0036: import org.apache.wicket.markup.WicketTag;
0037: import org.apache.wicket.markup.html.IHeaderContributor;
0038: import org.apache.wicket.markup.html.internal.HtmlHeaderContainer;
0039: import org.apache.wicket.model.IComponentAssignedModel;
0040: import org.apache.wicket.model.IComponentInheritedModel;
0041: import org.apache.wicket.model.IModel;
0042: import org.apache.wicket.model.IModelComparator;
0043: import org.apache.wicket.model.IWrapModel;
0044: import org.apache.wicket.settings.IDebugSettings;
0045: import org.apache.wicket.util.convert.IConverter;
0046: import org.apache.wicket.util.lang.Classes;
0047: import org.apache.wicket.util.lang.Objects;
0048: import org.apache.wicket.util.string.PrependingStringBuffer;
0049: import org.apache.wicket.util.string.Strings;
0050: import org.apache.wicket.util.value.ValueMap;
0051: import org.apache.wicket.version.undo.Change;
0052: import org.slf4j.Logger;
0053: import org.slf4j.LoggerFactory;
0054:
0055: /**
0056: * Component serves as the highest level abstract base class for all components.
0057: *
0058: * <ul>
0059: * <li><b>Identity </b>- All Components must have a non-null id which is
0060: * retrieved by calling getId(). The id must be unique within the
0061: * MarkupContainer that holds the Component, but does not have to be globally
0062: * unique or unique within a Page's component hierarchy.
0063: *
0064: * <li><b>Hierarchy </b>- A component has a parent which can be retrieved with
0065: * {@link #getParent()}. If a component is an instance of MarkupContainer, it
0066: * may have children. In this way it has a place in the hierarchy of components
0067: * contained on a given page.
0068: *
0069: * <li><b>Component Paths </b>- The path from the Page at the root of the
0070: * component hierarchy to a given Component is simply the concatenation with dot
0071: * separators of each id along the way. For example, the path "a.b.c" would
0072: * refer to the component named "c" inside the MarkupContainer named "b" inside
0073: * the container named "a". The path to a component can be retrieved by calling
0074: * getPath(). This path is an absolute path beginning with the id of the Page at
0075: * the root. Pages bear a PageMap/Session-relative identifier as their id, so
0076: * each absolute path will begin with a number, such as "0.a.b.c". To get a
0077: * Component path relative to the page that contains it, you can call
0078: * getPageRelativePath().
0079: *
0080: * <li><b>LifeCycle </b>- Components participate in the following lifecycle
0081: * phases:
0082: * <ul>
0083: * <li><b>Construction </b>- A Component is constructed with the Java language
0084: * new operator. Children may be added during construction if the Component is a
0085: * MarkupContainer.
0086: *
0087: * <li><b>Request Handling </b>- An incoming request is processed by a protocol
0088: * request handler such as WicketServlet. An associated Application object
0089: * creates Session, Request and Response objects for use by a given Component in
0090: * updating its model and rendering a response. These objects are stored inside
0091: * a container called {@link RequestCycle} which is accessible via
0092: * {@link Component#getRequestCycle()}. The convenience methods
0093: * {@link Component#getRequest()}, {@link Component#getResponse()} and
0094: * {@link Component#getSession()} provide easy access to the contents of this
0095: * container.
0096: *
0097: * <li><b>Listener Invocation </b>- If the request references a listener on an
0098: * existing Component, that listener is called, allowing arbitrary user code to
0099: * handle events such as link clicks or form submits. Although arbitrary
0100: * listeners are supported in Wicket, the need to implement a new class of
0101: * listener is unlikely for a web application and even the need to implement a
0102: * listener interface directly is highly discouraged. Instead, calls to
0103: * listeners are routed through logic specific to the event, resulting in calls
0104: * to user code through other overridable methods. For example, the
0105: * {@link org.apache.wicket.markup.html.form.IFormSubmitListener#onFormSubmitted()}
0106: * method implemented by the Form class is really a private implementation
0107: * detail of the Form class that is not designed to be overridden (although
0108: * unfortunately, it must be public since all interface methods in Java must be
0109: * public). Instead, Form subclasses should override user-oriented methods such
0110: * as onValidate(), onSubmit() and onError() (although only the latter two are
0111: * likely to be overridden in practice).
0112: *
0113: * <li><b>onBeginRequest </b>- The {@link Component#onBeginRequest()} method is
0114: * called.
0115: *
0116: * <li><b>Form Submit </b>- If a Form has been submitted and the Component is a
0117: * FormComponent, the component's model is validated by a call to
0118: * FormComponent.validate().
0119: *
0120: * <li><b>Form Model Update </b>- If a valid Form has been submitted and the
0121: * Component is a FormComponent, the component's model is updated by a call to
0122: * FormComponent.updateModel().
0123: *
0124: * <li><b>Rendering </b>- A markup response is generated by the Component via
0125: * {@link Component#render()}, which calls subclass implementation code
0126: * contained in {@link Component#onRender()}. Once this phase begins, a
0127: * Component becomes immutable. Attempts to alter the Component will result in a
0128: * WicketRuntimeException.
0129: *
0130: * <li><b>onEndRequest </b>() - The {@link Component#onEndRequest()} method is
0131: * called.
0132: * </ul>
0133: *
0134: * <li><b>Component Models </b>- The primary responsibility of a component is
0135: * to use its model (an object that implements IModel), which can be set via
0136: * {@link Component#setModel(IModel model)} and retrieved via
0137: * {@link Component#getModel()}, to render a response in an appropriate markup
0138: * language, such as HTML. In addition, form components know how to update their
0139: * models based on request information. Since the IModel interface is a wrapper
0140: * around an actual model object, a convenience method
0141: * {@link Component#getModelObject()} is provided to retrieve the model Object
0142: * from its IModel wrapper. A further convenience method,
0143: * {@link Component#getModelObjectAsString()}, is provided for the very common
0144: * operation of converting the wrapped model Object to a String.
0145: *
0146: * <li><b>Visibility </b>- Components which have setVisible(false) will return
0147: * false from isVisible() and will not render a response (nor will their
0148: * children).
0149: *
0150: * <li><b>Page </b>- The Page containing any given Component can be retrieved
0151: * by calling {@link Component#getPage()}. If the Component is not attached to
0152: * a Page, an IllegalStateException will be thrown. An equivalent method,
0153: * {@link Component#findPage()} is available for special circumstances where it
0154: * might be desirable to get a null reference back instead.
0155: *
0156: * <li><b>Session </b>- The Page for a Component points back to the Session
0157: * that contains the Page. The Session for a component can be accessed with the
0158: * convenience method getSession(), which simply calls getPage().getSession().
0159: *
0160: * <li><b>Locale </b>- The Locale for a Component is available through the
0161: * convenience method getLocale(), which is equivalent to
0162: * getSession().getLocale().
0163: *
0164: * <li><b>String Resources </b>- Components can have associated String
0165: * resources via the Application's Localizer, which is available through the
0166: * method {@link Component#getLocalizer()}. The convenience methods
0167: * {@link Component#getString(String key)} and
0168: * {@link Component#getString(String key, IModel model)} wrap the identical
0169: * methods on the Application Localizer for easy access in Components.
0170: *
0171: * <li><b>Style </b>- The style ("skin") for a component is available through
0172: * {@link Component#getStyle()}, which is equivalent to
0173: * getSession().getStyle(). Styles are intended to give a particular look to a
0174: * Component or Resource that is independent of its Locale. For example, a style
0175: * might be a set of resources, including images and markup files, which gives
0176: * the design look of "ocean" to the user. If the Session's style is set to
0177: * "ocean" and these resources are given names suffixed with "_ocean", Wicket's
0178: * resource management logic will prefer these resources to other resources,
0179: * such as default resources, which are not as good of a match.
0180: *
0181: * <li><b>Variation </b>- Whereas Styles are Session (user) specific,
0182: * variations are component specific. E.g. if the Style is "ocean" and the
0183: * Variation is "NorthSea", than the resources are given the names suffixed with
0184: * "_ocean_NorthSea".
0185: *
0186: * <li><b>AttributeModifiers </b>- You can add one or more
0187: * {@link AttributeModifier}s to any component if you need to programmatically
0188: * manipulate attributes of the markup tag to which a Component is attached.
0189: *
0190: * <li><b>Application, ApplicationSettings and ApplicationPages </b>- The
0191: * getApplication() method provides convenient access to the Application for a
0192: * Component via getSession().getApplication(). The getApplicationSettings()
0193: * method is equivalent to getApplication().getSettings(). The
0194: * getApplicationPages is equivalent to getApplication().getPages().
0195: *
0196: * <li><b>Feedback Messages </b>- The {@link Component#debug(String)},
0197: * {@link Component#info(String)}, {@link Component#warn(String)},
0198: * {@link Component#error(String)} and {@link Component#fatal(String)} methods
0199: * associate feedback messages with a Component. It is generally not necessary
0200: * to use these methods directly since Wicket validators automatically register
0201: * feedback messages on Components. Any feedback message for a given Component
0202: * can be retrieved with {@link Component#getFeedbackMessage}.
0203: *
0204: * <li><b>Page Factory </b>- It is possible to change the way that Pages are
0205: * constructed by overriding the {@link Component#getPageFactory()} method,
0206: * returning your own implementation of {@link org.apache.wicket.IPageFactory}.
0207: *
0208: * <li><b>Versioning </b>- Pages are the unit of versioning in Wicket, but
0209: * fine-grained control of which Components should participate in versioning is
0210: * possible via the {@link Component#setVersioned(boolean)} method. The
0211: * versioning participation of a given Component can be retrieved with
0212: * {@link Component#isVersioned()}.
0213: *
0214: * <li><b>AJAX support</b>- Components can be re-rendered after the whole Page
0215: * has been rendered at least once by calling doRender().
0216: *
0217: * @author Jonathan Locke
0218: * @author Chris Turner
0219: * @author Eelco Hillenius
0220: * @author Johan Compagner
0221: * @author Juergen Donnerstag
0222: * @author Igor Vaynberg (ivaynberg)
0223: */
0224: public abstract class Component implements IClusterable,
0225: IConverterLocator {
0226: /**
0227: * Change record of a model.
0228: */
0229: public class ComponentModelChange extends Change {
0230: private static final long serialVersionUID = 1L;
0231:
0232: /** Former model. */
0233: private final IModel model;
0234:
0235: /**
0236: * Construct.
0237: *
0238: * @param model
0239: */
0240: public ComponentModelChange(IModel model) {
0241: super ();
0242: this .model = model;
0243: }
0244:
0245: /**
0246: * @see java.lang.Object#toString()
0247: */
0248: public String toString() {
0249: return "ComponentModelChange[component: " + getPath() + "]";
0250: }
0251:
0252: /**
0253: * @see org.apache.wicket.version.undo.Change#undo()
0254: */
0255: public void undo() {
0256: setModel(model);
0257: }
0258: }
0259:
0260: /**
0261: * Generic component visitor interface for component traversals.
0262: */
0263: public static interface IVisitor {
0264: /**
0265: * Value to return to continue a traversal.
0266: */
0267: public static final Object CONTINUE_TRAVERSAL = null;
0268:
0269: /**
0270: * A generic value to return to contiue a traversal, but if the
0271: * component is a container, don't visit its children.
0272: */
0273: public static final Object CONTINUE_TRAVERSAL_BUT_DONT_GO_DEEPER = new Object();
0274:
0275: /**
0276: * A generic value to return to stop a traversal.
0277: */
0278: public static final Object STOP_TRAVERSAL = new Object();
0279:
0280: /**
0281: * Called at each component in a traversal.
0282: *
0283: * @param component
0284: * The component
0285: * @return CONTINUE_TRAVERSAL (null) if the traversal should continue,
0286: * or a non-null return value for the traversal method if it
0287: * should stop. If no return value is useful, the generic
0288: * non-null value STOP_TRAVERSAL can be used.
0289: */
0290: public Object component(Component component);
0291: }
0292:
0293: /**
0294: * Change object for undoing addition of behavior
0295: *
0296: * @author Igor Vaynberg (ivaynberg)
0297: */
0298: private final class AddedBehaviorChange extends Change {
0299:
0300: private static final long serialVersionUID = 1L;
0301:
0302: private final IBehavior behavior;
0303:
0304: /**
0305: * Construct.
0306: *
0307: * @param behavior
0308: */
0309: public AddedBehaviorChange(IBehavior behavior) {
0310: this .behavior = behavior;
0311: }
0312:
0313: public String toString() {
0314: return "[" + getClass().getName() + " behavior="
0315: + behavior.toString() + "]";
0316: }
0317:
0318: public void undo() {
0319: behaviors.remove(behavior);
0320: if (behaviors.size() == 0) {
0321: behaviors = null;
0322: }
0323: }
0324:
0325: }
0326:
0327: /**
0328: * Undo change for component border property
0329: *
0330: * @author ivaynberg
0331: */
0332: private class ComponentBorderChange extends Change {
0333: private static final long serialVersionUID = 1L;
0334:
0335: private final IComponentBorder old = getComponentBorder();
0336:
0337: public void undo() {
0338: setComponentBorder(old);
0339: }
0340:
0341: }
0342:
0343: /**
0344: * Change object for undoing removal of behavior
0345: *
0346: * @author Igor Vaynberg (ivaynberg)
0347: */
0348: private final class RemovedBehaviorChange extends Change {
0349:
0350: private static final long serialVersionUID = 1L;
0351:
0352: private final IBehavior behavior;
0353:
0354: /**
0355: * Construct.
0356: *
0357: * @param behavior
0358: */
0359: public RemovedBehaviorChange(IBehavior behavior) {
0360: this .behavior = behavior;
0361: }
0362:
0363: public String toString() {
0364: return "[" + getClass().getName() + " behavior="
0365: + behavior.toString() + "]";
0366: }
0367:
0368: public void undo() {
0369: if (behaviors == null) {
0370: behaviors = new ArrayList(1);
0371: }
0372: behaviors.add(behavior);
0373: }
0374:
0375: }
0376:
0377: /**
0378: * A enabled change operation.
0379: */
0380: protected final static class EnabledChange extends Change {
0381: private static final long serialVersionUID = 1L;
0382:
0383: /** Subject. */
0384: private final Component component;
0385:
0386: /** Former value. */
0387: private final boolean enabled;
0388:
0389: /**
0390: * Construct.
0391: *
0392: * @param component
0393: */
0394: EnabledChange(final Component component) {
0395: this .component = component;
0396: enabled = component.getFlag(FLAG_ENABLED);
0397: }
0398:
0399: /**
0400: * @see java.lang.Object#toString()
0401: */
0402: public String toString() {
0403: return "EnabledChange[component: " + component.getPath()
0404: + ",enabled: " + enabled + "]";
0405: }
0406:
0407: /**
0408: * @see org.apache.wicket.version.undo.Change#undo()
0409: */
0410: public void undo() {
0411: component.setEnabled(enabled);
0412: }
0413: }
0414:
0415: /**
0416: * A visibility change operation.
0417: */
0418: protected final static class VisibilityChange extends Change {
0419: private static final long serialVersionUID = 1L;
0420:
0421: /** Subject. */
0422: private final Component component;
0423:
0424: /** Former value. */
0425: private final boolean visible;
0426:
0427: /**
0428: * Construct.
0429: *
0430: * @param component
0431: */
0432: VisibilityChange(final Component component) {
0433: this .component = component;
0434: visible = component.getFlag(FLAG_VISIBLE);
0435: }
0436:
0437: /**
0438: * @see java.lang.Object#toString()
0439: */
0440: public String toString() {
0441: return "VisibilityChange[component: " + component.getPath()
0442: + ", visible: " + visible + "]";
0443: }
0444:
0445: /**
0446: * @see org.apache.wicket.version.undo.Change#undo()
0447: */
0448: public void undo() {
0449: component.setVisible(visible);
0450: }
0451: }
0452:
0453: /**
0454: * Action used with IAuthorizationStrategy to determine whether a component
0455: * is allowed to be enabled.
0456: * <p>
0457: * If enabling is authorized, a component may decide by itself (typically
0458: * using it's enabled property) whether it is enabled or not. If enabling is
0459: * not authorized, the given component is marked disabled, regardless its
0460: * enabled property.
0461: * <p>
0462: * When a component is not allowed to be enabled (in effect disabled through
0463: * the implementation of this interface), Wicket will try to prevent model
0464: * updates too. This is not completely fail safe, as constructs like:
0465: *
0466: * <pre>
0467: * User u = (User)getModelObject();
0468: * u.setName("got you there!");
0469: * </pre>
0470: *
0471: * can't be prevented. Indeed it can be argued that any model protection is
0472: * best dealt with in your model objects to be completely secured. Wicket
0473: * will catch all normal framework-directed use though.
0474: */
0475: public static final Action ENABLE = new Action(Action.ENABLE);
0476:
0477: /** Separator for component paths */
0478: public static final char PATH_SEPARATOR = ':';
0479:
0480: /**
0481: * Action used with IAuthorizationStrategy to determine whether a component
0482: * and its children are allowed to be rendered.
0483: * <p>
0484: * There are two uses for this method:
0485: * <ul>
0486: * <li>The 'normal' use is for controlling whether a component is rendered
0487: * without having any effect on the rest of the processing. If a strategy
0488: * lets this method return 'false', then the target component and its
0489: * children will not be rendered, in the same fashion as if that component
0490: * had visibility property 'false'.</li>
0491: * <li>The other use is when a component should block the rendering of the
0492: * whole page. So instead of 'hiding' a component, what we generally want to
0493: * achieve here is that we force the user to logon/give-credentials for a
0494: * higher level of authorization. For this functionality, the strategy
0495: * implementation should throw a {@link AuthorizationException}, which will
0496: * then be handled further by the framework.</li>
0497: * </ul>
0498: * </p>
0499: */
0500: public static final Action RENDER = new Action(Action.RENDER);
0501:
0502: /** meta data key for missing body tags logging. */
0503: private static final MetaDataKey BORDER_KEY = new MetaDataKey(
0504: IComponentBorder.class) {
0505: private static final long serialVersionUID = 1L;
0506: };
0507:
0508: /** Basic model IModelComparator implementation for normal object models */
0509: private static final IModelComparator defaultModelComparator = new IModelComparator() {
0510: private static final long serialVersionUID = 1L;
0511:
0512: public boolean compare(Component component, Object b) {
0513: final Object a = component.getModelObject();
0514: if (a == null && b == null) {
0515: return true;
0516: }
0517: if (a == null || b == null) {
0518: return false;
0519: }
0520: return a.equals(b);
0521: }
0522: };
0523:
0524: private static final int FLAG_AFTER_RENDERING = 0x8000000;
0525:
0526: /** True when a component is being auto-added */
0527: private static final int FLAG_AUTO = 0x0001;
0528:
0529: private static final int FLAG_BEFORE_RENDERING_SUPER_CALL_VERIFIED = 0x1000000;
0530:
0531: private static final int FLAG_DETACHING = 0x80000000;
0532:
0533: /** True when a component is enabled for model updates and is reachable. */
0534: private static final int FLAG_ENABLED = 0x0080;
0535:
0536: /** Flag for escaping HTML in model strings */
0537: private static final int FLAG_ESCAPE_MODEL_STRINGS = 0x0002;
0538:
0539: /**
0540: * Boolean whether this component was rendered at least once for tracking
0541: * changes.
0542: */
0543: private static final int FLAG_HAS_BEEN_RENDERED = 0x1000;
0544:
0545: /** Ignore attribute modifiers */
0546: private static final int FLAG_IGNORE_ATTRIBUTE_MODIFIER = 0x0040;
0547:
0548: /** Flag for escaping HTML in model strings */
0549: private static final int FLAG_INHERITABLE_MODEL = 0x0004;
0550:
0551: /**
0552: * Internal indicator of whether this component may be rendered given the
0553: * current context's authorization. It overrides the visible flag in case
0554: * this is false. Authorization is done before trying to render any
0555: * component (otherwise we would end up with a half rendered page in the
0556: * buffer)
0557: */
0558: private static final int FLAG_IS_RENDER_ALLOWED = 0x2000;
0559:
0560: /**
0561: * Whether or not the component should print out its markup id into the id
0562: * attribute
0563: */
0564: private static final int FLAG_OUTPUT_MARKUP_ID = 0x4000;
0565:
0566: /**
0567: * Ouput a placeholder tag if the component is not visible. This is useful
0568: * in ajax mode to go to visible(false) to visible(true) without the
0569: * overhead of repaiting a visible parent container
0570: */
0571:
0572: private static final int FLAG_PLACEHOLDER = 0x8000;
0573:
0574: /** Render tag boolean */
0575: private static final int FLAG_RENDER_BODY_ONLY = 0x0020;
0576:
0577: private static final int FLAG_RENDERING = 0x2000000;
0578:
0579: /** Versioning boolean */
0580: private static final int FLAG_VERSIONED = 0x0008;
0581:
0582: /** Visibility boolean */
0583: private static final int FLAG_VISIBLE = 0x0010;
0584:
0585: /** Log. */
0586: private static final Logger log = LoggerFactory
0587: .getLogger(Component.class);
0588:
0589: /**
0590: * The name of attribute that will hold markup id
0591: */
0592: private static final String MARKUP_ID_ATTR_NAME = "id";
0593:
0594: /**
0595: * Metadata key used to store/retrieve markup id
0596: */
0597: private static MetaDataKey MARKUP_ID_KEY = new MetaDataKey(
0598: String.class) {
0599:
0600: private static final long serialVersionUID = 1L;
0601:
0602: };
0603: private static final long serialVersionUID = 1L;
0604: /** Reserved subclass-definable flag bit */
0605: protected static final int FLAG_RESERVED1 = 0x0100;
0606: /** Reserved subclass-definable flag bit */
0607: protected static final int FLAG_RESERVED2 = 0x0200;
0608:
0609: /** Reserved subclass-definable flag bit */
0610: protected static final int FLAG_RESERVED3 = 0x0400;
0611: /** Reserved subclass-definable flag bit */
0612: protected static final int FLAG_RESERVED4 = 0x0800;
0613: /** Reserved subclass-definable flag bit */
0614: protected static final int FLAG_RESERVED5 = 0x10000;
0615:
0616: /** Reserved subclass-definable flag bit */
0617: protected static final int FLAG_RESERVED6 = 0x20000;
0618:
0619: /** Reserved subclass-definable flag bit */
0620: protected static final int FLAG_RESERVED7 = 0x40000;
0621:
0622: /** Reserved subclass-definable flag bit */
0623: protected static final int FLAG_RESERVED8 = 0x80000;
0624:
0625: /**
0626: * Meta data key for line precise error logging for the moment of addition.
0627: * Made package private for access in {@link MarkupContainer} and
0628: * {@link Page}
0629: */
0630: static final MetaDataKey ADDED_AT_KEY = new MetaDataKey(
0631: String.class) {
0632: private static final long serialVersionUID = 1L;
0633: };
0634:
0635: /**
0636: * meta data key for line precise error logging for the moment of
0637: * construction. Made package private for access in {@link Page}
0638: */
0639: static final MetaDataKey CONSTRUCTED_AT_KEY = new MetaDataKey(
0640: String.class) {
0641: private static final long serialVersionUID = 1L;
0642: };
0643:
0644: static final int FLAG_ATTACH_SUPER_CALL_VERIFIED = 0x10000000;
0645:
0646: static final int FLAG_ATTACHED = 0x20000000;
0647:
0648: static final int FLAG_ATTACHING = 0x40000000;
0649:
0650: /**
0651: * Flag that makes we are in before-render callback phase Set after
0652: * component.onBeforeRender is invoked (right before invoking beforeRender
0653: * on children)
0654: */
0655: static final int FLAG_PREPARED_FOR_RENDER = 0x4000000;
0656:
0657: /** List of behaviors to be applied for this Component */
0658: private ArrayList behaviors = null;
0659:
0660: /** Component flags. See FLAG_* for possible non-exclusive flag values. */
0661: private int flags = FLAG_VISIBLE | FLAG_ESCAPE_MODEL_STRINGS
0662: | FLAG_VERSIONED | FLAG_ENABLED | FLAG_IS_RENDER_ALLOWED;
0663:
0664: /** Component id. */
0665: private String id;
0666:
0667: /**
0668: * MetaDataEntry array.
0669: */
0670: private MetaDataEntry[] metaData;
0671:
0672: /** Any parent container. */
0673: private MarkupContainer parent;
0674:
0675: /**
0676: * I really dislike it, but for now we need it. Reason: due to transparent
0677: * containers and IComponentResolver there is guaranteed 1:1 mapping between
0678: * component and markup
0679: */
0680: int markupIndex = -1;
0681:
0682: /** The model for this component. */
0683: IModel model;
0684:
0685: /**
0686: * Constructor. All components have names. A component's id cannot be null.
0687: * This is the minimal constructor of component. It does not register a
0688: * model.
0689: *
0690: * @param id
0691: * The non-null id of this component
0692: * @throws WicketRuntimeException
0693: * Thrown if the component has been given a null id.
0694: */
0695: public Component(final String id) {
0696: setId(id);
0697: getApplication().notifyComponentInstantiationListeners(this );
0698:
0699: final IDebugSettings debugSettings = Application.get()
0700: .getDebugSettings();
0701: if (debugSettings.isLinePreciseReportingOnNewComponentEnabled()) {
0702: setMetaData(CONSTRUCTED_AT_KEY, Strings.toString(this ,
0703: new MarkupException("constructed")));
0704: }
0705: }
0706:
0707: /**
0708: * Constructor. All components have names. A component's id cannot be null.
0709: * This constructor includes a model.
0710: *
0711: * @param id
0712: * The non-null id of this component
0713: * @param model
0714: * The component's model
0715: *
0716: * @throws WicketRuntimeException
0717: * Thrown if the component has been given a null id.
0718: */
0719: public Component(final String id, final IModel model) {
0720: setId(id);
0721: getApplication().notifyComponentInstantiationListeners(this );
0722: this .model = wrap(model);
0723:
0724: final IDebugSettings debugSettings = Application.get()
0725: .getDebugSettings();
0726: if (debugSettings.isLinePreciseReportingOnNewComponentEnabled()) {
0727: setMetaData(CONSTRUCTED_AT_KEY, Strings.toString(this ,
0728: new MarkupException("constructed")));
0729: }
0730: }
0731:
0732: /**
0733: * Adds an behavior modifier to the component.
0734: *
0735: * <p>
0736: * Note: this method is override to enable users to do things like discussed
0737: * in <a
0738: * href="http://www.nabble.com/Why-add%28IBehavior%29-is-final--tf2598263.html#a7248198">this
0739: * thread</a>.
0740: * </p>
0741: *
0742: * @param behavior
0743: * The behavior modifier to be added
0744: * @return this (to allow method call chaining)
0745: */
0746: public Component add(final IBehavior behavior) {
0747: if (behavior == null) {
0748: throw new IllegalArgumentException(
0749: "Argument may not be null");
0750: }
0751:
0752: // Lazy create
0753: if (behaviors == null) {
0754: behaviors = new ArrayList(1);
0755: }
0756:
0757: behaviors.add(behavior);
0758:
0759: if (!behavior.isTemporary()) {
0760: addStateChange(new AddedBehaviorChange(behavior));
0761: }
0762:
0763: // Give handler the opportunity to bind this component
0764: behavior.bind(this );
0765:
0766: return this ;
0767: }
0768:
0769: /**
0770: * Called on very component after the page is renderd It will call
0771: * onAfterRender for it self and its childeren.
0772: */
0773: public final void afterRender() {
0774: // if the component has been previously attached via attach()
0775: // detach it now
0776: try {
0777: setFlag(FLAG_AFTER_RENDERING, true);
0778: onAfterRender();
0779: getApplication()
0780: .notifyComponentOnAfterRenderListeners(this );
0781: if (getFlag(FLAG_AFTER_RENDERING)) {
0782: throw new IllegalStateException(
0783: Component.class.getName()
0784: + " has not been properly detached. Something in the hierarchy of "
0785: + getClass().getName()
0786: + " has not called super.onAfterRender() in the override of onAfterRender() method");
0787: }
0788: // always detach children because components can be attached
0789: // independently of their parents
0790: onAfterRenderChildren();
0791: } finally {
0792: // this flag must always be set to false.
0793: setFlag(FLAG_RENDERING, false);
0794: }
0795: }
0796:
0797: /**
0798: * Attaches the component. This is called when the page is starting to be
0799: * used for rendering or when a component listener call is executed on it.
0800: */
0801: public final void attach() {
0802: internalAttach2();
0803: }
0804:
0805: /**
0806: * Called for every component when the page is getting to be rendered. it
0807: * will call onBeforeRender for this component and all the child components
0808: */
0809: public final void beforeRender() {
0810: if (isVisible() && !getFlag(FLAG_RENDERING)
0811: && !getFlag(FLAG_PREPARED_FOR_RENDER)) {
0812: setFlag(FLAG_BEFORE_RENDERING_SUPER_CALL_VERIFIED, false);
0813:
0814: onBeforeRender();
0815: getApplication().notifyComponentOnBeforeRenderListeners(
0816: this );
0817: if (!getFlag(FLAG_BEFORE_RENDERING_SUPER_CALL_VERIFIED)) {
0818: throw new IllegalStateException(
0819: Component.class.getName()
0820: + " has not been properly rendered. Something in the hierarchy of "
0821: + getClass().getName()
0822: + " has not called super.onBeforeRender() in the override of onBeforeRender() method");
0823: }
0824: }
0825: }
0826:
0827: /**
0828: * Redirects to any intercept page previously specified by a call to
0829: * redirectToInterceptPage.
0830: *
0831: * @return True if an original destination was redirected to
0832: * @see Component#redirectToInterceptPage(Page)
0833: */
0834: public final boolean continueToOriginalDestination() {
0835: return getPage().getPageMap().continueToOriginalDestination();
0836: }
0837:
0838: /**
0839: * Registers a debug feedback message for this component
0840: *
0841: * @param message
0842: * The feedback message
0843: */
0844: public final void debug(final String message) {
0845: Session.get().getFeedbackMessages().debug(this , message);
0846: Session.get().dirty();
0847: }
0848:
0849: /**
0850: * Detaches the component. This is called at the end of the request for all
0851: * the pages that are touched in that request.
0852: */
0853: public final void detach() {
0854: // if the component has been previously attached via attach()
0855: // detach it now
0856: setFlag(FLAG_DETACHING, true);
0857: onDetach();
0858: if (getFlag(FLAG_DETACHING)) {
0859: throw new IllegalStateException(
0860: Component.class.getName()
0861: + " has not been properly detached. Something in the hierarchy of "
0862: + getClass().getName()
0863: + " has not called super.onDetach() in the override of onDetach() method");
0864: }
0865: setFlag(FLAG_ATTACHED, false);
0866:
0867: // always detach models because they can be attached without the
0868: // component. eg component has a compoundpropertymodel and one of its
0869: // children component's getmodelobject is called
0870: detachModels();
0871:
0872: // always detach children because components can be attached
0873: // independently of their parents
0874: detachChildren();
0875:
0876: // reset the model to null when the current model is a IWrapModel and
0877: // the model that created it/wrapped in it is a IComponentInheritedModel
0878: // The model will be created next time.
0879: if (getFlag(FLAG_INHERITABLE_MODEL)) {
0880: model = null;
0881: setFlag(FLAG_INHERITABLE_MODEL, false);
0882: }
0883: }
0884:
0885: /**
0886: * THIS IS WICKET INTERNAL ONLY. DO NOT USE IT.
0887: *
0888: * Traverses all behaviors and calls detachModel() on them. This is needed
0889: * to cleanup behavior after render. This method is necessary for
0890: * {@link AjaxRequestTarget} to be able to cleanup component's behaviors
0891: * after header contribution has been done (which is separated from
0892: * component render).
0893: */
0894: public final void detachBehaviors() {
0895: if (behaviors != null) {
0896: for (Iterator i = behaviors.iterator(); i.hasNext();) {
0897: IBehavior behavior = (IBehavior) i.next();
0898:
0899: // Always detach models, 'accepted' or not. Otherwise, if they
0900: // are accepted during render, but not here - something can go
0901: // undetached, and calling isEnabled can also lead to nasty side
0902: // effects. See for instance Timo's comment on
0903: // http://issues.apache.org/jira/browse/WICKET-673
0904: behavior.detach(this );
0905:
0906: if (behavior.isTemporary()) {
0907: i.remove();
0908: }
0909: }
0910: }
0911: }
0912:
0913: /**
0914: * Detaches all models
0915: */
0916: public void detachModels() {
0917: // Detach any detachable model from this component
0918: detachModel();
0919:
0920: // detach any behaviors
0921: detachBehaviors();
0922: }
0923:
0924: /**
0925: * Registers an error feedback message for this component
0926: *
0927: * @param message
0928: * The feedback message
0929: */
0930: public final void error(final Serializable message) {
0931: Session.get().getFeedbackMessages().error(this , message);
0932: Session.get().dirty();
0933: }
0934:
0935: /**
0936: * Registers an fatal error feedback message for this component
0937: *
0938: * @param message
0939: * The feedback message
0940: */
0941: public final void fatal(final String message) {
0942: Session.get().getFeedbackMessages().fatal(this , message);
0943: Session.get().dirty();
0944: }
0945:
0946: /**
0947: * Finds the first container parent of this component of the given class.
0948: *
0949: * @param c
0950: * MarkupContainer class to search for
0951: * @return First container parent that is an instance of the given class, or
0952: * null if none can be found
0953: */
0954: public final MarkupContainer findParent(final Class c) {
0955: // Start with immediate parent
0956: MarkupContainer current = parent;
0957:
0958: // Walk up containment hierarchy
0959: while (current != null) {
0960: // Is current an instance of this class?
0961: if (c.isInstance(current)) {
0962: return current;
0963: }
0964:
0965: // Check parent
0966: current = current.getParent();
0967: }
0968:
0969: // Failed to find component
0970: return null;
0971: }
0972:
0973: /**
0974: * @return The nearest markup container with associated markup
0975: */
0976: public final MarkupContainer findParentWithAssociatedMarkup() {
0977: MarkupContainer container = parent;
0978: while (container != null) {
0979: if (container.hasAssociatedMarkup()) {
0980: return container;
0981: }
0982: container = container.getParent();
0983: }
0984:
0985: // This should never happen since Page always has associated markup
0986: throw new WicketRuntimeException(
0987: "Unable to find parent with associated markup");
0988: }
0989:
0990: /**
0991: * Gets interface to application that this component is a part of.
0992: *
0993: * @return The application associated with the session that this component
0994: * is in.
0995: * @see Application
0996: */
0997: public final Application getApplication() {
0998: return Application.get();
0999: }
1000:
1001: /**
1002: * Gets the currently coupled {@link IBehavior}s as a unmodifiable list.
1003: * Returns an empty list rather than null if there are no behaviors coupled
1004: * to this component.
1005: *
1006: * @return The currently coupled behaviors as a unmodifiable list
1007: */
1008: public final List/* <IBehavior> */getBehaviors() {
1009: return getBehaviors(null);
1010: }
1011:
1012: /**
1013: * @return A path of the form [page-class-name].[page-relative-path]
1014: * @see Component#getPageRelativePath()
1015: */
1016: public final String getClassRelativePath() {
1017: return getClass().getName() + PATH_SEPARATOR
1018: + getPageRelativePath();
1019: }
1020:
1021: /**
1022: * @return component border assigned to this component, or null if none
1023: */
1024: public final IComponentBorder getComponentBorder() {
1025: return (IComponentBorder) getMetaData(BORDER_KEY);
1026: }
1027:
1028: /**
1029: * @return nothing, will always throw an exception. Use
1030: * {@link #getConverter(Class)} instead.
1031: * @deprecated To be removed. Please use/ override
1032: * {@link #getConverter(Class)} instead.
1033: */
1034: public final IConverter getConverter() {
1035: throw new UnsupportedOperationException(
1036: "use #getConverter(Class) instead");
1037: }
1038:
1039: /**
1040: * Gets the converter that should be used by this component.
1041: *
1042: * @param type
1043: * The type to convert to
1044: *
1045: * @return The converter that should be used by this component
1046: */
1047: public IConverter getConverter(Class/* <?> */type) {
1048: return getApplication().getConverterLocator()
1049: .getConverter(type);
1050: }
1051:
1052: /**
1053: * Gets whether model strings should be escaped.
1054: *
1055: * @return Returns whether model strings should be escaped
1056: */
1057: public final boolean getEscapeModelStrings() {
1058: return getFlag(FLAG_ESCAPE_MODEL_STRINGS);
1059: }
1060:
1061: /**
1062: * @return Any feedback message for this component
1063: */
1064: public final FeedbackMessage getFeedbackMessage() {
1065: return Session.get().getFeedbackMessages().messageForComponent(
1066: this );
1067: }
1068:
1069: /**
1070: * Gets the id of this component.
1071: *
1072: * @return The id of this component
1073: */
1074: public String getId() {
1075: return id;
1076: }
1077:
1078: /**
1079: * @return Innermost model for this component
1080: */
1081: public final IModel getInnermostModel() {
1082: return getInnermostModel(getModel());
1083: }
1084:
1085: /**
1086: * Gets the locale for this component. By default, it searches it parent for
1087: * a locale. If no parents (it's a recursive search) returns a locale, it
1088: * gets one from the session.
1089: *
1090: * @return The locale to be used for this component
1091: * @see Session#getLocale()
1092: */
1093: public Locale getLocale() {
1094: Locale locale = null;
1095: if (parent != null) {
1096: locale = parent.getLocale();
1097: }
1098: return (locale != null) ? locale : getSession().getLocale();
1099: }
1100:
1101: /**
1102: * Convenience method to provide easy access to the localizer object within
1103: * any component.
1104: *
1105: * @return The localizer object
1106: */
1107: public final Localizer getLocalizer() {
1108: return getApplication().getResourceSettings().getLocalizer();
1109: }
1110:
1111: /**
1112: * THIS IS WICKET INTERNAL ONLY. DO NOT USE IT.
1113: *
1114: * Get a copy of the markup's attributes which are associated with the
1115: * component.
1116: * <p>
1117: * Modifications to the map returned don't change the tags attributes. It is
1118: * just a copy.
1119: * <p>
1120: * Note: The component must have been added (directly or indirectly) to a
1121: * container with an associated markup file (Page, Panel or Border).
1122: *
1123: * @return markup attributes
1124: */
1125: public final ValueMap getMarkupAttributes() {
1126: MarkupStream markupStream = locateMarkupStream();
1127: ValueMap attrs = new ValueMap(markupStream.getTag()
1128: .getAttributes());
1129: attrs.makeImmutable();
1130: return attrs;
1131: }
1132:
1133: /**
1134: * Retrieves id by which this component is represented within the markup.
1135: * This is either the id attribute set explicitly via a call to
1136: * {@link #setMarkupId(String)}, id attribute defined in the markup, or an
1137: * automatically generated id - in that order.
1138: * <p>
1139: * If no explicit id is set this function will generate an id value that
1140: * will be unique in the page. This is the preferred way as there is no
1141: * chance of id collision.
1142: * <p>
1143: * Note: This method should only be called after the component or its parent
1144: * have been added to the page.
1145: *
1146: * @return markup id of the component
1147: */
1148: public String getMarkupId() {
1149: String markupId = (String) getMetaData(MARKUP_ID_KEY);
1150: if (markupId == null) {
1151: Page page = findPage();
1152: if (page == null) {
1153: throw new WicketRuntimeException(
1154: "This component is not (yet) coupled to a page. It has to be able "
1155: + "to find the page it is supposed to operate in before you can call "
1156: + "this method (Component#getMarkupId)");
1157: }
1158: // try to read from markup
1159: // TODO getting the id from markup doesn't work everywhere yet.
1160: // unfortunately, we have to drop this until we have a good solution
1161: // for issue http://issues.apache.org/jira/browse/WICKET-694
1162: // markupId = getMarkupAttributes().getString("id");
1163:
1164: // if (markupId == null)
1165: // {
1166: // if not in the markup, generate one
1167:
1168: markupId = getId() + page.getAutoIndex();
1169: // make sure id is compliant with w3c requirements (starts with a
1170: // letter)
1171: char c = markupId.charAt(0);
1172: if (!Character.isLetter(c)) {
1173: markupId = "id" + markupId;
1174: }
1175:
1176: // }
1177: setMetaData(MARKUP_ID_KEY, markupId);
1178:
1179: }
1180: return markupId;
1181: }
1182:
1183: /**
1184: * Gets metadata for this component using the given key.
1185: *
1186: * @param key
1187: * The key for the data
1188: * @return The metadata or null of no metadata was found for the given key
1189: * @see MetaDataKey
1190: */
1191: public final Serializable getMetaData(final MetaDataKey key) {
1192: return key.get(metaData);
1193: }
1194:
1195: /**
1196: * Gets the model. It returns the object that wraps the backing model.
1197: *
1198: * @return The model
1199: */
1200: public final IModel getModel() {
1201: // If model is null
1202: if (model == null) {
1203: // give subclass a chance to lazy-init model
1204: model = initModel();
1205: }
1206:
1207: return model;
1208: }
1209:
1210: /**
1211: * Gets the backing model object; this is shorthand for
1212: * getModel().getObject().
1213: *
1214: * @return The backing model object
1215: */
1216: public final Object getModelObject() {
1217: final IModel model = getModel();
1218: if (model != null) {
1219: // Get model value for this component.
1220: return model.getObject();
1221: } else {
1222: return null;
1223: }
1224: }
1225:
1226: /**
1227: * Gets a model object as a string.
1228: *
1229: * @return Model object for this component as a string
1230: */
1231: public final String getModelObjectAsString() {
1232: return getModelObjectAsString(getModelObject());
1233: }
1234:
1235: /**
1236: * @param modelObject
1237: * Model object to convert to string
1238: * @return The string
1239: */
1240: public final String getModelObjectAsString(final Object modelObject) {
1241: if (modelObject != null) {
1242: // Get converter
1243: final IConverter converter = getConverter(modelObject
1244: .getClass());
1245:
1246: // Model string from property
1247: final String modelString = converter.convertToString(
1248: modelObject, getLocale());
1249:
1250: if (modelString != null) {
1251: // If we should escape the markup
1252: if (getFlag(FLAG_ESCAPE_MODEL_STRINGS)) {
1253: // Escape it
1254: return Strings.escapeMarkup(modelString, false,
1255: true).toString();
1256: }
1257: return modelString;
1258: }
1259: }
1260: return "";
1261: }
1262:
1263: /**
1264: * Gets whether or not component will output id attribute into the markup.
1265: * id attribute will be set to the value returned from
1266: * {@link Component#getMarkupId()}.
1267: *
1268: * @return whether or not component will output id attribute into the markup
1269: */
1270: public final boolean getOutputMarkupId() {
1271: return getFlag(FLAG_OUTPUT_MARKUP_ID);
1272: }
1273:
1274: /**
1275: * Gets the page holding this component.
1276: *
1277: * @return The page holding this component
1278: * @throws IllegalStateException
1279: * Thrown if component is not yet attached to a Page.
1280: */
1281: public final Page getPage() {
1282: // Search for nearest Page
1283: final Page page = findPage();
1284:
1285: // If no Page was found
1286: if (page == null) {
1287: // Give up with a nice exception
1288: throw new IllegalStateException(
1289: "No Page found for component " + this );
1290: }
1291:
1292: return page;
1293: }
1294:
1295: /**
1296: * @return The page factory for the session that this component is in
1297: */
1298: public final IPageFactory getPageFactory() {
1299: return getSession().getPageFactory();
1300: }
1301:
1302: /**
1303: * Gets the path to this component relative to the page it is in.
1304: *
1305: * @return The path to this component relative to the page it is in
1306: */
1307: public final String getPageRelativePath() {
1308: return Strings.afterFirstPathComponent(getPath(),
1309: PATH_SEPARATOR);
1310: }
1311:
1312: /**
1313: * Gets any parent container, or null if there is none.
1314: *
1315: * @return Any parent container, or null if there is none
1316: */
1317: public final MarkupContainer getParent() {
1318: return parent;
1319: }
1320:
1321: /**
1322: * Gets this component's path.
1323: *
1324: * @return Colon separated path to this component in the component hierarchy
1325: */
1326: public final String getPath() {
1327: final PrependingStringBuffer buffer = new PrependingStringBuffer(
1328: 32);
1329: for (Component c = this ; c != null; c = c.getParent()) {
1330: if (buffer.length() > 0) {
1331: buffer.prepend(PATH_SEPARATOR);
1332: }
1333: buffer.prepend(c.getId());
1334: }
1335: return buffer.toString();
1336: }
1337:
1338: /**
1339: * If false the component's tag will be printed as well as its body (which
1340: * is default). If true only the body will be printed, but not the
1341: * component's tag.
1342: *
1343: * @return If true, the component tag will not be printed
1344: */
1345: public final boolean getRenderBodyOnly() {
1346: return getFlag(FLAG_RENDER_BODY_ONLY);
1347: }
1348:
1349: /**
1350: * @return The request for this component's active request cycle
1351: */
1352: public final Request getRequest() {
1353: RequestCycle requestCycle = getRequestCycle();
1354: if (requestCycle == null) {
1355: // Happens often with WicketTester when one forgets to call
1356: // createRequestCycle()
1357: throw new WicketRuntimeException(
1358: "No RequestCycle is currently set!");
1359: }
1360: return requestCycle.getRequest();
1361: }
1362:
1363: /**
1364: * Gets the active request cycle for this component
1365: *
1366: * @return The request cycle
1367: */
1368: public final RequestCycle getRequestCycle() {
1369: return RequestCycle.get();
1370: }
1371:
1372: /**
1373: * @return The response for this component's active request cycle
1374: */
1375: public final Response getResponse() {
1376: return getRequestCycle().getResponse();
1377: }
1378:
1379: /**
1380: * Gets the current Session object.
1381: *
1382: * @return The Session that this component is in
1383: */
1384: public Session getSession() {
1385: return Session.get();
1386: }
1387:
1388: /**
1389: * @return Size of this Component in bytes
1390: */
1391: public long getSizeInBytes() {
1392: final MarkupContainer originalParent = parent;
1393: parent = null;
1394: long size = -1;
1395: try {
1396: size = Objects.sizeof(this );
1397: } catch (Exception e) {
1398: log
1399: .error("Exception getting size for component "
1400: + this , e);
1401: }
1402: parent = originalParent;
1403: return size;
1404: }
1405:
1406: /**
1407: * @param key
1408: * Key of string resource in property file
1409: * @return The String
1410: * @see Localizer
1411: */
1412: public final String getString(final String key) {
1413: return getString(key, null);
1414: }
1415:
1416: /**
1417: * @param key
1418: * The resource key
1419: * @param model
1420: * The model
1421: * @return The formatted string
1422: * @see Localizer
1423: */
1424: public final String getString(final String key, final IModel model) {
1425: return getLocalizer().getString(key, this , model);
1426: }
1427:
1428: /**
1429: * @param key
1430: * The resource key
1431: * @param model
1432: * The model
1433: * @param defaultValue
1434: * A default value if the string cannot be found
1435: * @return The formatted string
1436: * @see Localizer
1437: */
1438: public final String getString(final String key, final IModel model,
1439: final String defaultValue) {
1440: return getLocalizer().getString(key, this , model, defaultValue);
1441: }
1442:
1443: /**
1444: * Gets the style of this component (see {@link org.apache.wicket.Session}).
1445: *
1446: * @return The style of this component.
1447: *
1448: * @see org.apache.wicket.Session
1449: * @see org.apache.wicket.Session#getStyle()
1450: */
1451: public final String getStyle() {
1452: String variation = getVariation();
1453: String style = getSession().getStyle();
1454: if (variation != null && !"".equals(variation)) {
1455: if (style != null && !"".equals(style)) {
1456: style = variation + "_" + style;
1457: } else {
1458: style = variation;
1459: }
1460: }
1461: return style;
1462: }
1463:
1464: /**
1465: * Gets the variation string of this component that will be used to look up
1466: * markup for this component. Subclasses can override this method to define
1467: * by an instance what markup variation should be picked up. By default it
1468: * will return null or the value of a parent.
1469: *
1470: * @return The variation of this component.
1471: */
1472: public String getVariation() {
1473: String variation = null;
1474: if (parent != null) {
1475: variation = parent.getVariation();
1476: }
1477: return variation;
1478: }
1479:
1480: /**
1481: * Gets whether this component was rendered at least once.
1482: *
1483: * @return true if the component has been rendered before, false if it is
1484: * merely constructed
1485: */
1486: public final boolean hasBeenRendered() {
1487: return getFlag(FLAG_HAS_BEEN_RENDERED);
1488: }
1489:
1490: /**
1491: * @return True if this component has an error message
1492: */
1493: public final boolean hasErrorMessage() {
1494: return Session.get().getFeedbackMessages().hasErrorMessageFor(
1495: this );
1496: }
1497:
1498: /**
1499: * @return True if this component has some kind of feedback message
1500: */
1501: public final boolean hasFeedbackMessage() {
1502: return Session.get().getFeedbackMessages().hasMessageFor(this );
1503: }
1504:
1505: /**
1506: * Registers an informational feedback message for this component
1507: *
1508: * @param message
1509: * The feedback message
1510: */
1511: public final void info(final String message) {
1512: Session.get().getFeedbackMessages().info(this , message);
1513: Session.get().dirty();
1514: }
1515:
1516: /**
1517: * @deprecated
1518: */
1519: // TODO remove after deprecation release
1520: public final void internalAttach() {
1521: throw new UnsupportedOperationException();
1522: }
1523:
1524: /**
1525: * @deprecated
1526: */
1527: // TODO remove after deprecation release
1528: public final void internalDetach() {
1529: throw new UnsupportedOperationException();
1530: }
1531:
1532: /**
1533: * Authorizes an action for a component.
1534: *
1535: * @param action
1536: * The action to authorize
1537: * @return True if the action is allowed
1538: * @throws AuthorizationException
1539: * Can be thrown by implementation if action is unauthorized
1540: */
1541: public final boolean isActionAuthorized(Action action) {
1542: IAuthorizationStrategy authorizationStrategy = getSession()
1543: .getAuthorizationStrategy();
1544: if (authorizationStrategy != null) {
1545: return authorizationStrategy.isActionAuthorized(this ,
1546: action);
1547: }
1548: return true;
1549: }
1550:
1551: /**
1552: * Returns true if this component is an ancestor of the given component
1553: *
1554: * @param component
1555: * The component to check
1556: * @return True if the given component has this component as an ancestor
1557: * @deprecated use getParent().contains(component, false)
1558: */
1559: public final boolean isAncestorOf(final Component component) {
1560: return getParent().contains(component, false);
1561: // // Walk up containment hierarchy
1562: // for (MarkupContainer current = component.parent; current != null;
1563: // current = current
1564: // .getParent())
1565: // {
1566: // // Is this an ancestor?
1567: // if (current == this)
1568: // {
1569: // return true;
1570: // }
1571: // }
1572: //
1573: // // This component is not an ancestor of the given component
1574: // return false;
1575: }
1576:
1577: /**
1578: * @return true if this component is authorized to be enabled, false
1579: * otherwise
1580: */
1581: public final boolean isEnableAllowed() {
1582: return isActionAuthorized(ENABLE);
1583: }
1584:
1585: /**
1586: * Gets whether this component is enabled. Specific components may decide to
1587: * implement special behavior that uses this property, like web form
1588: * components that add a disabled='disabled' attribute when enabled is
1589: * false.
1590: *
1591: * @return Whether this component is enabled.
1592: */
1593: public boolean isEnabled() {
1594: return getFlag(FLAG_ENABLED);
1595: }
1596:
1597: /**
1598: * Checks the security strategy if the {@link Component#RENDER} action is
1599: * allowed on this component
1600: *
1601: * @return ture if {@link Component#RENDER} action is allowed, false
1602: * otherwise
1603: */
1604: public final boolean isRenderAllowed() {
1605: return getFlag(FLAG_IS_RENDER_ALLOWED);
1606: }
1607:
1608: /**
1609: * Returns if the component is stateless or not. It checks the stateless
1610: * hint if that is false it returns directly false. If that is still true it
1611: * checks all its behaviours if they can be stateless.
1612: *
1613: * @return whether the component is stateless.
1614: */
1615: public final boolean isStateless() {
1616: if (!getStatelessHint()) {
1617: return false;
1618: }
1619:
1620: final Iterator behaviors = getBehaviors().iterator();
1621:
1622: while (behaviors.hasNext()) {
1623: IBehavior behavior = (IBehavior) behaviors.next();
1624: if (!behavior.getStatelessHint(this )) {
1625: return false;
1626: }
1627: }
1628: return true;
1629: }
1630:
1631: /**
1632: * @return True if this component is versioned
1633: */
1634: public boolean isVersioned() {
1635: // Is the component itself versioned?
1636: if (!getFlag(FLAG_VERSIONED)
1637: || !getFlag(FLAG_HAS_BEEN_RENDERED)) {
1638: return false;
1639: } else {
1640: // If there's a parent and this component is versioned
1641: if (parent != null) {
1642: // Check if the parent is unversioned. If any parent
1643: // (recursively) is unversioned, then this component is too
1644: if (!parent.isVersioned()) {
1645: return false;
1646: }
1647: }
1648: return true;
1649: }
1650: }
1651:
1652: /**
1653: * Gets whether this component and any children are visible.
1654: * <p>
1655: * WARNING: this method can be called multiple times during a request. If
1656: * you override this method, it is a good idea to keep it cheap in terms of
1657: * processing. Alternatively, you can call {@link #setVisible(boolean)}.
1658: * <p>
1659: *
1660: * @return True if component and any children are visible
1661: */
1662: public boolean isVisible() {
1663: return getFlag(FLAG_VISIBLE);
1664: }
1665:
1666: /**
1667: * Checks if the component itself and all its parents are visible.
1668: *
1669: * @return true if the component and all its parents are visible.
1670: */
1671: public final boolean isVisibleInHierarchy() {
1672: Component component = this ;
1673: while (component != null) {
1674: if (component.isRenderAllowed() && component.isVisible()) {
1675: component = component.getParent();
1676: } else {
1677: return false;
1678: }
1679: }
1680: return true;
1681: }
1682:
1683: /**
1684: * Sets the RENDERING flag on component and it's children.
1685: */
1686: public final void markRendering() {
1687: internalMarkRendering();
1688: }
1689:
1690: /**
1691: * Called to indicate that the model content for this component has been
1692: * changed
1693: */
1694: public final void modelChanged() {
1695: // Call user code
1696: internalOnModelChanged();
1697: onModelChanged();
1698: }
1699:
1700: /**
1701: * Called to indicate that the model content for this component is about to
1702: * change
1703: */
1704: public final void modelChanging() {
1705: checkHierarchyChange(this );
1706:
1707: // Call user code
1708: onModelChanging();
1709:
1710: // Tell the page that our model changed
1711: final Page page = findPage();
1712: if (page != null) {
1713: page.componentModelChanging(this );
1714: }
1715: }
1716:
1717: /**
1718: * Creates a new page using the component's page factory
1719: *
1720: * @param c
1721: * The class of page to create
1722: * @return The new page
1723: */
1724: public final Page newPage(final Class c) {
1725: return getPageFactory().newPage(c);
1726: }
1727:
1728: /**
1729: * Creates a new page using the component's page factory
1730: *
1731: * @param c
1732: * The class of page to create
1733: * @param parameters
1734: * Any parameters to pass to the constructor
1735: * @return The new page
1736: */
1737: public final Page newPage(final Class c,
1738: final PageParameters parameters) {
1739: return getPageFactory().newPage(c, parameters);
1740: }
1741:
1742: /**
1743: * Prepares the component and it's children for rendering. On whole page
1744: * render this method must be called on the page. On AJAX request, this
1745: * method must be called on updated component.
1746: */
1747: public void prepareForRender() {
1748: beforeRender();
1749: markRendering();
1750: }
1751:
1752: /**
1753: * Redirects browser to an intermediate page such as a sign-in page. The
1754: * current request's url is saved for future use by method
1755: * continueToOriginalDestination(); Only use this method when you plan to
1756: * continue to the current url at some later time; otherwise just use
1757: * setResponsePage or - when you are in a constructor or checkAccessMethod,
1758: * call redirectTo.
1759: *
1760: * @param page
1761: * The sign in page
1762: *
1763: * @see Component#continueToOriginalDestination()
1764: */
1765: public final void redirectToInterceptPage(final Page page) {
1766: getPage().getPageMap().redirectToInterceptPage(page);
1767: }
1768:
1769: /**
1770: * Removes this component from its parent. It's important to remember that a
1771: * component that is removed cannot be referenced from the markup still.
1772: */
1773: public final void remove() {
1774: if (parent == null) {
1775: throw new IllegalStateException("Cannot remove " + this
1776: + " from null parent!");
1777: }
1778:
1779: parent.remove(this );
1780: }
1781:
1782: /**
1783: * Removes behavior from component
1784: *
1785: * @param behavior
1786: * behavior to remove
1787: *
1788: * @return this (to allow method call chaining)
1789: */
1790: public Component remove(final IBehavior behavior) {
1791: if (behavior == null) {
1792: throw new IllegalArgumentException(
1793: "Argument `behavior` cannot be null");
1794: }
1795: if (!behaviors.contains(behavior)) {
1796: throw new IllegalStateException(
1797: "Tried to remove a behavior that was not added to the component. Behavior: "
1798: + behavior.toString());
1799: }
1800:
1801: if (!behavior.isTemporary()) {
1802: addStateChange(new RemovedBehaviorChange(behavior));
1803: }
1804: behaviors.remove(behavior);
1805:
1806: if (behaviors.size() == 0) {
1807: behaviors = null;
1808: }
1809:
1810: return this ;
1811: }
1812:
1813: /**
1814: * Performs a render of this component as part of a Page level render
1815: * process.
1816: * <p>
1817: * For component level re-render (e.g. AJAX) please call
1818: * {@link #renderComponent()}. Though render() does seem to work, it will
1819: * fail for panel children.
1820: */
1821: public final void render() {
1822: // Allow currently invisible components to be re-rendered as well
1823: MarkupStream markupStream = null;
1824: if (getParent() != null) {
1825: markupStream = findMarkupStream();
1826: }
1827:
1828: render(markupStream);
1829: }
1830:
1831: /**
1832: * Performs a render of this component as part of a Page level render
1833: * process.
1834: * <p>
1835: * For component level re-render (e.g. AJAX) please call
1836: * {@link #renderComponent(MarkupStream)}. Though render() does seem to
1837: * work, it will fail for panel children.
1838: *
1839: * @param markupStream
1840: */
1841: public final void render(final MarkupStream markupStream) {
1842: // We need to know the index before we do the visibility check.
1843: // Otherwise
1844: // we wouldn't know the markup index for invisible components
1845: if (markupStream != null) {
1846: markupIndex = markupStream.getCurrentIndex();
1847: }
1848:
1849: markRendering();
1850:
1851: setMarkupStream(markupStream);
1852: setFlag(FLAG_HAS_BEEN_RENDERED, true);
1853:
1854: // Determine if component is visible using it's authorization status
1855: // and the isVisible property.
1856: if (isRenderAllowed() && isVisible()) {
1857: // Rendering is beginning
1858: if (log.isDebugEnabled()) {
1859: log.debug("Begin render " + this );
1860: }
1861:
1862: try {
1863: // Call implementation to render component
1864: notifyBehaviorsComponentBeforeRender();
1865: final IComponentBorder border = getComponentBorder();
1866: if (border != null) {
1867: border.renderBefore(this );
1868: }
1869: onRender(markupStream);
1870: if (border != null) {
1871: border.renderAfter(this );
1872: }
1873: // Component has been rendered
1874: rendered();
1875: } catch (RuntimeException ex) {
1876: // Call each behaviors onException() to allow the
1877: // behavior to clean up
1878: if (behaviors != null) {
1879: for (Iterator i = behaviors.iterator(); i.hasNext();) {
1880: IBehavior behavior = (IBehavior) i.next();
1881: if (isBehaviorAccepted(behavior)) {
1882: try {
1883: behavior.exception(this , ex);
1884: } catch (Throwable ex2) {
1885: log
1886: .error(
1887: "Error while cleaning up after exception",
1888: ex2);
1889: }
1890: }
1891: }
1892: }
1893:
1894: // Re-throw the exception
1895: throw ex;
1896: }
1897:
1898: if (log.isDebugEnabled()) {
1899: log.debug("End render " + this );
1900: }
1901: }
1902: // markupStream is null when rendering a page
1903: else if (markupStream != null) {
1904: if (getFlag(FLAG_PLACEHOLDER)) {
1905: // write out a placeholder tag into the markup
1906: final ComponentTag tag = markupStream.getTag();
1907:
1908: getResponse().write("<");
1909: getResponse().write(tag.getName());
1910: getResponse().write(" id=\"");
1911: getResponse().write(getMarkupId());
1912: getResponse().write("\" style=\"display:none\"></");
1913: getResponse().write(tag.getName());
1914: getResponse().write(">");
1915: }
1916: markupStream.skipComponent();
1917: }
1918: }
1919:
1920: /**
1921: * Page.renderPage() is used to render a whole page. With AJAX however it
1922: * must be possible to render any one component contained in a page. That is
1923: * what this method is for.
1924: * <p>
1925: * Note: it is not necessary that the page has previously been rendered. But
1926: * the component must have been added (directly or indirectly) to a
1927: * container with an associated markup file (Page, Panel or Border).
1928: */
1929: public final void renderComponent() {
1930: // If this Component is a Page
1931: if (this instanceof Page) {
1932: // Render as Page, with all the special logic that entails
1933: ((Page) this ).renderPage();
1934: } else {
1935: // Save the parent's markup stream to re-assign it at the end
1936: MarkupContainer parent = getParent();
1937: MarkupStream originalMarkupStream = parent
1938: .getMarkupStream();
1939: MarkupStream markupStream = locateMarkupStream();
1940:
1941: try {
1942: // Make sure that while rendering the markup stream is found
1943: parent.setMarkupStream(markupStream);
1944:
1945: beforeRender();
1946: markRendering();
1947: // check authorization
1948: // first the component itself
1949: // (after attach as otherwise list views etc wont work)
1950: setRenderAllowed(isActionAuthorized(RENDER));
1951: // check children if this is a container
1952: if (this instanceof MarkupContainer) {
1953: MarkupContainer container = (MarkupContainer) this ;
1954: container.visitChildren(new IVisitor() {
1955: public Object component(
1956: final Component component) {
1957: // Find out if this component can be rendered
1958: final boolean renderAllowed = component
1959: .isActionAuthorized(RENDER);
1960: // Authorize rendering
1961: component.setRenderAllowed(renderAllowed);
1962: return IVisitor.CONTINUE_TRAVERSAL;
1963: }
1964: });
1965: }
1966:
1967: // Render the component and all its children
1968: render(markupStream);
1969: } finally {
1970: // Make sure the original markup stream is back in place
1971: parent.setMarkupStream(originalMarkupStream);
1972: afterRender();
1973: }
1974: }
1975: }
1976:
1977: /**
1978: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT.
1979: * <p>
1980: * Renders the component at the current position in the given markup stream.
1981: * The method onComponentTag() is called to allow the component to mutate
1982: * the start tag. The method onComponentTagBody() is then called to permit
1983: * the component to render its body.
1984: *
1985: * @param markupStream
1986: * The markup stream
1987: */
1988: public final void renderComponent(final MarkupStream markupStream) {
1989: markupIndex = markupStream.getCurrentIndex();
1990:
1991: // Get mutable copy of next tag
1992: final ComponentTag openTag = markupStream.getTag();
1993: final ComponentTag tag = openTag.mutable();
1994:
1995: // Call any tag handler
1996: onComponentTag(tag);
1997:
1998: // If we're an openclose tag
1999: if (!tag.isOpenClose() && !tag.isOpen()) {
2000: // We were something other than <tag> or <tag/>
2001: markupStream
2002: .throwMarkupException("Method renderComponent called on bad markup element: "
2003: + tag);
2004: }
2005:
2006: if (tag.isOpenClose() && openTag.isOpen()) {
2007: markupStream
2008: .throwMarkupException("You can not modify a open tag to open-close: "
2009: + tag);
2010: }
2011:
2012: try {
2013: // Render open tag
2014: if (getRenderBodyOnly() == false) {
2015: renderComponentTag(tag);
2016: }
2017: markupStream.next();
2018:
2019: // Render the body only if open-body-close. Do not render if
2020: // open-close.
2021: if (tag.isOpen()) {
2022: // Render the body
2023: onComponentTagBody(markupStream, tag);
2024: }
2025:
2026: // Render close tag
2027: if (tag.isOpen()) {
2028: if (openTag.isOpen()) {
2029: renderClosingComponentTag(markupStream, tag,
2030: getRenderBodyOnly());
2031: } else {
2032: // If a open-close tag has been to modified to be
2033: // open-body-close than a synthetic close tag must be
2034: // rendered.
2035: if (getRenderBodyOnly() == false) {
2036: final boolean stripWicketTags = Application
2037: .get().getMarkupSettings()
2038: .getStripWicketTags();
2039: if (!(openTag instanceof WicketTag)
2040: || !stripWicketTags) {
2041: // Close the manually opened panel tag.
2042: getResponse().write(
2043: openTag.syntheticCloseTagString());
2044: }
2045: }
2046: }
2047: }
2048: } catch (RuntimeException re) {
2049: if (re instanceof WicketRuntimeException
2050: || re instanceof AbortException) {
2051: throw re;
2052: }
2053: throw new WicketRuntimeException(
2054: "Exception in rendering component: " + this , re);
2055: }
2056: }
2057:
2058: /**
2059: * Called to indicate that a component has been rendered. This method should
2060: * only very rarely be called at all. One usage is in ImageMap, which
2061: * renders its link children its own special way (without calling render()
2062: * on them). If ImageMap did not call rendered() to indicate that its child
2063: * components were actually rendered, the framework would think they had
2064: * never been rendered, and in development mode this would result in a
2065: * runtime exception.
2066: */
2067: public final void rendered() {
2068: notifyBehaviorsComponentRendered();
2069: // Tell the page that the component rendered
2070: getPage().componentRendered(this );
2071: }
2072:
2073: /**
2074: * Print to the web response what ever the component wants to contribute to
2075: * the head section. Make sure that all attached behaviors are asked as
2076: * well.
2077: * <p>
2078: * NOT intended for overriding by framework clients. Rather, use
2079: * {@link IHeaderContributor#renderHead(org.apache.wicket.markup.html.IHeaderResponse)}
2080: * </p>
2081: *
2082: * @param container
2083: * The HtmlHeaderContainer
2084: */
2085: public void renderHead(final HtmlHeaderContainer container) {
2086: if (isVisible()) {
2087: if (this instanceof IHeaderContributor) {
2088: ((IHeaderContributor) this ).renderHead(container
2089: .getHeaderResponse());
2090: }
2091:
2092: // Ask all behaviors if they have something to contribute to the
2093: // header or body onLoad tag.
2094: if (behaviors != null) {
2095: final Iterator iter = behaviors.iterator();
2096: while (iter.hasNext()) {
2097: IBehavior behavior = (IBehavior) iter.next();
2098: if (behavior instanceof IHeaderContributor
2099: && isBehaviorAccepted(behavior)) {
2100: ((IHeaderContributor) behavior)
2101: .renderHead(container
2102: .getHeaderResponse());
2103: }
2104: }
2105: }
2106: }
2107: }
2108:
2109: /**
2110: * Replaces this component with another. The replacing component must have
2111: * the same component id as this component. This method serves as a shortcut
2112: * to <code>this.getParent().replace(replacement)</code> and provides a
2113: * better context for errors.
2114: *
2115: * @since 1.2.1
2116: *
2117: * @param replacement
2118: * component to replace this one
2119: */
2120: public void replaceWith(Component replacement) {
2121: if (replacement == null) {
2122: throw new IllegalArgumentException(
2123: "Argument [[replacement]] cannot be null.");
2124: }
2125: if (!getId().equals(replacement.getId())) {
2126: throw new IllegalArgumentException(
2127: "Replacement component must have the same id as the component it will replace. Replacement id [["
2128: + replacement.getId()
2129: + "]], replaced id [[" + getId() + "]].");
2130: }
2131: if (parent == null) {
2132: throw new IllegalStateException(
2133: "This method can only be called on a component that has already been added to its parent.");
2134: }
2135: parent.replace(replacement);
2136: }
2137:
2138: /**
2139: * @param component
2140: * The component to compare with
2141: * @return True if the given component's model is the same as this
2142: * component's model.
2143: */
2144: public final boolean sameInnermostModel(final Component component) {
2145: return sameInnermostModel(component.getModel());
2146: }
2147:
2148: /**
2149: * @param model
2150: * The model to compare with
2151: * @return True if the given component's model is the same as this
2152: * component's model.
2153: */
2154: public final boolean sameInnermostModel(final IModel model) {
2155: // Get the two models
2156: IModel this Model = getModel();
2157: IModel thatModel = model;
2158:
2159: // If both models are non-null they could be the same
2160: if (this Model != null && thatModel != null) {
2161: return getInnermostModel(this Model) == getInnermostModel(thatModel);
2162: }
2163:
2164: return false;
2165: }
2166:
2167: /**
2168: * Assigns a component border to this component. If called with
2169: * <code>null</code> any previous border will be cleared.
2170: *
2171: * @param border
2172: * componnet border to assign, or <code>null</code> to clear
2173: * any previous
2174: * @return component for chaining
2175: */
2176: public final Component setComponentBorder(
2177: final IComponentBorder border) {
2178: if (!Objects.equal(getComponentBorder(), border)) {
2179: addStateChange(new ComponentBorderChange());
2180: }
2181: setMetaData(BORDER_KEY, border);
2182: return this ;
2183: }
2184:
2185: /**
2186: * Sets whether this component is enabled. Specific components may decide to
2187: * implement special behavior that uses this property, like web form
2188: * components that add a disabled='disabled' attribute when enabled is
2189: * false. If it is not enabled, it will not be allowed to call any listener
2190: * method on it (e.g. Link.onClick) and the model object will be protected
2191: * (for the common use cases, not for programmer's misuse)
2192: *
2193: * @param enabled
2194: * whether this component is enabled
2195: * @return This
2196: */
2197: public final Component setEnabled(final boolean enabled) {
2198: // Is new enabled state a change?
2199: if (enabled != getFlag(FLAG_ENABLED)) {
2200: // Tell the page that this component's enabled was changed
2201: if (isVersioned()) {
2202: final Page page = findPage();
2203: if (page != null) {
2204: addStateChange(new EnabledChange(this ));
2205: }
2206: }
2207:
2208: // Change visibility
2209: setFlag(FLAG_ENABLED, enabled);
2210: }
2211: return this ;
2212: }
2213:
2214: /**
2215: * Sets whether model strings should be escaped.
2216: *
2217: * @param escapeMarkup
2218: * True is model strings should be escaped
2219: * @return This
2220: */
2221: public final Component setEscapeModelStrings(
2222: final boolean escapeMarkup) {
2223: setFlag(FLAG_ESCAPE_MODEL_STRINGS, escapeMarkup);
2224: return this ;
2225: }
2226:
2227: /**
2228: * Sets this component's markup id to a user defined value. It is up to the
2229: * user to ensure this value is unique.
2230: * <p>
2231: * The recommended way is to let wicket generate the value automatically,
2232: * this method is here to serve as an override for that value in cases where
2233: * a specific id must be used.
2234: * <p>
2235: * If null is passed in the user defined value is cleared and markup id
2236: * value will fall back on automatically generated value
2237: *
2238: * @see #getMarkupId()
2239: *
2240: * @param markupId
2241: * markup id value or null to clear any previous user defined
2242: * value
2243: */
2244: public void setMarkupId(String markupId) {
2245: if (markupId != null && Strings.isEmpty(markupId)) {
2246: throw new IllegalArgumentException(
2247: "Markup id cannot be an empty string");
2248: }
2249: setMetaData(MARKUP_ID_KEY, markupId);
2250:
2251: }
2252:
2253: /**
2254: * Sets the metadata for this component using the given key. If the metadata
2255: * object is not of the correct type for the metadata key, an
2256: * IllegalArgumentException will be thrown. For information on creating
2257: * MetaDataKeys, see {@link MetaDataKey}.
2258: *
2259: * @param key
2260: * The singleton key for the metadata
2261: * @param object
2262: * The metadata object
2263: * @throws IllegalArgumentException
2264: * @see MetaDataKey
2265: */
2266: public final void setMetaData(final MetaDataKey key,
2267: final Serializable object) {
2268: metaData = key.set(metaData, object);
2269: }
2270:
2271: /**
2272: * Sets the given model.
2273: * <p>
2274: * WARNING: DO NOT OVERRIDE THIS METHOD UNLESS YOU HAVE A VERY GOOD REASON
2275: * FOR IT. OVERRIDING THIS MIGHT OPEN UP SECURITY LEAKS AND BREAK
2276: * BACK-BUTTON SUPPORT.
2277: * </p>
2278: *
2279: * @param model
2280: * The model
2281: * @return This
2282: */
2283: public Component setModel(final IModel model) {
2284: // Detach current model
2285: if (this .model != null) {
2286: this .model.detach();
2287: }
2288:
2289: IModel prevModel = this .model;
2290: if (prevModel instanceof IWrapModel) {
2291: prevModel = ((IWrapModel) prevModel).getWrappedModel();
2292: }
2293:
2294: // Change model
2295: if (prevModel != model) {
2296: if (prevModel != null) {
2297: addStateChange(new ComponentModelChange(prevModel));
2298: }
2299:
2300: this .model = wrap(model);
2301: }
2302:
2303: modelChanged();
2304: return this ;
2305: }
2306:
2307: /**
2308: * Sets the backing model object; shorthand for
2309: * getModel().setObject(object).
2310: *
2311: * @param object
2312: * The object to set
2313: * @return This
2314: */
2315: public final Component setModelObject(final Object object) {
2316: final IModel model = getModel();
2317:
2318: // Check whether anything can be set at all
2319: if (model == null) {
2320: throw new IllegalStateException(
2321: "Attempt to set model object on null model of component: "
2322: + getPageRelativePath());
2323: }
2324:
2325: // Check authorization
2326: if (!isActionAuthorized(ENABLE)) {
2327: throw new UnauthorizedActionException(this , ENABLE);
2328: }
2329:
2330: // Check whether this will result in an actual change
2331: if (!getModelComparator().compare(this , object)) {
2332: modelChanging();
2333: model.setObject(object);
2334: modelChanged();
2335: }
2336:
2337: return this ;
2338: }
2339:
2340: /**
2341: * Sets whether or not component will output id attribute into the markup.
2342: * id attribute will be set to the value returned from
2343: * {@link Component#getMarkupId()}.
2344: *
2345: * @param output
2346: * True if the component will out the id attribute into markup.
2347: * Please note that the default behavior is to use the same id as
2348: * the component. This means that your component must begin with
2349: * [a-zA-Z] in order to generate a valid markup id according to:
2350: * http://www.w3.org/TR/html401/types.html#type-name
2351: *
2352: * @return this for chaining
2353: */
2354: public final Component setOutputMarkupId(final boolean output) {
2355: setFlag(FLAG_OUTPUT_MARKUP_ID, output);
2356: return this ;
2357: }
2358:
2359: /**
2360: * Render a placeholder tag when the component is not visible. The tag is of
2361: * form: <componenttag style="display:none;" id="componentid"/>. This
2362: * method will also call <code>setOutputMarkupId(true)</code>.
2363: *
2364: * This is useful, for example, in ajax situations where the component
2365: * starts out invisible and then becomes visible through an ajax update.
2366: * With a placeholder tag already in the markup you do not need to repaint
2367: * this component's parent, instead you can repaint the component directly.
2368: *
2369: * When this method is called with parameter <code>false</code> the
2370: * outputmarkupid flag is not reverted to false.
2371: *
2372: * @param outputTag
2373: * @return this for chaining
2374: */
2375: public final Component setOutputMarkupPlaceholderTag(
2376: final boolean outputTag) {
2377: if (outputTag != getFlag(FLAG_PLACEHOLDER)) {
2378: if (outputTag) {
2379: setOutputMarkupId(true);
2380: setFlag(FLAG_PLACEHOLDER, true);
2381: } else {
2382: setFlag(FLAG_PLACEHOLDER, false);
2383: // I think it's better to not setOutputMarkupId to false...
2384: // user can do it if we want
2385: }
2386: }
2387: return this ;
2388: }
2389:
2390: /**
2391: * @param redirect
2392: * True if the response should be redirected to
2393: * @see RequestCycle#setRedirect(boolean)
2394: */
2395: public final void setRedirect(final boolean redirect) {
2396: getRequestCycle().setRedirect(redirect);
2397: }
2398:
2399: /**
2400: * If false the component's tag will be printed as well as its body (which
2401: * is default). If true only the body will be printed, but not the
2402: * component's tag.
2403: *
2404: * @param renderTag
2405: * If true, the component tag will not be printed
2406: * @return This
2407: */
2408: public final Component setRenderBodyOnly(final boolean renderTag) {
2409: this .setFlag(FLAG_RENDER_BODY_ONLY, renderTag);
2410: return this ;
2411: }
2412:
2413: /**
2414: * Sets the page that will respond to this request
2415: *
2416: * @param cls
2417: * The response page class
2418: * @see RequestCycle#setResponsePage(Class)
2419: */
2420: public final void setResponsePage(final Class cls) {
2421: getRequestCycle().setResponsePage(cls);
2422: }
2423:
2424: /**
2425: * Sets the page class and its parameters that will respond to this request
2426: *
2427: * @param cls
2428: * The response page class
2429: * @param parameters
2430: * The parameters for this bookmarkable page.
2431: * @see RequestCycle#setResponsePage(Class, PageParameters)
2432: */
2433: public final void setResponsePage(final Class cls,
2434: PageParameters parameters) {
2435: getRequestCycle().setResponsePage(cls, parameters);
2436: }
2437:
2438: /**
2439: * Sets the page that will respond to this request
2440: *
2441: * @param page
2442: * The response page
2443: * @see RequestCycle#setResponsePage(Page)
2444: */
2445: public final void setResponsePage(final Page page) {
2446: getRequestCycle().setResponsePage(page);
2447: }
2448:
2449: /**
2450: * @param versioned
2451: * True to turn on versioning for this component, false to turn
2452: * it off for this component and any children.
2453: * @return This
2454: */
2455: public Component setVersioned(boolean versioned) {
2456: setFlag(FLAG_VERSIONED, versioned);
2457: return this ;
2458: }
2459:
2460: /**
2461: * Sets whether this component and any children are visible.
2462: *
2463: * @param visible
2464: * True if this component and any children should be visible
2465: * @return This
2466: */
2467: public final Component setVisible(final boolean visible) {
2468: // Is new visibility state a change?
2469: if (visible != getFlag(FLAG_VISIBLE)) {
2470: // record component's visibility change
2471: addStateChange(new VisibilityChange(this ));
2472:
2473: // Change visibility
2474: setFlag(FLAG_VISIBLE, visible);
2475: }
2476: return this ;
2477: }
2478:
2479: /**
2480: * Gets the string representation of this component.
2481: *
2482: * @return The path to this component
2483: */
2484: public String toString() {
2485: return toString(true);
2486: }
2487:
2488: /**
2489: * @param detailed
2490: * True if a detailed string is desired
2491: * @return The string
2492: */
2493: public String toString(final boolean detailed) {
2494: if (detailed) {
2495: final Page page = findPage();
2496: if (page == null) {
2497: return new StringBuffer("[Component id = ").append(
2498: getId()).append(", page = <No Page>, path = ")
2499: .append(getPath()).append(".").append(
2500: Classes.simpleName(getClass())).append(
2501: "]").toString();
2502: } else {
2503: return new StringBuffer("[Component id = ").append(
2504: getId()).append(", page = ").append(
2505: getPage().getClass().getName()).append(
2506: ", path = ").append(getPath()).append(".")
2507: .append(Classes.simpleName(getClass())).append(
2508: ", isVisible = ").append(
2509: (isRenderAllowed() && isVisible()))
2510: .append(", isVersioned = ").append(
2511: isVersioned()).append("]").toString();
2512: }
2513: } else {
2514: return "[Component id = " + getId() + "]";
2515: }
2516: }
2517:
2518: /**
2519: * Returns a bookmarkable URL that references a given page class using a
2520: * given set of page parameters. Since the URL which is returned contains
2521: * all information necessary to instantiate and render the page, it can be
2522: * stored in a user's browser as a stable bookmark.
2523: *
2524: * @see RequestCycle#urlFor(PageMap, Class, PageParameters)
2525: *
2526: * @param pageClass
2527: * Class of page
2528: * @param parameters
2529: * Parameters to page
2530: * @return Bookmarkable URL to page
2531: */
2532: public final CharSequence urlFor(final Class pageClass,
2533: final PageParameters parameters) {
2534: return getRequestCycle().urlFor(getPage().getPageMap(),
2535: pageClass, parameters);
2536: }
2537:
2538: /**
2539: * Gets a URL for the listener interface on a behaviour (e.g.
2540: * IBehaviorListener on AjaxPagingNavigationBehavior).
2541: *
2542: * @param behaviour
2543: * The behaviour that the URL should point to
2544: * @param listener
2545: * The listener interface that the URL should call
2546: * @return The URL
2547: */
2548: public final CharSequence urlFor(final IBehavior behaviour,
2549: final RequestListenerInterface listener) {
2550: return getRequestCycle().urlFor(this , behaviour, listener);
2551: }
2552:
2553: /**
2554: * Returns a bookmarkable URL that references a given page class using a
2555: * given set of page parameters. Since the URL which is returned contains
2556: * all information necessary to instantiate and render the page, it can be
2557: * stored in a user's browser as a stable bookmark.
2558: *
2559: * @see RequestCycle#urlFor(PageMap, Class, PageParameters)
2560: *
2561: * @param pageMap
2562: * Page map to use
2563: * @param pageClass
2564: * Class of page
2565: * @param parameters
2566: * Parameters to page
2567: *
2568: *
2569: * @return Bookmarkable URL to page
2570: */
2571: public final CharSequence urlFor(final IPageMap pageMap,
2572: final Class pageClass, final PageParameters parameters) {
2573: return getRequestCycle().urlFor(pageMap, pageClass, parameters);
2574: }
2575:
2576: /**
2577: * Returns a URL that references the given request target.
2578: *
2579: * @see RequestCycle#urlFor(IRequestTarget)
2580: *
2581: * @param requestTarget
2582: * the request target to reference
2583: *
2584: * @return a URL that references the given request target
2585: */
2586: public final CharSequence urlFor(final IRequestTarget requestTarget) {
2587: return getRequestCycle().urlFor(requestTarget);
2588: }
2589:
2590: /**
2591: * Gets a URL for the listener interface (e.g. ILinkListener).
2592: *
2593: * @param listener
2594: * The listener interface that the URL should call
2595: * @return The URL
2596: */
2597: public final CharSequence urlFor(
2598: final RequestListenerInterface listener) {
2599: return getRequestCycle().urlFor(this , listener);
2600: }
2601:
2602: /**
2603: * Returns a URL that references a shared resource through the provided
2604: * resource reference.
2605: *
2606: * @see RequestCycle#urlFor(ResourceReference)
2607: *
2608: * @param resourceReference
2609: * The resource reference
2610: * @return The url for the shared resource
2611: */
2612: public final CharSequence urlFor(
2613: final ResourceReference resourceReference) {
2614: return getRequestCycle().urlFor(resourceReference);
2615: }
2616:
2617: /**
2618: * Traverses all parent components of the given class in this container,
2619: * calling the visitor's visit method at each one.
2620: *
2621: * @param c
2622: * Class
2623: * @param visitor
2624: * The visitor to call at each parent of the given type
2625: * @return First non-null value returned by visitor callback
2626: */
2627: public final Object visitParents(final Class c,
2628: final IVisitor visitor) {
2629: // Start here
2630: Component current = this ;
2631:
2632: // Walk up containment hierarchy
2633: while (current != null) {
2634: // Is current an instance of this class?
2635: if (c.isInstance(current)) {
2636: final Object object = visitor.component(current);
2637: if (object != IVisitor.CONTINUE_TRAVERSAL) {
2638: return object;
2639: }
2640: }
2641:
2642: // Check parent
2643: current = current.getParent();
2644: }
2645: return null;
2646: }
2647:
2648: /**
2649: * Registers a warning feedback message for this component.
2650: *
2651: * @param message
2652: * The feedback message
2653: */
2654: public final void warn(final String message) {
2655: Session.get().getFeedbackMessages().warn(this , message);
2656: Session.get().dirty();
2657: }
2658:
2659: /**
2660: * {@link IBehavior#beforeRender(Component)} Notify all behaviors that are
2661: * assigned to this component that the component is about to be rendered.
2662: */
2663: private void notifyBehaviorsComponentBeforeRender() {
2664: if (behaviors != null) {
2665: for (Iterator i = behaviors.iterator(); i.hasNext();) {
2666: IBehavior behavior = (IBehavior) i.next();
2667: if (isBehaviorAccepted(behavior)) {
2668: behavior.beforeRender(this );
2669: }
2670: }
2671: }
2672: }
2673:
2674: /**
2675: * {@link IBehavior#afterRender(Component)} Notify all behaviors that are
2676: * assigned to this component that the component has rendered.
2677: */
2678: private void notifyBehaviorsComponentRendered() {
2679: // notify the behaviors that component has been rendered
2680: if (behaviors != null) {
2681: for (Iterator i = behaviors.iterator(); i.hasNext();) {
2682: IBehavior behavior = (IBehavior) i.next();
2683: if (isBehaviorAccepted(behavior)) {
2684: behavior.afterRender(this );
2685: }
2686: }
2687: }
2688: }
2689:
2690: /**
2691: * Adds state change to page.
2692: *
2693: * @param change
2694: * The change
2695: */
2696: protected final void addStateChange(final Change change) {
2697: checkHierarchyChange(this );
2698: final Page page = findPage();
2699: if (page != null) {
2700: page.componentStateChanging(this , change);
2701: }
2702: }
2703:
2704: /**
2705: * Checks whether the given type has the expected name.
2706: *
2707: * @param tag
2708: * The tag to check
2709: * @param name
2710: * The expected tag name
2711: * @throws MarkupException
2712: * Thrown if the tag is not of the right name
2713: */
2714: protected final void checkComponentTag(final ComponentTag tag,
2715: final String name) {
2716: if (!tag.getName().equalsIgnoreCase(name)) {
2717: findMarkupStream().throwMarkupException(
2718: "Component " + getId()
2719: + " must be applied to a tag of type '"
2720: + name + "', not "
2721: + tag.toUserDebugString());
2722: }
2723: }
2724:
2725: /**
2726: * Checks that a given tag has a required attribute value.
2727: *
2728: * @param tag
2729: * The tag
2730: * @param key
2731: * The attribute key
2732: * @param value
2733: * The required value for the attribute key
2734: * @throws MarkupException
2735: * Thrown if the tag does not have the required attribute value
2736: */
2737: protected final void checkComponentTagAttribute(
2738: final ComponentTag tag, final String key, final String value) {
2739: if (key != null) {
2740: final String tagAttributeValue = tag.getAttributes()
2741: .getString(key);
2742: if (tagAttributeValue == null
2743: || !value.equalsIgnoreCase(tagAttributeValue)) {
2744: findMarkupStream().throwMarkupException(
2745: "Component " + getId()
2746: + " must be applied to a tag with '"
2747: + key + "' attribute matching '"
2748: + value + "', not '"
2749: + tagAttributeValue + "'");
2750: }
2751: }
2752: }
2753:
2754: /**
2755: * Checks whether the hierarchy may be changed at all, and throws an
2756: * exception if this is not the case.
2757: *
2758: * @param component
2759: * the component which is about to be added or removed
2760: */
2761: protected void checkHierarchyChange(final Component component) {
2762: // Throw exception if modification is attempted during rendering
2763: if (!component.isAuto() && getFlag(FLAG_RENDERING)) {
2764: throw new WicketRuntimeException(
2765: "Cannot modify component hierarchy during render phase");
2766: }
2767:
2768: // Throw exception if modification is attempted during attach
2769: if (getFlag(FLAG_ATTACHING)) {
2770: throw new WicketRuntimeException(
2771: "Cannot modify component hierarchy during attach phase");
2772: }
2773: }
2774:
2775: /**
2776: * Detaches the model for this component if it is detachable.
2777: */
2778: protected void detachModel() {
2779: if (model != null) {
2780: model.detach();
2781: }
2782: // also detach the wrapped model of a component assignet wrap (not
2783: // inherited)
2784: if (model instanceof IWrapModel
2785: && !getFlag(FLAG_INHERITABLE_MODEL)) {
2786: ((IWrapModel) model).getWrappedModel().detach();
2787: }
2788: }
2789:
2790: /**
2791: * Prefixes an exception message with useful information about this.
2792: * component.
2793: *
2794: * @param message
2795: * The message
2796: * @return The modified message
2797: */
2798: protected final String exceptionMessage(final String message) {
2799: return message + ":\n" + toString();
2800: }
2801:
2802: /**
2803: * Finds the markup stream for this component.
2804: *
2805: * @return The markup stream for this component. Since a Component cannot
2806: * have a markup stream, we ask this component's parent to search
2807: * for it.
2808: */
2809: protected MarkupStream findMarkupStream() {
2810: if (parent == null) {
2811: throw new IllegalStateException(
2812: "Cannot find markupstream for " + this
2813: + " as there is no parent");
2814: }
2815:
2816: return parent.findMarkupStream();
2817: }
2818:
2819: /**
2820: * If this Component is a Page, returns self. Otherwise, searches for the
2821: * nearest Page parent in the component hierarchy. If no Page parent can be
2822: * found, null is returned.
2823: *
2824: * @return The Page or null if none can be found
2825: */
2826: protected final Page findPage() {
2827: // Search for page
2828: return (Page) (this instanceof Page ? this
2829: : findParent(Page.class));
2830: }
2831:
2832: /**
2833: * Gets the subset of the currently coupled {@link IBehavior}s that are of
2834: * the provided type as a unmodifiable list or null if there are no
2835: * behaviors attached. Returns an empty list rather than null if there are
2836: * no behaviors coupled to this component.
2837: *
2838: * @param type
2839: * The type or null for all
2840: *
2841: * @return The subset of the currently coupled behaviors that are of the
2842: * provided type as a unmodifiable list or null
2843: */
2844: protected List/* <IBehavior> */getBehaviors(Class type) {
2845: if (behaviors == null) {
2846: return Collections.EMPTY_LIST;
2847: }
2848:
2849: List subset = new ArrayList(behaviors.size()); // avoid growing
2850: for (Iterator i = behaviors.iterator(); i.hasNext();) {
2851: Object behavior = i.next();
2852: if (type == null
2853: || type.isAssignableFrom(behavior.getClass())) {
2854: subset.add(behavior);
2855: }
2856: }
2857: return Collections.unmodifiableList(subset);
2858: }
2859:
2860: /**
2861: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT!
2862: *
2863: * @param flag
2864: * The flag to test
2865: * @return True if the flag is set
2866: */
2867: protected final boolean getFlag(final int flag) {
2868: return (flags & flag) != 0;
2869: }
2870:
2871: /**
2872: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT!
2873: *
2874: * @param flag
2875: * The flag to test
2876: * @return True if the flag is set
2877: */
2878: protected final boolean getFlag(final short flag) {
2879: return getFlag((int) flag);
2880: }
2881:
2882: /**
2883: * Finds the innermost IModel object for an IModel that might contain nested
2884: * IModel(s).
2885: *
2886: * @param model
2887: * The model
2888: * @return The innermost (most nested) model
2889: */
2890: protected final IModel getInnermostModel(final IModel model) {
2891: IModel nested = model;
2892: while (nested != null && nested instanceof IWrapModel) {
2893: final IModel next = ((IWrapModel) nested).getWrappedModel();
2894: if (nested == next) {
2895: throw new WicketRuntimeException("Model for " + nested
2896: + " is self-referential");
2897: }
2898: nested = next;
2899: }
2900: return nested;
2901: }
2902:
2903: /**
2904: * Gets the value defaultModelComparator. Implementations of this interface
2905: * can be used in the Component.getComparator() for testing the current
2906: * value of the components model data with the new value that is given.
2907: *
2908: * @return the value defaultModelComparator
2909: */
2910: protected IModelComparator getModelComparator() {
2911: return defaultModelComparator;
2912: }
2913:
2914: /**
2915: * Returns whether the component can be stateless. Being able to be
2916: * stateless doesn't necessary mean, that the component should be stateless.
2917: * Whether the component should be stateless depends on
2918: *
2919: * @return whether the component can be stateless
2920: */
2921: protected boolean getStatelessHint() {
2922: return true;
2923: }
2924:
2925: /**
2926: * Called when a null model is about to be retrieved in order to allow a
2927: * subclass to provide an initial model. This gives FormComponent, for
2928: * example, an opportunity to instantiate a model on the fly using the
2929: * containing Form's model.
2930: *
2931: * @return The model
2932: */
2933: protected IModel initModel() {
2934: // Search parents for CompoundPropertyModel
2935: for (Component current = getParent(); current != null; current = current
2936: .getParent()) {
2937: // Get model
2938: // Dont call the getModel() that could initialize many inbetween
2939: // completely useless models.
2940: // IModel model = current.getModel();
2941: IModel model = current.model;
2942:
2943: if (model instanceof IWrapModel) {
2944: model = ((IWrapModel) model).getWrappedModel();
2945: }
2946:
2947: if (model instanceof IComponentInheritedModel) {
2948: // we turn off versioning as we share the model with another
2949: // component that is the owner of the model (that component
2950: // has to decide whether to version or not
2951: // TODO can we really do this?? Model shouldn't versioned but
2952: // all other things?? (add/remove)
2953: setVersioned(false);
2954:
2955: // return the shared inherited
2956: model = ((IComponentInheritedModel) model)
2957: .wrapOnInheritance(this );
2958: setFlag(FLAG_INHERITABLE_MODEL, true);
2959: return model;
2960: }
2961: }
2962:
2963: // No model for this component!
2964: return null;
2965: }
2966:
2967: /**
2968: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL OR
2969: * OVERRIDE.
2970: *
2971: * Called when a request begins.
2972: *
2973: * @Deprecated use {@link #onBeforeRender()} instead
2974: */
2975: protected final void internalOnAttach() {
2976: }
2977:
2978: /**
2979: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL OR
2980: * OVERRIDE.
2981: *
2982: * Called when a request ends.
2983: *
2984: * @Deprecated use {@link #onBeforeRender()} instead
2985: *
2986: */
2987: protected final void internalOnDetach() {
2988: }
2989:
2990: /**
2991: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL OR
2992: * OVERRIDE.
2993: *
2994: * Called anytime a model is changed via setModel or setModelObject.
2995: */
2996: protected void internalOnModelChanged() {
2997: }
2998:
2999: /**
3000: * @return true if this component is attached
3001: */
3002: protected final boolean isAttached() {
3003: return getFlag(FLAG_ATTACHED);
3004: }
3005:
3006: /**
3007: * Components are allowed to reject behavior modifiers.
3008: *
3009: * @param behavior
3010: * @return False, if the component should not apply this behavior
3011: */
3012: protected boolean isBehaviorAccepted(final IBehavior behavior) {
3013: // Ignore AttributeModifiers when FLAG_IGNORE_ATTRIBUTE_MODIFIER is set
3014: if ((behavior instanceof AttributeModifier)
3015: && (getFlag(FLAG_IGNORE_ATTRIBUTE_MODIFIER) != false)) {
3016: return false;
3017: }
3018:
3019: return behavior.isEnabled(this );
3020: }
3021:
3022: /**
3023: * If true, all attribute modifiers will be ignored
3024: *
3025: * @return True, if attribute modifiers are to be ignored
3026: */
3027: protected final boolean isIgnoreAttributeModifier() {
3028: return this .getFlag(FLAG_IGNORE_ATTRIBUTE_MODIFIER);
3029: }
3030:
3031: /**
3032: * @return Component's markup stream
3033: */
3034: protected MarkupStream locateMarkupStream() {
3035: return new MarkupFragmentFinder().find(this );
3036: }
3037:
3038: /**
3039: * Called just after a component is rendered.
3040: */
3041: protected void onAfterRender() {
3042: setFlag(FLAG_AFTER_RENDERING, false);
3043: }
3044:
3045: /**
3046: * Called to allow a component to attach resources for use.
3047: *
3048: * Overrides of this method MUST call the super implementation, the most
3049: * logical place to do this is the first line of the override method.
3050: *
3051: */
3052: protected void onAttach() {
3053: setFlag(FLAG_ATTACH_SUPER_CALL_VERIFIED, true);
3054: }
3055:
3056: /**
3057: * Called just before a component is rendered. If you override this, you
3058: * *must* call super.onBeforeRender() within your implementation.
3059: */
3060: protected void onBeforeRender() {
3061: setFlag(FLAG_PREPARED_FOR_RENDER, true);
3062: onBeforeRenderChildren();
3063: setFlag(FLAG_BEFORE_RENDERING_SUPER_CALL_VERIFIED, true);
3064: }
3065:
3066: /**
3067: * @deprecated use onAttach() instead
3068: */
3069: // TODO remove after the deprecation release
3070: protected final void onBeginRequest() {
3071: throw new UnsupportedOperationException();
3072: }
3073:
3074: /**
3075: * Processes the component tag.
3076: *
3077: * @param tag
3078: * Tag to modify
3079: */
3080: protected void onComponentTag(final ComponentTag tag) {
3081: // We can't try to get the ID from markup. This could be different than
3082: // id returned from getMarkupId() prior first rendering the component
3083: // (due to transparent resolvers and borders which break the 1:1
3084: // component <-> markup relation)
3085: if (getFlag(FLAG_OUTPUT_MARKUP_ID)) {
3086: tag.put(MARKUP_ID_ATTR_NAME, getMarkupId());
3087: }
3088: }
3089:
3090: /**
3091: * Processes the body.
3092: *
3093: * @param markupStream
3094: * The markup stream
3095: * @param openTag
3096: * The open tag for the body
3097: */
3098: protected void onComponentTagBody(final MarkupStream markupStream,
3099: final ComponentTag openTag) {
3100: }
3101:
3102: /**
3103: * Called to allow a component to detach resources after use.
3104: *
3105: * Overrides of this method MUST call the super implementation, the most
3106: * logical place to do this is the last line of the override method.
3107: *
3108: *
3109: */
3110: protected void onDetach() {
3111: if (behaviors != null) {
3112: behaviors.trimToSize();
3113: }
3114: setFlag(FLAG_DETACHING, false);
3115:
3116: }
3117:
3118: /**
3119: * @deprecated use onDetach() instead
3120: */
3121: // TODO remove after the deprecation release
3122: protected final void onEndRequest() {
3123: throw new UnsupportedOperationException();
3124: }
3125:
3126: /**
3127: * Called anytime a model is changed after the change has occurred
3128: */
3129: protected void onModelChanged() {
3130: }
3131:
3132: /**
3133: * Called anytime a model is changed, but before the change actually occurs
3134: */
3135: protected void onModelChanging() {
3136: }
3137:
3138: /**
3139: * Implementation that renders this component.
3140: *
3141: * @since Wicket 1.2
3142: * @param markupStream
3143: */
3144: protected abstract void onRender(final MarkupStream markupStream);
3145:
3146: /**
3147: * Writes a simple tag out to the response stream. Any components that might
3148: * be referenced by the tag are ignored. Also undertakes any tag attribute
3149: * modifications if they have been added to the component.
3150: *
3151: * @param tag
3152: * The tag to write
3153: */
3154: protected final void renderComponentTag(ComponentTag tag) {
3155: final boolean stripWicketTags = Application.get()
3156: .getMarkupSettings().getStripWicketTags();
3157: if (!(tag instanceof WicketTag) || !stripWicketTags) {
3158: // Apply behavior modifiers
3159: if ((behaviors != null) && !behaviors.isEmpty()
3160: && !tag.isClose()
3161: && (isIgnoreAttributeModifier() == false)) {
3162: tag = tag.mutable();
3163:
3164: for (Iterator i = behaviors.iterator(); i.hasNext();) {
3165: IBehavior behavior = (IBehavior) i.next();
3166:
3167: // Components may reject some behavior components
3168: if (isBehaviorAccepted(behavior)) {
3169: behavior.onComponentTag(this , tag);
3170: }
3171: }
3172: }
3173:
3174: // apply behaviors that are attached to the component tag.
3175: if (tag.hasBehaviors()) {
3176: Iterator behaviors = tag.getBehaviors();
3177: while (behaviors.hasNext()) {
3178: final IBehavior behavior = (IBehavior) behaviors
3179: .next();
3180: behavior.onComponentTag(this , tag);
3181: }
3182: }
3183:
3184: // Write the tag
3185: tag.writeOutput(getResponse(), stripWicketTags,
3186: findMarkupStream().getWicketNamespace());
3187: }
3188: }
3189:
3190: /**
3191: * Replaces the body with the given one.
3192: *
3193: * @param markupStream
3194: * The markup stream to replace the tag body in
3195: * @param tag
3196: * The tag
3197: * @param body
3198: * The new markup
3199: */
3200: protected final void replaceComponentTagBody(
3201: final MarkupStream markupStream, final ComponentTag tag,
3202: final CharSequence body) {
3203: // The tag might have been changed from open-close to open. Hence
3204: // we'll need what was in the markup itself
3205: ComponentTag markupOpenTag = null;
3206:
3207: // If tag has a body
3208: if (tag.isOpen()) {
3209: // Get what tag was in the markup; not what the user it might
3210: // have changed it to.
3211: markupStream
3212: .setCurrentIndex(markupStream.getCurrentIndex() - 1);
3213: markupOpenTag = markupStream.getTag();
3214: markupStream.next();
3215:
3216: // If it was an open tag in the markup as well, than ...
3217: if (markupOpenTag.isOpen()) {
3218: // skip any raw markup in the body
3219: markupStream.skipRawMarkup();
3220: }
3221: }
3222:
3223: if (body != null) {
3224: // Write the new body
3225: getResponse().write(body);
3226: }
3227:
3228: // If we had an open tag (and not an openclose tag) and we found a
3229: // close tag, we're good
3230: if (tag.isOpen()) {
3231: // If it was an open tag in the markup, than there must be
3232: // a close tag as well.
3233: if ((markupOpenTag != null) && markupOpenTag.isOpen()
3234: && !markupStream.atCloseTag()) {
3235: // There must be a component in this discarded body
3236: markupStream
3237: .throwMarkupException("Expected close tag for '"
3238: + markupOpenTag
3239: + "' Possible attempt to embed component(s) '"
3240: + markupStream.get()
3241: + "' in the body of this component which discards its body");
3242: }
3243: }
3244: }
3245:
3246: /**
3247: * @param auto
3248: * True to put component into auto-add mode
3249: */
3250: protected final void setAuto(final boolean auto) {
3251: setFlag(FLAG_AUTO, auto);
3252: }
3253:
3254: /**
3255: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT!
3256: *
3257: * @param flag
3258: * The flag to set
3259: * @param set
3260: * True to turn the flag on, false to turn it off
3261: */
3262: protected final void setFlag(final int flag, final boolean set) {
3263: if (set) {
3264: flags |= flag;
3265: } else {
3266: flags &= ~flag;
3267: }
3268: }
3269:
3270: /**
3271: * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT!
3272: *
3273: * @param flag
3274: * The flag to set
3275: * @param set
3276: * True to turn the flag on, false to turn it off
3277: */
3278: protected final void setFlag(final short flag, final boolean set) {
3279: setFlag((int) flag, set);
3280: }
3281:
3282: /**
3283: * If true, all attribute modifiers will be ignored
3284: *
3285: * @param ignore
3286: * If true, all attribute modifiers will be ignored
3287: * @return This
3288: */
3289: protected final Component setIgnoreAttributeModifier(
3290: final boolean ignore) {
3291: this .setFlag(FLAG_IGNORE_ATTRIBUTE_MODIFIER, ignore);
3292: return this ;
3293: }
3294:
3295: /**
3296: * The markup stream will be assigned to the component at the beginning of
3297: * the component render phase. It is temporary working variable only.
3298: *
3299: * @see #findMarkupStream()
3300: * @see MarkupContainer#getMarkupStream()
3301: *
3302: * @param markupStream
3303: * The current markup stream which should be applied by the
3304: * component to render itself
3305: */
3306: protected void setMarkupStream(final MarkupStream markupStream) {
3307: }
3308:
3309: /**
3310: * @param model
3311: * The model to wrap if need be
3312: * @return The wrapped model
3313: */
3314: protected final IModel wrap(final IModel model) {
3315: if (model instanceof IComponentAssignedModel) {
3316: return ((IComponentAssignedModel) model)
3317: .wrapOnAssignment(this );
3318: } else {
3319: return model;
3320: }
3321: }
3322:
3323: /**
3324: * Detaches any child components
3325: *
3326: * @see {@link #attachChildren()}
3327: */
3328: void detachChildren() {
3329: }
3330:
3331: /**
3332: * Gets the component at the given path.
3333: *
3334: * @param path
3335: * Path to component
3336: * @return The component at the path
3337: */
3338: Component get(final String path) {
3339: // Path to this component is an empty path
3340: if (path.equals("")) {
3341: return this ;
3342: }
3343: throw new IllegalArgumentException(
3344: exceptionMessage("Component is not a container and so does "
3345: + "not contain the path " + path));
3346: }
3347:
3348: /**
3349: * Checks whether or not this component has a markup id value generated,
3350: * whether it is automatic or user defined
3351: *
3352: * @return true if this component has a markup id value generated
3353: */
3354: final boolean hasMarkupIdMetaData() {
3355: return getMetaData(MARKUP_ID_KEY) != null;
3356: }
3357:
3358: /**
3359: * Attaches any child components
3360: *
3361: * This method is here only for {@link MarkupContainer}. It is broken out
3362: * of {@link #onBeforeRender()} so we can guarantee that it executes as the
3363: * last in onAttach() chain no matter where user places the
3364: * <code>super.onAttach()</code> call
3365: */
3366: void internalAttach2() {
3367: if (!getFlag(FLAG_ATTACHED)) {
3368: setFlag(FLAG_ATTACHING, true);
3369: setFlag(FLAG_ATTACH_SUPER_CALL_VERIFIED, false);
3370: onAttach();
3371: if (!getFlag(FLAG_ATTACH_SUPER_CALL_VERIFIED)) {
3372: throw new IllegalStateException(
3373: "Component "
3374: + this
3375: + " of type "
3376: + getClass().getName()
3377: + " has not been properly attached. "
3378: + "Something in its class hierarchy has failed to call super.onAttach() in an override of onAttach() method");
3379: }
3380: setFlag(FLAG_ATTACHING, false);
3381: setFlag(FLAG_ATTACHED, true);
3382: }
3383: }
3384:
3385: void internalMarkRendering() {
3386: setFlag(FLAG_PREPARED_FOR_RENDER, false);
3387: setFlag(FLAG_RENDERING, true);
3388: }
3389:
3390: /**
3391: * @return True if this component or any of its parents is in auto-add mode
3392: */
3393: final boolean isAuto() {
3394: // Search up hierarchy for FLAG_AUTO
3395: for (Component current = this ; current != null; current = current
3396: .getParent()) {
3397: if (current.getFlag(FLAG_AUTO)) {
3398: return true;
3399: }
3400: }
3401: return false;
3402: }
3403:
3404: boolean isPreparedForRender() {
3405: return getFlag(FLAG_PREPARED_FOR_RENDER);
3406: }
3407:
3408: void onAfterRenderChildren() {
3409: }
3410:
3411: /**
3412: * This method is here for {@link MarkupContainer}. It is broken out of
3413: * {@link #onBeforeRender()} so we can guarantee that it executes as the
3414: * last in onBeforeRender() chain no matter where user places the
3415: * <code>super.onBeforeRender()</code> call.
3416: */
3417: void onBeforeRenderChildren() {
3418: }
3419:
3420: /**
3421: * Renders the close tag at the current position in the markup stream.
3422: *
3423: * @param markupStream
3424: * the markup stream
3425: * @param openTag
3426: * the tag to render
3427: * @param renderTagOnly
3428: * if true, the tag will not be written to the output
3429: */
3430: final void renderClosingComponentTag(
3431: final MarkupStream markupStream,
3432: final ComponentTag openTag, final boolean renderTagOnly) {
3433: // Tag should be open tag and not openclose tag
3434: if (openTag.isOpen()) {
3435: // If we found a close tag and it closes the open tag, we're good
3436: if (markupStream.atCloseTag()
3437: && markupStream.getTag().closes(openTag)) {
3438: // Get the close tag from the stream
3439: ComponentTag closeTag = markupStream.getTag();
3440:
3441: // If the open tag had its id changed
3442: if (openTag.getNameChanged()) {
3443: // change the id of the close tag
3444: closeTag = closeTag.mutable();
3445: closeTag.setName(openTag.getName());
3446: }
3447:
3448: // Render the close tag
3449: if (renderTagOnly == false) {
3450: renderComponentTag(closeTag);
3451: }
3452: markupStream.next();
3453: } else {
3454: if (openTag.requiresCloseTag()) {
3455: // Missing close tag
3456: markupStream
3457: .throwMarkupException("Expected close tag for "
3458: + openTag);
3459: }
3460: }
3461: }
3462: }
3463:
3464: /**
3465: * Sets the id of this component. This method is private because the only
3466: * time a component's id can be set is in its constructor.
3467: *
3468: * @param id
3469: * The non-null id of this component
3470: */
3471: final void setId(final String id) {
3472: if (id == null && !(this instanceof Page)) {
3473: throw new WicketRuntimeException(
3474: "Null component id is not allowed.");
3475: }
3476: this .id = id;
3477: }
3478:
3479: /**
3480: * Sets the parent of a component.
3481: *
3482: * @param parent
3483: * The parent container
3484: */
3485: final void setParent(final MarkupContainer parent) {
3486: if (this .parent != null && log.isDebugEnabled()) {
3487: log.debug("Replacing parent " + this .parent + " with "
3488: + parent);
3489: }
3490: this .parent = parent;
3491: }
3492:
3493: /**
3494: * Sets the render allowed flag.
3495: *
3496: * @param renderAllowed
3497: */
3498: final void setRenderAllowed(boolean renderAllowed) {
3499: setFlag(FLAG_IS_RENDER_ALLOWED, renderAllowed);
3500: }
3501: }
|