blob: 9d44c8857f717778419157933d7a1ce36d8e80b6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.wicket;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import org.apache.wicket.ajax.IAjaxRegionMarkupIdProvider;
import org.apache.wicket.application.IComponentInstantiationListener;
import org.apache.wicket.authorization.Action;
import org.apache.wicket.authorization.AuthorizationException;
import org.apache.wicket.authorization.IAuthorizationStrategy;
import org.apache.wicket.authorization.UnauthorizedActionException;
import org.apache.wicket.authorization.strategies.page.SimplePageAuthorizationStrategy;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.core.request.handler.BookmarkableListenerRequestHandler;
import org.apache.wicket.core.request.handler.ListenerRequestHandler;
import org.apache.wicket.core.request.handler.PageAndComponentProvider;
import org.apache.wicket.core.util.lang.WicketObjects;
import org.apache.wicket.core.util.string.ComponentStrings;
import org.apache.wicket.event.Broadcast;
import org.apache.wicket.event.IEvent;
import org.apache.wicket.event.IEventSink;
import org.apache.wicket.event.IEventSource;
import org.apache.wicket.feedback.FeedbackDelay;
import org.apache.wicket.feedback.FeedbackMessage;
import org.apache.wicket.feedback.FeedbackMessages;
import org.apache.wicket.feedback.IFeedback;
import org.apache.wicket.feedback.IFeedbackContributor;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.IMarkupFragment;
import org.apache.wicket.markup.Markup;
import org.apache.wicket.markup.MarkupCache;
import org.apache.wicket.markup.MarkupElement;
import org.apache.wicket.markup.MarkupException;
import org.apache.wicket.markup.MarkupNotFoundException;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.WicketTag;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.StringHeaderItem;
import org.apache.wicket.markup.html.IHeaderContributor;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.internal.HtmlHeaderContainer;
import org.apache.wicket.markup.html.panel.DefaultMarkupSourcingStrategy;
import org.apache.wicket.markup.html.panel.IMarkupSourcingStrategy;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IComponentAssignedModel;
import org.apache.wicket.model.IComponentInheritedModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.IModelComparator;
import org.apache.wicket.model.IWrapModel;
import org.apache.wicket.protocol.http.WicketFilter;
import org.apache.wicket.request.IRequestHandler;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.Response;
import org.apache.wicket.request.component.IRequestableComponent;
import org.apache.wicket.request.component.IRequestablePage;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.request.resource.ResourceReference;
import org.apache.wicket.response.StringResponse;
import org.apache.wicket.settings.DebugSettings;
import org.apache.wicket.settings.ExceptionSettings;
import org.apache.wicket.util.IHierarchical;
import org.apache.wicket.util.convert.IConverter;
import org.apache.wicket.util.io.IClusterable;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.lang.Classes;
import org.apache.wicket.util.string.PrependingStringBuffer;
import org.apache.wicket.util.string.Strings;
import org.apache.wicket.util.value.ValueMap;
import org.apache.wicket.util.visit.IVisitFilter;
import org.apache.wicket.util.visit.IVisitor;
import org.apache.wicket.util.visit.Visit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Component serves as the highest level abstract base class for all components.
*
* <ul>
* <li><b>Identity </b>- All Components must have a non-null id which is retrieved by calling
* getId(). The id must be unique within the {@link MarkupContainer} that holds the Component, but
* does not have to be globally unique or unique within a Page's component hierarchy.</li>
* <li><b>Hierarchy </b>- A component has a parent which can be retrieved with {@link #getParent()}.
* If a component is an instance of MarkupContainer, it may have children. In this way it has a
* place in the hierarchy of components contained on a given page.
* <p>
* The path from the Page at the root of the component hierarchy to a given Component is simply the
* concatenation with colon separators of each id along the way. For example, the path "a:b:c" would
* refer to the component named "c" inside the MarkupContainer named "b" inside the container named
* "a". The path to a component can be retrieved by calling {@link #getPath()}. To get a Component
* path relative to the page that contains it, you can call {@link #getPageRelativePath()}.</li>
* <li><b>LifeCycle </b>- Components participate in the following lifecycle phases:
* <ul>
* <li><b>Construction </b>- A Component is constructed with the Java language new operator.
* Children may be added during construction if the Component is a MarkupContainer.
* {@link IComponentInstantiationListener}s are notified of component instantiation.
* <p>
* {@link #onInitialize()} is called on the component as soon as the component is part of a page's
* component tree. At this state the component is able to access its markup.</li>
* <li><b>Request Handling </b>- An incoming request is processed by a protocol request handler such
* as {@link WicketFilter}. An associated Application object creates {@link Session},
* {@link Request} and {@link Response} objects for use by a given Component in updating its model
* and rendering a response. These objects are stored inside a container called {@link RequestCycle}
* which is accessible via {@link Component#getRequestCycle()}. The convenience methods
* {@link Component#getRequest()}, {@link Component#getResponse()} and
* {@link Component#getSession()} provide easy access to the contents of this container.</li>
* <li><b>Listener Invocation </b>- If the request references an {@link IRequestListener} on an
* existing Component (or one of its {@link Behavior}s, see below), that listener is notified,
* allowing arbitrary user code to handle events such as link clicks or form submits. Although
* arbitrary listeners are supported in Wicket, the need to implement a new class of listener is
* unlikely for a web application and even the need to implement a listener interface directly is
* highly discouraged. Instead, calls to listeners are routed through logic specific to the event,
* resulting in calls to user code through other overridable methods. See {@link Form} for an
* example of a component which listens for events via {@link IRequestListener}.</li>
* <li><b>Rendering </b>- Before a page or part of a page (in case of Ajax updates) is rendered, all
* containing components are able to prepare for rendering via two hook methods:
* {@link #onConfigure()} (regardless whether they are visible or not) and {@link #onBeforeRender()}
* (if visible only) . <br>
* A markup response is generated by the Component via {@link Component#render()}, which calls
* subclass implementation code contained in {@link Component#onRender()}. Once this phase begins, a
* Component becomes immutable. Attempts to alter the Component will result in a
* WicketRuntimeException.</li>
* <li><b>Detachment </b>- Each request cycle finishes by detaching all touched components.
* Subclasses should clean up their state by overriding {@link #onDetach()} or more specifically
* {@link #detachModels()} if they keep references to models beside the default model.</li>
* </ul>
* </li>
* <li><b>Visibility </b>- If a component is not visible (see {@link #setVisible(boolean)}) it will
* not render a response (nor will their children).</li>
* <li><b>Enabling </b>- Component subclasses take into account their enabled state (see
* {@link #setEnabled(boolean)} when rendering, and in case of a {@link FormComponent} will not not
* update its model while the request is handled.</li>
* <li><b>Models </b>- The primary responsibility of a component is to use its model (an object that
* implements {@link IModel}) to render a response in an appropriate markup language, such as HTML.
* In addition, {@link FormComponent}s know how to update their models based on request information,
* see {@link FormComponent#updateModel()}. Since the IModel interface is a wrapper around another
* object, a convenience method {@link Component#getDefaultModelObject()} is provided to retrieve
* the object from its IModel wrapper. A further convenience method,
* {@link Component#getDefaultModelObjectAsString()}, is provided for the very common operation of
* converting the wrapped object to a String. <br>
* The component's model can be passed in the constructor or set via
* {@link Component#setDefaultModel(IModel)}. In neither case a model can be created on demand with
* {@link #initModel()}.<br>
* Note that a component can have more models besides its default model.</li>
* <li><b>Behaviors </b>- You can add multiple {@link Behavior}s to any component if you need to
* dynamically alter the behavior of components, e.g. manipulate attributes of the markup tag to
* which a Component is attached. Behaviors take part in the component's lifecycle through various
* callback methods.</li>
* <li><b>Locale </b>- The Locale for a Component is available through {@link #getLocale()}, which
* delegates to its parent's locale, finally consulting the {@link Session}'s locale.</li>
* <li><b>Style </b>- The Session's style ("skin") is available through
* {@link org.apache.wicket.Component#getStyle()}. Styles are intended to give a particular look to
* all components or resources in a session that is independent of its Locale. For example, a style
* might be a set of resources, including images and markup files, which gives the design look of
* "ocean" to the user. If the Session's style is set to "ocean" and these resources are given names
* suffixed with "_ocean", Wicket's resource management logic will prefer these resources to other
* resources, such as default resources, which are not as good of a match.</li>
* <li><b>Variation </b>- Whereas styles are Session (user) specific, variations are component
* specific. E.g. if the Style is "ocean" and {@link #getVariation()} returnss "NorthSea", than the
* resources are given the names suffixed with "_ocean_NorthSea".</li>
* <li><b>String Resources </b>- Components can have associated String resources via the
* Application's Localizer, which is available through the method {@link Component#getLocalizer()}.
* The convenience methods {@link Component#getString(String key)} and
* {@link Component#getString(String key, IModel model)} wrap the identical methods on the
* Application Localizer for easy access in Components.</li>
* <li><b>Feedback Messages </b>- The {@link Component#debug(Serializable)},
* {@link Component#info(Serializable)}, {@link Component#warn(Serializable)},
* {@link Component#error(java.io.Serializable)} and {@link Component#fatal(Serializable)} methods
* associate feedback messages with a Component. It is generally not necessary to use these methods
* directly since Wicket validators automatically register feedback messages on Components. Feedback
* message for a given Component can be retrieved with {@link Component#getFeedbackMessages}.</li>
* <li><b>Versioning </b>- Pages are the unit of versioning in Wicket, but fine-grained control of
* which Components should participate in versioning is possible via the
* {@link Component#setVersioned(boolean)} method. The versioning participation of a given Component
* can be retrieved with {@link Component#isVersioned()}.</li>
* <li><b>Page </b>- The Page containing any given Component can be retrieved by calling
* {@link Component#getPage()}. If the Component is not attached to a Page, an IllegalStateException
* will be thrown. An equivalent method, {@link Component#findPage()} is available for special
* circumstances where it might be desirable to get a null reference back instead.</li>
* <li><b>Application </b>- The {@link #getApplication()} method provides convenient access to the
* {@link Application} for a Component.</li>
* <li><b>AJAX support</b>- Components can be re-rendered after the whole Page has been rendered at
* least once by calling doRender().</li>
* <li><b>Security </b>- All components are subject to an {@link IAuthorizationStrategy} which
* controls instantiation, visibility and enabling. See {@link SimplePageAuthorizationStrategy} for
* a simple implementation.</li>
*
* @author Jonathan Locke
* @author Chris Turner
* @author Eelco Hillenius
* @author Johan Compagner
* @author Juergen Donnerstag
* @author Igor Vaynberg (ivaynberg)
*/
public abstract class Component
implements
IClusterable,
IConverterLocator,
IRequestableComponent,
IHeaderContributor,
IHierarchical<Component>,
IEventSink,
IEventSource,
IMetadataContext<Serializable, Component>,
IFeedbackContributor
{
/** Log. */
private static final Logger log = LoggerFactory.getLogger(Component.class);
private static final long serialVersionUID = 1L;
/**
* Action used with IAuthorizationStrategy to determine whether a component is allowed to be
* enabled.
* <p>
* If enabling is authorized, a component may decide by itself (typically using it's enabled
* property) whether it is enabled or not. If enabling is not authorized, the given component is
* marked disabled, regardless its enabled property.
* <p>
* When a component is not allowed to be enabled (in effect disabled through the implementation
* of this interface), Wicket will try to prevent model updates too. This is not completely fail
* safe, as constructs like:
*
* <pre>
*
* User u = (User)getModelObject();
* u.setName(&quot;got you there!&quot;);
*
* </pre>
*
* can't be prevented. Indeed it can be argued that any model protection is best dealt with in
* your model objects to be completely secured. Wicket will catch all normal framework-directed
* use though.
*/
public static final Action ENABLE = new Action(Action.ENABLE);
/** Separator for component paths */
public static final char PATH_SEPARATOR = ':';
/** Path segment that represents this component's parent */
public static final String PARENT_PATH = "..";
/**
* Action used with IAuthorizationStrategy to determine whether a component and its children are
* allowed to be rendered.
* <p>
* There are two uses for this method:
* <ul>
* <li>The 'normal' use is for controlling whether a component is rendered without having any
* effect on the rest of the processing. If a strategy lets this method return 'false', then the
* target component and its children will not be rendered, in the same fashion as if that
* component had visibility property 'false'.</li>
* <li>The other use is when a component should block the rendering of the whole page. So
* instead of 'hiding' a component, what we generally want to achieve here is that we force the
* user to logon/give-credentials for a higher level of authorization. For this functionality,
* the strategy implementation should throw a {@link AuthorizationException}, which will then be
* handled further by the framework.</li>
* </ul>
* </p>
*/
public static final Action RENDER = new Action(Action.RENDER);
/** meta data for user specified markup id */
private static final MetaDataKey<String> MARKUP_ID_KEY = new MetaDataKey<>()
{
private static final long serialVersionUID = 1L;
};
/** meta data for user specified markup id */
private static final MetaDataKey<FeedbackMessages> FEEDBACK_KEY = new MetaDataKey<>()
{
private static final long serialVersionUID = 1L;
};
/** Basic model IModelComparator implementation for normal object models */
private static final IModelComparator defaultModelComparator = new IModelComparator()
{
private static final long serialVersionUID = 1L;
@Override
public boolean compare(Component component, Object b)
{
final Object a = component.getDefaultModelObject();
if (a == null && b == null)
{
return true;
}
if (a == null || b == null)
{
return false;
}
return a.equals(b);
}
};
/** True when a component is being auto-added */
private static final int FLAG_AUTO = 0x0001;
/** Flag for escaping HTML in model strings */
private static final int FLAG_ESCAPE_MODEL_STRINGS = 0x0002;
/** Boolean whether this component's model is inheritable. */
static final int FLAG_INHERITABLE_MODEL = 0x0004;
/** Versioning boolean */
private static final int FLAG_VERSIONED = 0x0008;
/** Visibility boolean */
private static final int FLAG_VISIBLE = 0x0010;
/** Render tag boolean */
private static final int FLAG_RENDER_BODY_ONLY = 0x0020;
/** Ignore attribute modifiers */
private static final int FLAG_IGNORE_ATTRIBUTE_MODIFIER = 0x0040;
/** True when a component is enabled for model updates and is reachable. */
private static final int FLAG_ENABLED = 0x0080;
/** Reserved subclass-definable flag bit */
protected static final int FLAG_RESERVED1 = 0x0100;
/** Reserved subclass-definable flag bit */
protected static final int FLAG_RESERVED2 = 0x0200;
/** Reserved subclass-definable flag bit */
protected static final int FLAG_RESERVED3 = 0x0400;
/** Reserved subclass-definable flag bit */
protected static final int FLAG_RESERVED4 = 0x0800;
/** Boolean whether this component was rendered at least once for tracking changes. */
private static final int FLAG_HAS_BEEN_RENDERED = 0x1000;
/**
* Internal indicator of whether this component may be rendered given the current context's
* authorization. It overrides the visible flag in case this is false. Authorization is done
* before trying to render any component (otherwise we would end up with a half rendered page in
* the buffer)
*/
private static final int FLAG_IS_RENDER_ALLOWED = 0x2000;
/** Whether or not the component should print out its markup id into the id attribute */
private static final int FLAG_OUTPUT_MARKUP_ID = 0x4000;
/**
* Output a placeholder tag if the component is not visible. This is useful in ajax mode to go
* to visible(false) to visible(true) without the overhead of repainting a visible parent
* container
*/
private static final int FLAG_PLACEHOLDER = 0x8000;
/** Reserved subclass-definable flag bit */
protected static final int FLAG_RESERVED5 = 0x10000;
/** onInitialize called */
protected static final int FLAG_INITIALIZED = 0x20000;
/** Set when a component is removed from the hierarchy */
private static final int FLAG_REMOVED = 0x40000;
/** Reserved subclass-definable flag bit */
protected static final int FLAG_RESERVED8 = 0x80000;
/**
* Flag that determines whether the model is set. This is necessary because of the way we
* represent component state ({@link #data}). We can't distinguish between model and behavior
* using instanceof, because one object can implement both interfaces. Thus we need this flag -
* when the flag is set, first object in {@link #data} is always model.
*/
private static final int FLAG_MODEL_SET = 0x100000;
/**
* Flag that restricts visibility of a component when set to true. This is usually used when a
* component wants to restrict visibility of another component. Calling
* {@link #setVisible(boolean)} on a component does not always have the desired effect because
* isVisible() can be overwritten thus this flag offers an alternative that should always work.
*/
private static final int FLAG_VISIBILITY_ALLOWED = 0x40000000;
/**
* The name of attribute that will hold markup id
*/
private static final String MARKUP_ID_ATTR_NAME = "id";
/**
* Meta data key for line precise error logging for the moment of addition. Made package private
* for access in {@link MarkupContainer} and {@link Page}
*/
static final MetaDataKey<String> ADDED_AT_KEY = new MetaDataKey<>()
{
private static final long serialVersionUID = 1L;
};
/**
* meta data key for line precise error logging for the moment of construction. Made package
* private for access in {@link Page}
*/
static final MetaDataKey<String> CONSTRUCTED_AT_KEY = new MetaDataKey<>()
{
private static final long serialVersionUID = 1L;
};
/** Component flags. See FLAG_* for possible non-exclusive flag values. */
private int flags = FLAG_VISIBLE | FLAG_ESCAPE_MODEL_STRINGS | FLAG_VERSIONED | FLAG_ENABLED |
FLAG_IS_RENDER_ALLOWED | FLAG_VISIBILITY_ALLOWED | FLAG_RESERVED5 /* page's stateless hint */;
// @formatter:off
private static final short RFLAG_ENABLED_IN_HIERARCHY_VALUE = 0x1;
private static final short RFLAG_ENABLED_IN_HIERARCHY_SET = 0x2;
private static final short RFLAG_VISIBLE_IN_HIERARCHY_VALUE = 0x4;
private static final short RFLAG_VISIBLE_IN_HIERARCHY_SET = 0x8;
/** onconfigure has been called */
private static final short RFLAG_CONFIGURED = 0x10;
private static final short RFLAG_BEFORE_RENDER_SUPER_CALL_VERIFIED = 0x20;
private static final short RFLAG_INITIALIZE_SUPER_CALL_VERIFIED = 0x40;
protected static final short RFLAG_CONTAINER_DEQUEING = 0x80;
private static final short RFLAG_ON_RE_ADD_SUPER_CALL_VERIFIED = 0x100;
/**
* Flag that makes we are in before-render callback phase Set after component.onBeforeRender is
* invoked (right before invoking beforeRender on children)
*/
private static final short RFLAG_RENDERING = 0x200;
private static final short RFLAG_PREPARED_FOR_RENDER = 0x400;
private static final short RFLAG_AFTER_RENDER_SUPER_CALL_VERIFIED = 0x800;
private static final short RFLAG_DETACHING = 0x1000;
/** True when a component is being removed from the hierarchy */
private static final short RFLAG_REMOVING_FROM_HIERARCHY = 0x2000;
/**
* This flag tracks if removals have been set on this component. Clearing this key is an
* expensive operation. With this flag this expensive call can be avoided.
*/
protected static final short RFLAG_CONTAINER_HAS_REMOVALS = 0x4000;
private static final short RFLAG_ON_CONFIGURE_SUPER_CALL_VERIFIED = (short) 0x8000;
// @formatter:on
/**
* Flags that only keep their value during the request. Useful for cache markers, etc. At the
* end of the request the value of this variable is reset to 0
*/
private transient short requestFlags = 0;
/** Component id. */
private final String id;
/** Any parent container. */
private MarkupContainer parent;
/**
* Instead of remembering the whole markupId, we just remember the number for this component so
* we can "reconstruct" the markupId on demand. While this could be part of {@link #data},
* profiling showed that having it as separate property consumes less memory.
*/
int generatedMarkupId = -1;
/** Must only be used by auto components */
private transient IMarkupFragment markup;
/**
* Will be re-created instead of persisted when session is replicated. Markup sourcing strategy
* are typically stateless (but don't have to).
*/
private transient IMarkupSourcingStrategy markupSourcingStrategy;
/**
* The object that holds the component state.
* <p>
* What's stored here depends on what attributes are set on component. Data can contains
* combination of following attributes:
* <ul>
* <li>Model (indicated by {@link #FLAG_MODEL_SET})
* <li>MetaDataEntry (optionally {@link MetaDataEntry}[] if more metadata entries are present) *
* <li>{@link Behavior}(s) added to component. The behaviors are not stored in separate array,
* they are part of the {@link #data} array (this is in order to save the space of the pointer
* to an empty array as most components have no behaviours). - FIXME - explain why - is this
* correct?
* </ul>
* If there is only one attribute set (i.e. model or MetaDataEntry([]) or one behavior), the
* #data object points directly to value of that attribute. Otherwise the data is of type
* Object[] where the attributes are ordered as specified above.
* <p>
*/
Object data = null;
final int data_start()
{
return getFlag(FLAG_MODEL_SET) ? 1 : 0;
}
final int data_length()
{
if (data == null)
{
return 0;
}
else if (data instanceof Object[] && !(data instanceof MetaDataEntry<?>[]))
{
return ((Object[])data).length;
}
else
{
return 1;
}
}
final Object data_get(int index)
{
if (data == null)
{
return null;
}
else if (data instanceof Object[] && !(data instanceof MetaDataEntry<?>[]))
{
Object[] array = (Object[])data;
return index < array.length ? array[index] : null;
}
else if (index == 0)
{
return data;
}
else
{
return null;
}
}
final void data_set(int index, Object object)
{
if (index > data_length() - 1)
{
throw new IndexOutOfBoundsException("can not set data at " + index +
" when data_length() is " + data_length());
}
else if (index == 0 && !(data instanceof Object[] && !(data instanceof MetaDataEntry<?>[])))
{
data = object;
}
else
{
Object[] array = (Object[])data;
array[index] = object;
}
}
final void data_add(Object object)
{
data_insert(-1, object);
}
final void data_insert(int position, Object object)
{
int currentLength = data_length();
if (position == -1)
{
position = currentLength;
}
if (position > currentLength)
{
throw new IndexOutOfBoundsException("can not insert data at " + position +
" when data_length() is " + currentLength);
}
if (currentLength == 0)
{
data = object;
}
else if (currentLength == 1)
{
Object[] array = new Object[2];
if (position == 0)
{
array[0] = object;
array[1] = data;
}
else
{
array[0] = data;
array[1] = object;
}
data = array;
}
else
{
Object[] array = new Object[currentLength + 1];
Object[] current = (Object[])data;
int after = currentLength - position;
if (position > 0)
{
System.arraycopy(current, 0, array, 0, position);
}
array[position] = object;
if (after > 0)
{
System.arraycopy(current, position, array, position + 1, after);
}
data = array;
}
}
final void data_remove(int position)
{
int currentLength = data_length();
if (position > currentLength - 1)
{
throw new IndexOutOfBoundsException();
}
else if (currentLength == 1)
{
data = null;
}
else if (currentLength == 2)
{
Object[] current = (Object[])data;
if (position == 0)
{
data = current[1];
}
else
{
data = current[0];
}
}
else
{
Object[] current = (Object[])data;
data = new Object[currentLength - 1];
if (position > 0)
{
System.arraycopy(current, 0, data, 0, position);
}
if (position != currentLength - 1)
{
final int left = currentLength - position - 1;
System.arraycopy(current, position + 1, data, position, left);
}
}
}
/**
* Constructor. All components have names. A component's id cannot be null. This is the minimal
* constructor of component. It does not register a model.
*
* @param id
* The non-null id of this component
* @throws WicketRuntimeException
* Thrown if the component has been given a null id.
*/
public Component(final String id)
{
this(id, null);
}
/**
* Constructor. All components have names. A component's id cannot be null. This constructor
* includes a model.
*
* @param id
* The non-null id of this component
* @param model
* The component's model
*
* @throws WicketRuntimeException
* Thrown if the component has been given a null id.
*/
public Component(final String id, final IModel<?> model)
{
checkId(id);
this.id = id;
init();
Application application = getApplication();
application.getComponentInstantiationListeners().onInstantiation(this);
final DebugSettings debugSettings = application.getDebugSettings();
if (debugSettings.isLinePreciseReportingOnNewComponentEnabled() && debugSettings.getComponentUseCheck())
{
setMetaData(CONSTRUCTED_AT_KEY,
ComponentStrings.toString(this, new MarkupException("constructed")));
}
if (model != null)
{
setModelImpl(wrap(model));
}
}
/**
* Let subclasses initialize this instance, before constructors are executed. <br>
* This method is intentionally <b>not</b> declared protected, to limit overriding to classes in
* this package.
*/
void init()
{
}
/**
* Get the Markup associated with the Component. If not subclassed, the parent container is
* asked to return the markup of this child component.
* <p/>
* Components like Panel and Border should return the "calling" markup fragment, e.g.
* <code>&lt;span wicket:id="myPanel"&gt;body&lt;/span&gt;</code>. You may use
* Panel/Border/Enclosure.getMarkup(null) to return the associated markup file. And
* Panel/Border/Enclosure.getMarkup(child) will search the child in the appropriate markup
* fragment.
*
* @see MarkupContainer#getMarkup(Component)
*
* @return The markup fragment
*/
public IMarkupFragment getMarkup()
{
// Markup already determined or preset?
if (markup != null)
{
return markup;
}
// No parent, than check associated markup files
if (parent == null)
{
// Must be a MarkupContainer to have associated markup file
if (this instanceof MarkupContainer)
{
MarkupContainer container = (MarkupContainer)this;
Markup associatedMarkup = container.getAssociatedMarkup();
if (associatedMarkup != null)
{
markup = associatedMarkup;
return markup;
}
}
// Don't know how to find the markup
throw new MarkupNotFoundException(
"Can not determine Markup. Component is not yet connected to a parent. " +
toString());
}
// Ask the parent for find the markup for me
markup = parent.getMarkup(this);
return markup;
}
/**
* @return The 'id' attribute from the associated markup tag
*/
public final String getMarkupIdFromMarkup()
{
ComponentTag tag = getMarkupTag();
if (tag != null)
{
String id = tag.getAttribute("id");
if (Strings.isEmpty(id) == false)
{
return id.trim();
}
}
return null;
}
/**
* Set the markup for the component. Note that the component's markup variable is transient and
* thus must only be used for one render cycle. E.g. auto-component are using it. You may also
* it if you subclassed getMarkup().
*
* @param markup
*/
public final Component setMarkup(final IMarkupFragment markup)
{
this.markup = markup;
return this;
}
/**
* Called on all components before any component is rendered. This method
* should be used to configure such things as visibility and enabled flags.
* <p>
* Overrides must call {@code super.onConfigure()}, usually before any other code
* <p>
* NOTE: Component hierarchy should not be modified inside this method, instead it should be
* done in {@link #onBeforeRender()}
* <p>
* NOTE: Why this method is preferrable to directly overriding {@link #isVisible()} and
* {@link #isEnabled()}? Because those methods are called multiple times even for processing of
* a single request. If they contain expensive logic they can slow down the response time of the
* entire page. Further, overriding those methods directly on form components may lead to
* inconsistent or unexpected state depending on when those methods are called in the form
* processing workflow. It is a better practice to push changes to state rather than pull.
* <p>
* NOTE: If component's visibility or another property depends on another component you may call
* {@code other.configure()} followed by {@code other.isVisible()} as mentioned in
* {@link #configure()} javadoc.
* <p>
* NOTE: Why should {@link #onBeforeRender()} not be used for this? Because if a component's
* visibility is controlled inside {@link #onBeforeRender()}, once invisible the component will
* never become visible again.
*/
protected void onConfigure()
{
setRequestFlag(RFLAG_ON_CONFIGURE_SUPER_CALL_VERIFIED, true);
}
/**
* This method is meant to be used as an alternative to initialize components. Usually the
* component's constructor is used for this task, but sometimes a component cannot be
* initialized in isolation, it may need to access its parent component or its markup in order
* to fully initialize. This method is invoked once per component's lifecycle when a path exists
* from this component to the {@link Page} thus providing the component with an atomic callback
* when the component's environment is built out.
* <p>
* Overrides must call super#{@link #onInitialize()}. Usually this should be the first thing an
* override does, much like a constructor.
* </p>
* <p>
* Parent containers are guaranteed to be initialized before their children
* </p>
*
* <p>
* It is safe to use {@link #getPage()} in this method
* </p>
*
* <p>
* NOTE:The timing of this call is not precise, the contract is that it is called sometime
* before {@link Component#onBeforeRender()}.
* </p>
*
*/
protected void onInitialize()
{
setRequestFlag(RFLAG_INITIALIZE_SUPER_CALL_VERIFIED, true);
}
/**
* Checks if the component has been initialized - {@link #onInitialize()} has been called
*
* @return {@code true} if component has been initialized
*/
public final boolean isInitialized()
{
return getFlag(FLAG_INITIALIZED);
}
/**
* THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT!
*
* Used to call {@link #onInitialize()}
*/
public void internalInitialize()
{
fireInitialize();
}
/**
* Used to call {@link #onInitialize()}
*/
final void fireInitialize()
{
if (!getFlag(FLAG_INITIALIZED))
{
setFlag(FLAG_INITIALIZED, true);
setRequestFlag(RFLAG_INITIALIZE_SUPER_CALL_VERIFIED, false);
onInitialize();
verifySuperCall("onInitialize", RFLAG_INITIALIZE_SUPER_CALL_VERIFIED);
getApplication().getComponentInitializationListeners().onInitialize(this);
}
else if (getFlag(FLAG_REMOVED))
{
setFlag(FLAG_REMOVED, false);
setRequestFlag(RFLAG_ON_RE_ADD_SUPER_CALL_VERIFIED, false);
onReAdd();
verifySuperCall("onReAdd", RFLAG_ON_RE_ADD_SUPER_CALL_VERIFIED);
}
}
/**
* Called on every component after the page is rendered. Calls hook {@link #onAfterRender()}.
*/
final void afterRender()
{
setRequestFlag(RFLAG_PREPARED_FOR_RENDER, false);
try
{
setRequestFlag(RFLAG_AFTER_RENDER_SUPER_CALL_VERIFIED, false);
onAfterRender();
getApplication().getComponentOnAfterRenderListeners().onAfterRender(this);
verifySuperCall("onAfterRender", RFLAG_AFTER_RENDER_SUPER_CALL_VERIFIED);
}
finally
{
// this flag must always be set to false.
setRequestFlag(RFLAG_RENDERING, false);
}
}
/**
* THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT!
*
* Called on all components before any component is rendered. Calls hooks
* {@link #configure()} and (if visible) {@link #onBeforeRender()}
* and delegates to {@link #beforeRender()} of all child components.
*/
public final void beforeRender()
{
if (this instanceof IFeedback)
{
Optional<FeedbackDelay> delay = FeedbackDelay.get(getRequestCycle());
if (delay.isPresent()) {
delay.get().postpone((IFeedback)this);
return;
}
}
configure();
if ((determineVisibility()) && !getRequestFlag(RFLAG_RENDERING) &&
!getRequestFlag(RFLAG_PREPARED_FOR_RENDER))
{
try {
setRequestFlag(RFLAG_BEFORE_RENDER_SUPER_CALL_VERIFIED, false);
Application application = getApplication();
application.getComponentPreOnBeforeRenderListeners().onBeforeRender(this);
onBeforeRender();
application.getComponentPostOnBeforeRenderListeners().onBeforeRender(this);
verifySuperCall("onBeforeRender", RFLAG_BEFORE_RENDER_SUPER_CALL_VERIFIED);
} catch (RuntimeException ex) {
setRequestFlag(RFLAG_PREPARED_FOR_RENDER, false);
throw ex;
}
}
}
/**
* Triggers {@link #onConfigure()} to be invoked on this component if it has not already during
* this request.
* <p>
* This method should be invoked before any calls to {@link #isVisible()} or
* {@link #isEnabled()}. Usually this method will be called by the framework before the
* component is rendered and so users should not need to call it; however, in cases where
* visibility or enabled or other state of one component depends on the state of another this
* method should be manually invoked on the other component by the user. EG to link visiliby of
* two markup containers the following should be done:
*
* <pre>
* final WebMarkupContainer source=new WebMarkupContainer("a") {
* protected void onConfigure() {
* setVisible(Math.rand()&gt;0.5f);
* }
* };
*
* WebMarkupContainer linked=new WebMarkupContainer("b") {
* protected void onConfigure() {
* source.configure(); // make sure source is configured
* setVisible(source.isVisible());
* }
* }
* </pre>
*
* </p>
*/
public final void configure()
{
if (!getRequestFlag(RFLAG_CONFIGURED))
{
clearEnabledInHierarchyCache();
clearVisibleInHierarchyCache();
setRequestFlag(RFLAG_ON_CONFIGURE_SUPER_CALL_VERIFIED, false);
onConfigure();
verifySuperCall("onConfigure", RFLAG_ON_CONFIGURE_SUPER_CALL_VERIFIED);
for (Behavior behavior : getBehaviors())
{
if (isBehaviorAccepted(behavior))
{
behavior.onConfigure(this);
}
}
// check authorization
setRenderAllowed();
internalOnAfterConfigure();
getApplication().getComponentOnConfigureListeners().onConfigure(this);
setRequestFlag(RFLAG_CONFIGURED, true);
}
}
/**
* Verify super calls of an overridden hook method.
*/
private final void verifySuperCall(String method, short flag)
{
if (!getRequestFlag(flag))
{
throw new IllegalStateException(String.format("%s() in the hierarchy of %s has not called super.%s()", method, getClass().getName(), method));
}
setRequestFlag(flag, false);
}
/**
* Called after the {@link #onConfigure()}, but before {@link #onBeforeRender()}
*/
void internalOnAfterConfigure()
{
}
/**
* Redirects to any intercept page previously specified by a call to {@link #redirectToInterceptPage(Page)}.
* The redirect is done by throwing an exception. If there is no intercept page no exception
* will be thrown and the program flow will continue uninterrupted.
*
* Example:
*
* <pre>
* add(new Link(&quot;login&quot;)
* {
* protected void onClick()
* {
* if (authenticate())
* {
* continueToOriginalDestination();
* // if we reach this line there was no intercept page, so go to home page
* setResponsePage(WelcomePage.class);
* }
* }
* });
*
* @see Component#redirectToInterceptPage(Page)
*/
public final void continueToOriginalDestination()
{
RestartResponseAtInterceptPageException.continueToOriginalDestination();
}
/**
* Clears any data about previously intercepted page.
*/
public final void clearOriginalDestination()
{
RestartResponseAtInterceptPageException.clearOriginalDestination();
}
/**
* Registers a debug feedback message for this component
*
* @param message
* The feedback message
*/
@Override
public final void debug(final Serializable message)
{
getFeedbackMessages().debug(this, message);
addStateChange();
}
/**
* Signals this Component that it is removed from the Component hierarchy.
*/
final void internalOnRemove()
{
setRequestFlag(RFLAG_REMOVING_FROM_HIERARCHY, true);
onRemove();
setFlag(FLAG_REMOVED, true);
if (getRequestFlag(RFLAG_REMOVING_FROM_HIERARCHY))
{
throw new IllegalStateException(Component.class.getName() +
" has not been properly removed from hierarchy. Something in the hierarchy of " +
getClass().getName() +
" has not called super.onRemove() in the override of onRemove() method");
}
Behaviors.onRemove(this);
removeChildren();
}
/**
* Detaches the component. This is called at the end of the request for all the pages that are
* touched in that request.
*/
@Override
public final void detach()
{
try
{
setRequestFlag(RFLAG_DETACHING, true);
onDetach();
if (getRequestFlag(RFLAG_DETACHING))
{
throw new IllegalStateException(Component.class.getName() +
" has not been properly detached. Something in the hierarchy of " +
getClass().getName() +
" has not called super.onDetach() in the override of onDetach() method");
}
// always detach models because they can be attached without the
// component. eg component has a compoundpropertymodel and one of its
// children component's getmodelobject is called
detachModels();
// detach any behaviors
Behaviors.detach(this);
}
catch (Exception x)
{
throw new WicketRuntimeException("An error occurred while detaching component: " + toString(true), x);
}
// always detach children because components can be attached
// independently of their parents
detachChildren();
// reset the model to null when the current model is a IWrapModel and
// the model that created it/wrapped in it is a IComponentInheritedModel
// The model will be created next time.
if (getFlag(FLAG_INHERITABLE_MODEL))
{
setModelImpl(null);
setFlag(FLAG_INHERITABLE_MODEL, false);
}
clearEnabledInHierarchyCache();
clearVisibleInHierarchyCache();
// clear request flags but keep super call verifications WICKET-5417
requestFlags &= (RFLAG_INITIALIZE_SUPER_CALL_VERIFIED | RFLAG_ON_CONFIGURE_SUPER_CALL_VERIFIED | RFLAG_BEFORE_RENDER_SUPER_CALL_VERIFIED);
detachFeedback();
internalDetach();
// notify any detach listener
IDetachListener detachListener = getApplication().getFrameworkSettings()
.getDetachListener();
if (detachListener != null)
{
detachListener.onDetach(this);
}
}
private void detachFeedback()
{
FeedbackMessages feedback = getMetaData(FEEDBACK_KEY);
if (feedback != null)
{
feedback.clear(getApplication().getApplicationSettings()
.getFeedbackMessageCleanupFilter());
if (feedback.isEmpty())
{
setMetaData(FEEDBACK_KEY, null);
}
else
{
feedback.detach();
}
}
}
/**
* Removes the cached markup at the end of the request. For the next request it will be get
* either from the parent's markup or from {@link MarkupCache}.
*/
private void internalDetach()
{
markup = null;
}
/**
* Detaches all models
*/
public void detachModels()
{
// Detach any detachable model from this component
detachModel();
}
/**
* Registers an error feedback message for this component
*
* @param message
* The feedback message
*/
@Override
public final void error(final Serializable message)
{
getFeedbackMessages().error(this, message);
addStateChange();
}
/**
* Registers a fatal feedback message for this component
*
* @param message
* The feedback message
*/
@Override
public final void fatal(final Serializable message)
{
getFeedbackMessages().fatal(this, message);
addStateChange();
}
/**
* Finds the first container parent of this component of the given class.
*
* @param <Z>
* type of parent
*
*
* @param c
* MarkupContainer class to search for
* @return First container parent that is an instance of the given class, or null if none can be
* found
*/
public final <Z> Z findParent(final Class<Z> c)
{
// Start with immediate parent
MarkupContainer current = parent;
// Walk up containment hierarchy
while (current != null)
{
// Is current an instance of this class?
if (c.isInstance(current))
{
return c.cast(current);
}
// Check parent
current = current.getParent();
}
// Failed to find component
return null;
}
/**
* @return The nearest markup container with associated markup
*/
public final MarkupContainer findParentWithAssociatedMarkup()
{
MarkupContainer container = parent;
while (container != null)
{
if (container.getAssociatedMarkup() != null)
{
return container;
}
container = container.getParent();
}
// This should never happen since Page always has associated markup
throw new WicketRuntimeException("Unable to find parent with associated markup");
}
/**
* Gets interface to application that this component is a part of.
*
* @return The application associated with the session that this component is in.
* @see Application
*/
public final Application getApplication()
{
return Application.get();
}
/**
* @return A path of the form [page-class-name]:[page-relative-path]
* @see Component#getPageRelativePath()
*/
public final String getClassRelativePath()
{
return getClass().getName() + PATH_SEPARATOR + getPageRelativePath();
}
/**
* Get the converter that should be used by this component, delegates to
* {@link #createConverter(Class)} and then to the application's
* {@link IConverterLocator}.
*
* @param type
* The type to convert to
*
* @return The converter that should be used by this component
*/
@SuppressWarnings("unchecked")
@Override
public <C> IConverter<C> getConverter(Class<C> type) {
IConverter<?> converter = createConverter(type);
if (converter != null) {
return (IConverter<C>) converter;
}
return getApplication().getConverterLocator().getConverter(type);
}
/**
* Factory method for converters to be used by this component,
* returns {@code null} by default.
*
* @param type
* The type to convert to
*
* @return a converter to be used by this component
*/
protected IConverter<?> createConverter(Class<?> type) {
return null;
}
/**
* Gets whether model strings should be escaped.
*
* @return Returns whether model strings should be escaped
*/
public final boolean getEscapeModelStrings()
{
return getFlag(FLAG_ESCAPE_MODEL_STRINGS);
}
/**
* Gets the id of this component.
*
* @return The id of this component
*/
@Override
public String getId()
{
return id;
}
/**
* @return Innermost model for this component
*/
public final IModel<?> getInnermostModel()
{
return getInnermostModel(getDefaultModel());
}
/**
* Gets the locale for this component. By default, it searches its parents for a locale. If no
* parents (it's a recursive search) returns a locale, it gets one from the session.
*
* @return The locale to be used for this component
* @see Session#getLocale()
*/
public Locale getLocale()
{
if (parent != null)
{
return parent.getLocale();
}
return getSession().getLocale();
}
/**
* Convenience method to provide easy access to the localizer object within any component.
*
* @return The localizer object
*/
public final Localizer getLocalizer()
{
return getApplication().getResourceSettings().getLocalizer();
}
/**
* THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT!
*
* Get the first component tag in the associated markup
*
* @return first component tag
*/
protected final ComponentTag getMarkupTag()
{
IMarkupFragment markup = getMarkup();
if (markup != null)
{
for (int i = 0; i < markup.size(); i++)
{
MarkupElement elem = markup.get(i);
if (elem instanceof ComponentTag)
{
return (ComponentTag)elem;
}
}
}
return null;
}
/**
* THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT!
*
* Get a copy of the markup's attributes which are associated with the component.
* <p>
* Modifications to the map returned don't change the tags attributes. It is just a copy.
* <p>
* Note: The component must have been added (directly or indirectly) to a container with an
* associated markup file (Page, Panel or Border).
*
* @return markup attributes
*/
public final ValueMap getMarkupAttributes()
{
ComponentTag tag = getMarkupTag();
if (tag != null)
{
ValueMap attrs = new ValueMap(tag.getAttributes());
attrs.makeImmutable();
return attrs;
}
return ValueMap.EMPTY_MAP;
}
/**
* Get the markupId
*
* @return MarkupId
*/
public final Object getMarkupIdImpl()
{
if (generatedMarkupId != -1)
{
return generatedMarkupId;
}
String id = getMetaData(MARKUP_ID_KEY);
// if still no markup id is found, and the component has been attached to a page, try to
// retrieve the id from the markup file.
if (id == null && findPage() != null)
{
id = getMarkupIdFromMarkup();
}
return id;
}
/**
* Retrieves id by which this component is represented within the markup. This is either the id
* attribute set explicitly via a call to {@link #setMarkupId(String)}, id attribute defined in
* the markup, or an automatically generated id - in that order.
* <p>
* If no id is set and <code>createIfDoesNotExist</code> is false, this method will return null.
* Otherwise it will generate an id value which by default will be unique in the page. This is
* the preferred way as there is no chance of id collision. This will also enable
* {@link #setOutputMarkupId(boolean)}.
* <p>
*
* <p>
* Note: This method should only be called after the component or its parent have been added to
* the page.
*
* @param createIfDoesNotExist
* When there is no existing markup id, determines whether it should be generated or
* whether <code>null</code> should be returned.
*
* @return markup id of the component
*/
public String getMarkupId(boolean createIfDoesNotExist)
{
IMarkupIdGenerator markupIdGenerator = getApplication().getMarkupSettings().getMarkupIdGenerator();
String markupId = markupIdGenerator.generateMarkupId(this, createIfDoesNotExist);
return markupId;
}
/**
* Retrieves id by which this component is represented within the markup. This is either the id
* attribute set explicitly via a call to {@link #setMarkupId(String)}, id attribute defined in
* the markup, or an automatically generated id - in that order.
* <p>
* If no explicit id is set this function will generate an id value that will be unique in the
* page. This is the preferred way as there is no chance of id collision. This will also enable
* {@link #setOutputMarkupId(boolean)}.
* <p>
* Note: This method should only be called after the component or its parent have been added to
* the page.
*
* @return markup id of the component
*/
public String getMarkupId()
{
return getMarkupId(true);
}
/**
* Gets metadata for this component using the given key.
*
* @param <M>
* The type of the metadata.
*
* @param key
* The key for the data
* @return The metadata or null of no metadata was found for the given key
* @see MetaDataKey
*/
@Override
public final <M extends Serializable> M getMetaData(final MetaDataKey<M> key)
{
return key.get(getMetaData());
}
/**
* Gets the meta data entries for this component as an array of {@link MetaDataEntry} objects.
*
* @return the meta data entries for this component
*/
private MetaDataEntry<?>[] getMetaData()
{
MetaDataEntry<?>[] metaData = null;
// index where we should expect the entry
int index = getFlag(FLAG_MODEL_SET) ? 1 : 0;
int length = data_length();
if (index < length)
{
Object object = data_get(index);
if (object instanceof MetaDataEntry<?>[])
{
metaData = (MetaDataEntry<?>[])object;
}
else if (object instanceof MetaDataEntry)
{
metaData = new MetaDataEntry[] { (MetaDataEntry<?>)object };
}
}
return metaData;
}
/**
* Gets the model. It returns the object that wraps the backing model.
*
* @return The model
*/
public final IModel<?> getDefaultModel()
{
IModel<?> model = getModelImpl();
// If model is null
if (model == null)
{
// give subclass a chance to lazy-init model
model = initModel();
setModelImpl(model);
}
return model;
}
/**
* Gets the backing model object. Unlike getDefaultModel().getObject(), this method returns null
* for a null model.
*
* @return The backing model object
*/
public final Object getDefaultModelObject()
{
final IModel<?> model = getDefaultModel();
if (model != null)
{
try
{
// Get model value for this component.
return model.getObject();
}
catch (Exception ex)
{
// wrap the exception so that it brings info about the component
WicketRuntimeException rex = new WicketRuntimeException(
"An error occurred while getting the model object for Component: " +
this.toString(true), ex);
throw rex;
}
}
return null;
}
/**
* Gets a model object as a string. Depending on the "escape model strings" flag of the
* component, the string is either HTML escaped or not. "HTML escaped" meaning that only HTML
* sensitive chars are escaped but not all none-ascii chars. Proper HTML encoding should be used
* instead. In case you really need a fully escaped model string you may call
* {@link Strings#escapeMarkup(CharSequence, boolean, boolean)} on the model string returned.
*
* @see Strings#escapeMarkup(CharSequence, boolean, boolean)
* @see #getEscapeModelStrings()
*
* @return Model object for this component as a string
*/
public final String getDefaultModelObjectAsString()
{
return getDefaultModelObjectAsString(getDefaultModelObject());
}
/**
* Gets a model object as a string. Depending on the "escape model strings" flag of the
* component, the string is either HTML escaped or not. "HTML escaped" meaning that only HTML
* sensitive chars are escaped but not all none-ascii chars. Proper HTML encoding should be used
* instead. In case you really need a fully escaped model string you may call
* {@link Strings#escapeMarkup(CharSequence, boolean, boolean)} on the model string returned.
*
* @see Strings#escapeMarkup(CharSequence, boolean, boolean)
* @see #getEscapeModelStrings()
*
* @param modelObject
* Model object to convert to string
* @return The string
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public final String getDefaultModelObjectAsString(final Object modelObject)
{
if (modelObject != null)
{
// Get converter
final Class<?> objectClass = modelObject.getClass();
final IConverter converter = getConverter(objectClass);
// Model string from property
final String modelString = converter.convertToString(modelObject, getLocale());
if (modelString != null)
{
// If we should escape the markup
if (getFlag(FLAG_ESCAPE_MODEL_STRINGS))
{
// Escape HTML sensitive characters only. Not all none-ascii chars
return Strings.escapeMarkup(modelString, false, false).toString();
}
return modelString;
}
}
return "";
}
/**
* Gets whether or not component will output id attribute into the markup. id attribute will be
* set to the value returned from {@link Component#getMarkupId()}.
*
* @return whether or not component will output id attribute into the markup
*/
public final boolean getOutputMarkupId()
{
return getFlag(FLAG_OUTPUT_MARKUP_ID);
}
/**
* Gets whether or not an invisible component will render a placeholder tag.
*
* @return true if a placeholder tag should be rendered
*/
public final boolean getOutputMarkupPlaceholderTag()
{
return getFlag(FLAG_PLACEHOLDER);
}
/**
* Gets the page holding this component.
*
* @return The page holding this component
* @throws WicketRuntimeException
* Thrown if component is not yet attached to a Page.
* @see #findPage()
*/
@Override
public final Page getPage()
{
// Search for nearest Page
final Page page = findPage();
// If no Page was found
if (page == null)
{
// Give up with a nice exception
throw new WicketRuntimeException("No Page found for component: " + this.toString(true)
+ ". You probably forgot to add it to its parent component.");
}
return page;
}
/**
* Gets the path to this component relative to its containing page, i.e. without leading page
* id.
*
* @return The path to this component relative to the page it is in
*/
@Override
public final String getPageRelativePath()
{
return Strings.afterFirstPathComponent(getPath(), PATH_SEPARATOR);
}
/**
* Gets any parent container, or null if there is none.
*
* @return Any parent container, or null if there is none
*/
@Override
public final MarkupContainer getParent()
{
return parent;
}
/**
* Gets this component's path.
*
* @return Colon separated path to this component in the component hierarchy
*/
public final String getPath()
{
final PrependingStringBuffer buffer = new PrependingStringBuffer(32);
for (Component c = this; c != null; c = c.getParent())
{
if (buffer.length() > 0)
{
buffer.prepend(PATH_SEPARATOR);
}
buffer.prepend(c.getId());
}
return buffer.toString();
}
/**
* If false the component's tag will be printed as well as its body (which is default). If true
* only the body will be printed, but not the component's tag.
*
* @return If true, the component tag will not be printed
*/
public final boolean getRenderBodyOnly()
{
return getFlag(FLAG_RENDER_BODY_ONLY);
}
/**
* @return The request for this component's active request cycle
*/
public final Request getRequest()
{
RequestCycle requestCycle = getRequestCycle();
if (requestCycle == null)
{
// Happens often with WicketTester when one forgets to call
// createRequestCycle()
throw new WicketRuntimeException("No RequestCycle is currently set!");
}
return requestCycle.getRequest();
}
/**
* Gets the active request cycle for this component
*
* @return The request cycle
*/
public final RequestCycle getRequestCycle()
{
return RequestCycle.get();
}
/**
* @return The response for this component's active request cycle
*/
public final Response getResponse()
{
return getRequestCycle().getResponse();
}
/**
* Gets the current Session object.
*
* @return The Session that this component is in
*/
public Session getSession()
{
return Session.get();
}
/**
* @return Size of this Component in bytes. Returns {@code 0} - if the size cannot be calculated for some reason
*/
public long getSizeInBytes()
{
final MarkupContainer originalParent = parent;
parent = null;
long size = 0;
try
{
size = WicketObjects.sizeof(this);
}
catch (Exception e)
{
log.error("Exception getting size for component " + this, e);
}
parent = originalParent;
return size;
}
/**
* @param key
* Key of string resource in property file
* @return The String
* @see Localizer
*/
public final String getString(final String key)
{
return getString(key, null);
}
/**
* @param key
* The resource key
* @param model
* The model
* @return The formatted string
* @see Localizer
*/
public final String getString(final String key, final IModel<?> model)
{
return getLocalizer().getString(key, this, model);
}
/**
* @param key
* The resource key
* @param model
* The model
* @param defaultValue
* A default value if the string cannot be found
* @return The formatted string
* @see Localizer
*/
public final String getString(final String key, final IModel<?> model, final String defaultValue)
{
return getLocalizer().getString(key, this, model, defaultValue);
}
/**
* A convenience method to access the Sessions's style.
*
* @return The style of this component respectively the style of the Session.
*
* @see org.apache.wicket.Session#getStyle()
*/
public final String getStyle()
{
Session session = getSession();
if (session == null)
{
throw new WicketRuntimeException("Wicket Session object not available");
}
return session.getStyle();
}
/**
* Gets the variation string of this component that will be used to look up markup for this
* component. Subclasses can override this method to define by an instance what markup variation
* should be picked up. By default it will return null or the value of a parent.
*
* @return The variation of this component.
*/
public String getVariation()
{
if (parent != null)
{
return parent.getVariation();
}
return null;
}
/**
* Gets whether this component was rendered at least once.
*
* @return true if the component has been rendered before, false if it is merely constructed
*/
public final boolean hasBeenRendered()
{
return getFlag(FLAG_HAS_BEEN_RENDERED);
}
/**
* Gets feedback messages for this component. This method will instantiate a
* {@link FeedbackMessages} instance and add it to the component metadata, even when called on a
* component that has no feedback messages, to avoid the overhead use
* {@link #hasFeedbackMessage()}
*
* @return feedback messages instance
*/
public FeedbackMessages getFeedbackMessages()
{
FeedbackMessages messages = getMetaData(FEEDBACK_KEY);
if (messages == null)
{
messages = new FeedbackMessages();
setMetaData(FEEDBACK_KEY, messages);
}
return messages;
}
/**
* @return True if this component has an error message
*/
public final boolean hasErrorMessage()
{
FeedbackMessages messages = getMetaData(FEEDBACK_KEY);
if (messages == null)
{
return false;
}
return messages.hasMessage(FeedbackMessage.ERROR);
}
/**
* @return True if this component has some kind of feedback message
*
*/
public final boolean hasFeedbackMessage()
{
FeedbackMessages messages = getMetaData(FEEDBACK_KEY);
if (messages == null)
{
return false;
}
return messages.size() > 0;
}
/**
* Registers an informational feedback message for this component
*
* @param message
* The feedback message
*/
@Override
public final void info(final Serializable message)
{
getFeedbackMessages().info(this, message);
addStateChange();
}
/**
* Registers an success feedback message for this component
*
* @param message
* The feedback message
*/
@Override
public final void success(final Serializable message)
{
getFeedbackMessages().success(this, message);
addStateChange();
}
/**
* Authorizes an action for a component.
*
* @param action
* The action to authorize
* @return True if the action is allowed
* @throws AuthorizationException
* Can be thrown by implementation if action is unauthorized
*/
public final boolean isActionAuthorized(Action action)
{
IAuthorizationStrategy authorizationStrategy = getSession().getAuthorizationStrategy();
if (authorizationStrategy != null)
{
return authorizationStrategy.isActionAuthorized(this, action);
}
return true;
}
/**
* @return true if this component is authorized to be enabled, false otherwise
*/
public final boolean isEnableAllowed()
{
return isActionAuthorized(ENABLE);
}
/**
* Gets whether this component is enabled. Specific components may decide to implement special
* behavior that uses this property, like web form components that add a disabled='disabled'
* attribute when enabled is false.
*
* @return Whether this component is enabled.
*/
public boolean isEnabled()
{
return getFlag(FLAG_ENABLED);
}
/**
* Checks the security strategy if the {@link Component#RENDER} action is allowed on this
* component
*
* @return true if {@link Component#RENDER} action is allowed, false otherwise
*/
public final boolean isRenderAllowed()
{
return getFlag(FLAG_IS_RENDER_ALLOWED);
}
/**
* Returns if the component is stateless or not. It checks the stateless hint if that is false
* it returns directly false. If that is still true it checks all its behaviors if they can be
* stateless.
*
* @return whether the component is stateless.
*/
public final boolean isStateless()
{
if ((isVisibleInHierarchy() && isEnabledInHierarchy()) == false && canCallListener() == false)
{
// the component is either invisible or disabled and it can't call listeners
// then pretend the component is stateless
return true;
}
if (!getStatelessHint())
{
return false;
}
for (Behavior behavior : getBehaviors())
{
if (!behavior.getStatelessHint(this))
{
return false;
}
}
return true;
}
/**
* @return {@code true} if this component should notify its holding page about changes in its
* state. If a {@link Page} is not versioned then it wont track changes in its
* components and will use the same {@link Page#getPageId()} during its lifetime
*/
public boolean isVersioned()
{
// Is the component itself versioned?
if (!getFlag(FLAG_VERSIONED))
{
return false;
}
else
{
// If there's a parent and this component is versioned
if (parent != null)
{
// Check if the parent is unversioned. If any parent
// (recursively) is unversioned, then this component is too
if (!parent.isVersioned())
{
return false;
}
}
return true;
}
}
/**
* Gets whether this component and any children are visible.
* <p>
* WARNING: this method can be called multiple times during a request. If you override this
* method, it is a good idea to keep it cheap in terms of processing. Alternatively, you can
* call {@link #setVisible(boolean)}.
* <p>
*
* @return True if component and any children are visible
*/
public boolean isVisible()
{
return getFlag(FLAG_VISIBLE);
}
/**
* Checks if the component itself and all its parents are visible.
*
* @return true if the component and all its parents are visible.
*/
public final boolean isVisibleInHierarchy()
{
if (getRequestFlag(RFLAG_VISIBLE_IN_HIERARCHY_SET))
{
return getRequestFlag(RFLAG_VISIBLE_IN_HIERARCHY_VALUE);
}
final boolean state;
Component parent = getParent();
if (parent != null && !parent.isVisibleInHierarchy())
{
state = false;
}
else
{
state = determineVisibility();
}
setRequestFlag(RFLAG_VISIBLE_IN_HIERARCHY_SET, true);
setRequestFlag(RFLAG_VISIBLE_IN_HIERARCHY_VALUE, state);
return state;
}
/**
* THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT!
*
* Sets the RENDERING flag and removes the PREPARED_FOR_RENDER flag on component and it's
* children.
*
* @param setRenderingFlag
* if this is false only the PREPARED_FOR_RENDER flag is removed from component, the
* RENDERING flag is not set.
*/
public final void markRendering(boolean setRenderingFlag)
{
internalMarkRendering(setRenderingFlag);
}
/**
* Called to indicate that the model content for this component has been changed
*/
public final void modelChanged()
{
// Call user code
internalOnModelChanged();
onModelChanged();
}
/**
* Called to indicate that the model content for this component is about to change
*/
public final void modelChanging()
{
checkHierarchyChange(this);
// Call user code
onModelChanging();
// Tell the page that our model changed
final Page page = findPage();
if (page != null)
{
page.componentModelChanging(this);
}
}
/**
* Redirects browser to an intermediate page such as a sign-in page. The current request's URL
* is saved for future use by method {@link #continueToOriginalDestination()}; only use this method when
* you plan to continue to the current URL at some later time; otherwise just set a new response page.
*
* @param page
* The sign in page
*
* @see #setResponsePage(Class)
* @see #setResponsePage(IRequestablePage)
* @see #setResponsePage(Class, PageParameters)
* @see Component#continueToOriginalDestination()
*/
public final void redirectToInterceptPage(final Page page)
{
throw new RestartResponseAtInterceptPageException(page);
}
/**
* Removes this component from its parent. It's important to remember that a component that is
* removed cannot be referenced from the markup still.
* <p>
* You must not use this method in your callback to any of the
* {@link MarkupContainer#visitChildren(IVisitor)} methods. See <a
* href="https://issues.apache.org/jira/browse/WICKET-3229">WICKET-3329</a>.
*/
public final void remove()
{
if (parent == null)
{
throw new IllegalStateException("Cannot remove " + this + " from null parent!");
}
parent.remove(this);
}
/**
* THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT!
* <p>
* Renders this component as a part of a response - the caller has to
* make sure that this component is prepared for render.
*
* @see #beforeRender()
*/
public final void renderPart() {
Page page = getPage();
page.startComponentRender(this);
markRendering(true);
render();
page.endComponentRender(this);
}
/**
* Render this component and all its children. Always calls hook {@link #onAfterRender()}
* regardless of any exception.
*/
public final void render()
{
if (isAuto())
{
// auto components are prepared when rendered
beforeRender();
}
// Do the render
RuntimeException exception = null;
try
{
setRequestFlag(RFLAG_RENDERING, true);
internalRender();
}
catch (final RuntimeException ex)
{
// Remember it as the originating exception
exception = ex;
}
finally
{
try
{
// Cleanup
afterRender();
}
catch (RuntimeException ex2)
{
// Only remember it if not already another exception happened
if (exception == null)
{
exception = ex2;
}
}
}
// Re-throw if needed
if (exception != null)
{
throw exception;
}
}
/**
* Performs a render of this component as part of a Page level render process.
*/
private void internalRender()
{
// Make sure there is a markup available for the Component
IMarkupFragment markup = getMarkup();
if (markup == null)
{
throw new MarkupNotFoundException("Markup not found for Component: " + toString());
}
// MarkupStream is an Iterator for the markup
MarkupStream markupStream = new MarkupStream(markup);
MarkupElement elem = markup.get(0);
if (elem instanceof ComponentTag)
{
// Guarantee that the markupStream is set and determineVisibility not yet tested
// See WICKET-2049
((ComponentTag)elem).onBeforeRender(this, markupStream);
}
// Determine if component is visible using it's authorization status
// and the isVisible property.
if (determineVisibility())
{
setFlag(FLAG_HAS_BEEN_RENDERED, true);
// Rendering is beginning
if (log.isDebugEnabled())
{
log.debug("Begin render {}", this);
}
try
{
notifyBehaviorsComponentBeforeRender();
onRender();
notifyBehaviorsComponentRendered();
// Component has been rendered
rendered();
}
catch (RuntimeException ex)
{
onException(ex);
}
if (log.isDebugEnabled())
{
log.debug("End render {}", this);
}
}
// elem is null when rendering a page
else if ((elem != null) && (elem instanceof ComponentTag))
{
if (getFlag(FLAG_PLACEHOLDER))
{
renderPlaceholderTag(((ComponentTag)elem).mutable(), getResponse());
}
}
}
/**
* Called when a runtime exception is caught during the render process
*
* @param ex
* The exception caught.
*/
private void onException(final RuntimeException ex)
{
// Call each behaviors onException() to allow the
// behavior to clean up
for (Behavior behavior : getBehaviors())
{
if (isBehaviorAccepted(behavior))
{
try
{
behavior.onException(this, ex);
}
catch (Exception ex2)
{
log.error("Error while cleaning up after exception", ex2);
}
}
}
// Re-throw the exception
throw ex;
}
/**
* Renders a placeholder tag for the component when it is invisible and
* {@link #setOutputMarkupPlaceholderTag(boolean)} has been called with <code>true</code>.
*
* @param tag
* component tag
* @param response
* response
*/
protected void renderPlaceholderTag(final ComponentTag tag, final Response response)
{
String name = Strings.isEmpty(tag.getNamespace()) ? tag.getName()
: tag.getNamespace() + ':' + tag.getName();
// prefer concatenation over String#format() for performance
response.write(
"<" + name + " id=\"" + getAjaxRegionMarkupId() +
"\" hidden=\"\" data-wicket-placeholder=\"\"></" + name + ">");
}
/**
* Returns the id of the markup region that will be updated via ajax. This can be different to
* the markup id of the component if a {@link IAjaxRegionMarkupIdProvider} behavior has been
* added.
*
* @return the markup id of the region to be updated via ajax.
*/
public final String getAjaxRegionMarkupId()
{
String markupId = null;
for (Behavior behavior : getBehaviors())
{
if (behavior instanceof IAjaxRegionMarkupIdProvider && behavior.isEnabled(this))
{
markupId = ((IAjaxRegionMarkupIdProvider)behavior).getAjaxRegionMarkupId(this);
break;
}
}
if (markupId == null)
{
if (this instanceof IAjaxRegionMarkupIdProvider)
{
markupId = ((IAjaxRegionMarkupIdProvider)this).getAjaxRegionMarkupId(this);
}
}
if (markupId == null)
{
markupId = getMarkupId();
}
return markupId;
}
/**
* THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT!
* <p>
* Renders the component at the current position in the given markup stream. The method
* onComponentTag() is called to allow the component to mutate the start tag. The method
* onComponentTagBody() is then called to permit the component to render its body.
*/
protected final void internalRenderComponent()
{
final IMarkupFragment markup = getMarkup();
if (markup == null)
{
throw new MarkupException("Markup not found. Component: " + toString());
}
final MarkupStream markupStream = new MarkupStream(markup);
// Get mutable copy of next tag
final ComponentTag openTag = markupStream.getTag();
final ComponentTag tag = openTag.mutable();
// call application-wide tag listeners
getApplication().getOnComponentTagListeners().onComponentTag(this, tag);
// Call any tag handler
onComponentTag(tag);
// If we're an openclose tag
if (!tag.isOpenClose() && !tag.isOpen())
{
// We were something other than <tag> or <tag/>
markupStream.throwMarkupException("Method renderComponent called on bad markup element: " +
tag);
}
if (tag.isOpenClose() && openTag.isOpen())
{
markupStream.throwMarkupException("You can not modify a open tag to open-close: " + tag);
}
try
{
// Render open tag
boolean renderBodyOnly = getRenderBodyOnly();
if (renderBodyOnly)
{
ExceptionSettings.NotRenderableErrorStrategy notRenderableErrorStrategy = ExceptionSettings.NotRenderableErrorStrategy.LOG_WARNING;
if (Application.exists())
{
notRenderableErrorStrategy = getApplication().getExceptionSettings().getNotRenderableErrorStrategy();
}
if (getFlag(FLAG_OUTPUT_MARKUP_ID))
{
String message = String.format("Markup id set on a component that renders its body only. " +
"Markup id: %s, component id: %s.", getMarkupId(), getId());
if (notRenderableErrorStrategy == ExceptionSettings.NotRenderableErrorStrategy.THROW_EXCEPTION)
{
throw new IllegalStateException(message);
}
log.warn(message);
}
if (getFlag(FLAG_PLACEHOLDER))
{
String message = String.format("Placeholder tag set on a component that renders its body only. " +
"Component id: %s.", getId());
if (notRenderableErrorStrategy == ExceptionSettings.NotRenderableErrorStrategy.THROW_EXCEPTION)
{
throw new IllegalStateException(message);
}
log.warn(message);
}
}
else
{
renderComponentTag(tag);
}
markupStream.next();
// Render the body only if open-body-close. Do not render if open-close.
if (tag.isOpen())
{
// Render the body. The default strategy will simply call the component's
// onComponentTagBody() implementation.
getMarkupSourcingStrategy().onComponentTagBody(this, markupStream, tag);
// Render close tag
if (openTag.isOpen())
{
renderClosingComponentTag(markupStream, tag, renderBodyOnly);
}
else if (renderBodyOnly == false)
{
if (needToRenderTag(openTag))
{
// Close the manually opened tag. And since the user might have changed the
// tag name ...
getResponse().write(tag.syntheticCloseTagString());
}
}
}
}
catch (WicketRuntimeException wre)
{
throw wre;
}
catch (RuntimeException re)
{
throw new WicketRuntimeException("Exception in rendering component: " + this, re);
}
}
/**
*
* @param openTag
* @return true, if the tag shall be rendered
*/
private boolean needToRenderTag(final ComponentTag openTag)
{
// If a open-close tag has been modified to be open-body-close then a
// synthetic close tag must be rendered.
boolean renderTag = (openTag != null && !(openTag instanceof WicketTag));
if (renderTag == false)
{
renderTag = !getApplication().getMarkupSettings().getStripWicketTags();
}
return renderTag;
}
/**
* Called to indicate that a component has been rendered. This method should only very rarely be
* called at all. Some components may render its children without calling render() on them.
* These components need to call rendered() to indicate that its child components were actually
* rendered, the framework would think they had never been rendered, and in development mode
* this would result in a runtime exception.
*/
public final void rendered()
{
Page page = findPage();
if (page != null)
{
// Tell the page that the component rendered
page.componentRendered(this);
}
else
{
log.error("Component is not connected to a Page. Cannot register the component as being rendered. Component: " +
toString());
}
}
/**
* Get the markup sourcing strategy for the component. If null,
* {@link #newMarkupSourcingStrategy()} will be called.
*
* @return Markup sourcing strategy
*/
protected final IMarkupSourcingStrategy getMarkupSourcingStrategy()
{
if (markupSourcingStrategy == null)
{
markupSourcingStrategy = newMarkupSourcingStrategy();
// If not strategy by provided, than we use a default one.
if (markupSourcingStrategy == null)
{
markupSourcingStrategy = DefaultMarkupSourcingStrategy.get();
}
}
return markupSourcingStrategy;
}
/**
* If {@link #getMarkupSourcingStrategy()} returns null, this method will be called. By default
* it returns null, which means that a default markup strategy will be attached to the
* component.
* <p>
* Please note that markup source strategies are not persisted. Instead they get re-created by
* calling this method again. That's ok since markup sourcing strategies usually do not maintain
* a state.
*
* @return Markup sourcing strategy
*/
protected IMarkupSourcingStrategy newMarkupSourcingStrategy()
{
return null;
}
/**
* THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT!
*
* Print to the web response what ever the component wants to contribute to the head section.
* Make sure that all attached behaviors are asked as well.
* <p>
* NOT intended for overriding by framework clients. Rather, use
* {@link Component#renderHead(org.apache.wicket.markup.head.IHeaderResponse)}
* </p>
*
* @param container
* The HtmlHeaderContainer
*/
public void internalRenderHead(final HtmlHeaderContainer container)
{
if (isVisibleInHierarchy() && isRenderAllowed())
{
if (log.isDebugEnabled())
{
log.debug("internalRenderHead: {}", toString(false));
}
IHeaderResponse response = container.getHeaderResponse();
// Allow component to contribute
boolean wasRendered = response.wasRendered(this);
if (wasRendered == false)
{
StringResponse markupHeaderResponse = new StringResponse();
Response oldResponse = getResponse();
RequestCycle.get().setResponse(markupHeaderResponse);
try
{
// Make sure the markup source strategy contributes to the header first
// to be backward compatible. WICKET-3761
getMarkupSourcingStrategy().renderHead(this, container);
CharSequence headerContribution = markupHeaderResponse.getBuffer();
if (Strings.isEmpty(headerContribution) == false)
{
response.render(StringHeaderItem.forString(headerContribution));
}
}
finally
{
RequestCycle.get().setResponse(oldResponse);
}
// Then let the component itself to contribute to the header
renderHead(response);
}
// Then ask all behaviors
for (Behavior behavior : getBehaviors())
{
if (isBehaviorAccepted(behavior))
{
if (response.wasRendered(behavior) == false)
{
behavior.renderHead(this, response);
List<IClusterable> pair = Arrays.asList(this, behavior);
response.markRendered(pair);
}
}
}
if (wasRendered == false)
{
response.markRendered(this);
}
}
}
/**
* Replaces this component with another. The replacing component must have the same component id
* as this component. This method serves as a shortcut to
*
* <code>this.getParent().replace(replacement)</code>
*
* and provides a better context for errors.
* <p>
* Usage: <code>component = component.replaceWith(replacement);</code>
* </p>
*
* @since 1.2.1
*
* @param replacement
* component to replace this one
* @return the component which replaced this one
*/
public Component replaceWith(Component replacement)
{
Args.notNull(replacement, "replacement");
if (!getId().equals(replacement.getId()))
{
throw new IllegalArgumentException(
"Replacement component must have the same id as the component it will replace. Replacement id [[" +
replacement.getId() + "]], replaced id [[" + getId() + "]].");
}
if (parent == null)
{
throw new IllegalStateException(
"This method can only be called on a component that has already been added to its parent.");
}
parent.replace(replacement);
return replacement;
}
/**
* @param component
* The component to compare with
* @return True if the given component's model is the same as this component's model.
*/
public final boolean sameInnermostModel(final Component component)
{
return sameInnermostModel(component.getDefaultModel());
}
/**
* @param model
* The model to compare with
* @return True if the given component's model is the same as this component's model.
*/
public final boolean sameInnermostModel(final IModel<?> model)
{
// Get the two models
IModel<?> thisModel = getDefaultModel();
// If both models are non-null they could be the same
if (thisModel != null && model != null)
{
return getInnermostModel(thisModel) == getInnermostModel(model);
}
return false;
}
/**
* Sets whether this component is enabled. Specific components may decide to implement special
* behavior that uses this property, like web form components that add a disabled='disabled'
* attribute when enabled is false. If it is not enabled, it will not be allowed to call any
* listener method on it (e.g. Link.onClick) and the model object will be protected (for the
* common use cases, not for programmer's misuse)
*
* @param enabled
* whether this component is enabled
* @return This
*/
public final Component setEnabled(final boolean enabled)
{
// Is new enabled state a change?
if (enabled != getFlag(FLAG_ENABLED))
{
// Tell the page that this component's enabled was changed
if (isVersioned())
{
final Page page = findPage();
if (page != null)
{
addStateChange();
}
}
// Change visibility
setFlag(FLAG_ENABLED, enabled);
onEnabledStateChanged();
}
return this;
}
void clearEnabledInHierarchyCache()
{
setRequestFlag(RFLAG_ENABLED_IN_HIERARCHY_SET, false);
}
void onEnabledStateChanged()
{
clearEnabledInHierarchyCache();
}
/**
* Sets whether model strings should be escaped.
*
* @param escapeMarkup
* True is model strings should be escaped
* @return This
*/
public final Component setEscapeModelStrings(final boolean escapeMarkup)
{
setFlag(FLAG_ESCAPE_MODEL_STRINGS, escapeMarkup);
return this;
}
/**
* Set markup ID, which must be String or Integer
*
* @param markupId
*/
public final void setMarkupIdImpl(Object markupId)
{
if (markupId != null && !(markupId instanceof String) && !(markupId instanceof Integer))
{
throw new IllegalArgumentException("markupId must be String or Integer");
}
setOutputMarkupId(true);
if (markupId instanceof Integer)
{
generatedMarkupId = (Integer)markupId;
setMetaData(MARKUP_ID_KEY, null);
return;
}
generatedMarkupId = -1;
setMetaData(MARKUP_ID_KEY, (String)markupId);
}
/**
* Copy markupId
*
* @param comp
*/
final void setMarkupId(Component comp)
{
Args.notNull(comp, "comp");
generatedMarkupId = comp.generatedMarkupId;
setMetaData(MARKUP_ID_KEY, comp.getMetaData(MARKUP_ID_KEY));
if (comp.getOutputMarkupId())
{
setOutputMarkupId(true);
}
}
/**
* Sets this component's markup id to a user defined value. It is up to the user to ensure this
* value is unique.
* <p>
* The recommended way is to let wicket generate the value automatically, this method is here to
* serve as an override for that value in cases where a specific id must be used.
* <p>
* If null is passed in the user defined value is cleared and markup id value will fall back on
* automatically generated value
*
* @see #getMarkupId()
*
* @param markupId
* markup id value or null to clear any previous user defined value
* @return this for chaining
*/
public Component setMarkupId(String markupId)
{
Args.notEmpty(markupId, "markupId");
// TODO check if an automatic id has already been generated or getmarkupid() called
// previously and throw an illegalstateexception because something else might be depending
// on previous id
setMarkupIdImpl(markupId);
return this;
}
/**
* Sets the metadata for this component using the given key. If the metadata object is not of
* the correct type for the metadata key, an IllegalArgumentException will be thrown. For
* information on creating MetaDataKeys, see {@link MetaDataKey}.
*
* @param <M>
* The type of the metadata
*
* @param key
* The singleton key for the metadata
* @param object
* The metadata object
* @throws IllegalArgumentException
* @see MetaDataKey
*/
@Override
public final <M extends Serializable> Component setMetaData(final MetaDataKey<M> key, final M object)
{
MetaDataEntry<?>[] old = getMetaData();
Object metaData = null;
MetaDataEntry<?>[] metaDataArray = key.set(getMetaData(), object);
if (metaDataArray != null && metaDataArray.length > 0)
{
metaData = (metaDataArray.length > 1) ? metaDataArray : metaDataArray[0];
}
int index = getFlag(FLAG_MODEL_SET) ? 1 : 0;
if (old == null && metaData != null)
{
data_insert(index, metaData);
}
else if (old != null && metaData != null)
{
data_set(index, metaData);
}
else if (old != null && metaData == null)
{
data_remove(index);
}
return this;
}
/**
* Sets the given model.
* <p>
* WARNING: DO NOT OVERRIDE THIS METHOD UNLESS YOU HAVE A VERY GOOD REASON FOR IT. OVERRIDING
* THIS MIGHT OPEN UP SECURITY LEAKS AND BREAK BACK-BUTTON SUPPORT.
* </p>
*
* @param model
* The model
* @return This
*/
public Component setDefaultModel(final IModel<?> model)
{
IModel<?> prevModel = getModelImpl();
IModel<?> wrappedModel = prevModel;
if (prevModel instanceof IWrapModel)
{
wrappedModel = ((IWrapModel<?>)prevModel).getWrappedModel();
}
// Change model
if (wrappedModel != model)
{
// Detach the old/current model
if (prevModel != null)
{
prevModel.detach();
}
modelChanging();
setModelImpl(wrap(model));
modelChanged();
// WICKET-3413 reset 'inherited model' when model is explicitely set
setFlag(FLAG_INHERITABLE_MODEL, false);
}
return this;
}
/**
* @return model
*/
IModel<?> getModelImpl()
{
if (getFlag(FLAG_MODEL_SET))
{
return (IModel<?>)data_get(0);
}
return null;
}
/**
*
* @param model
*/
void setModelImpl(IModel<?> model)
{
if (getFlag(FLAG_MODEL_SET))
{
if (model != null)
{
data_set(0, model);
}
else
{
data_remove(0);
setFlag(FLAG_MODEL_SET, false);
}
}
else
{
if (model != null)
{
data_insert(0, model);
setFlag(FLAG_MODEL_SET, true);
}
}
}
/**
* Sets the backing model object. Unlike <code>getDefaultModel().setObject(object)</code>, this
* method checks authorisation and model comparator, and invokes <code>modelChanging</code> and
* <code>modelChanged</code> if the value really changes.
*
* @param object
* The object to set
* @return This
* @throws IllegalStateException If the component has neither its own model nor any of its
* parents uses {@link IComponentInheritedModel}
*/
@SuppressWarnings("unchecked")
public final Component setDefaultModelObject(final Object object)
{
final IModel<Object> model = (IModel<Object>)getDefaultModel();
// Check whether anything can be set at all
if (model == null)
{
throw new IllegalStateException(
"Attempt to set a model object on a component without a model! " +
"Either pass an IModel to the constructor or use #setDefaultModel(new SomeModel(object)). " +
"Component: " + getPageRelativePath());
}
// Check authorization
if (!isActionAuthorized(ENABLE))
{
throw new UnauthorizedActionException(this, ENABLE);
}
// Check whether this will result in an actual change
if (!getModelComparator().compare(this, object))
{
modelChanging();
try
{
model.setObject(object);
}
catch (UnsupportedOperationException uox)
{
throw new WicketRuntimeException("You need to use writeable IModel for component " + getPageRelativePath(), uox);
}
modelChanged();
}
return this;
}
/**
* Sets whether or not component will output id attribute into the markup. id attribute will be
* set to the value returned from {@link Component#getMarkupId()}.
*
* @param output
* True if the component will output the id attribute into markup. Please note that
* the default behavior is to use the same id as the component. This means that your
* component must begin with [a-zA-Z] in order to generate a valid markup id
* according to: http://www.w3.org/TR/html401/types.html#type-name
*
* @return this for chaining
*/
public final Component setOutputMarkupId(final boolean output)
{
setFlag(FLAG_OUTPUT_MARKUP_ID, output);
return this;
}
/**
* Render a placeholder tag when the component is not visible. The tag is of form:
* &lt;componenttag hidden=""" id="markupid"/&gt;. This method will also call
* <code>setOutputMarkupId(true)</code>.
*
* This is useful, for example, in ajax situations where the component starts out invisible and
* then becomes visible through an ajax update. With a placeholder tag already in the markup you
* do not need to repaint this component's parent, instead you can repaint the component
* directly.
*
* When this method is called with parameter <code>false</code> the outputmarkupid flag is not
* reverted to false.
*
* @param outputTag
* @return this for chaining
*/
public final Component setOutputMarkupPlaceholderTag(final boolean outputTag)
{
if (outputTag != getFlag(FLAG_PLACEHOLDER))
{
if (outputTag)
{
setOutputMarkupId(true);
setFlag(FLAG_PLACEHOLDER, true);
}
else
{
setFlag(FLAG_PLACEHOLDER, false);
// I think it's better to not setOutputMarkupId to false...
// user can do it if she want
}
}
return this;
}
/**
* If false the component's tag will be printed as well as its body (which is default). If true
* only the body will be printed, but not the component's tag.
*
* @param renderTag
* If true, the component tag will not be printed
* @return This
*/
public final Component setRenderBodyOnly(final boolean renderTag)
{
setFlag(FLAG_RENDER_BODY_ONLY, renderTag);
return this;
}
/**
* Sets the page that will respond to this request
*
* @param <C>
*
* @param cls
* The response page class
* @see RequestCycle#setResponsePage(Class)
*/
public final <C extends IRequestablePage> void setResponsePage(final Class<C> cls)
{
getRequestCycle().setResponsePage(cls, (PageParameters)null);
}
/**
* Sets the page class and its parameters that will respond to this request
*
* @param <C>
*
* @param cls
* The response page class
* @param parameters
* The parameters for this bookmarkable page.
* @see RequestCycle#setResponsePage(Class, PageParameters)
*/
public final <C extends IRequestablePage> void setResponsePage(final Class<C> cls,
PageParameters parameters)
{
getRequestCycle().setResponsePage(cls, parameters);
}
/**
* Sets the page that will respond to this request
*
* @param page
* The response page
*
* @see RequestCycle#setResponsePage(org.apache.wicket.request.component.IRequestablePage)
*/
public final void setResponsePage(final IRequestablePage page)
{
getRequestCycle().setResponsePage(page);
}
/**
* @param versioned
* True to turn on versioning for this component, false to turn it off for this
* component and any children.
* @return This
*/
public Component setVersioned(boolean versioned)
{
setFlag(FLAG_VERSIONED, versioned);
return this;
}
/**
* Sets whether this component and any children are visible.
*
* @param visible
* True if this component and any children should be visible
* @return This
*/
public final Component setVisible(final boolean visible)
{
// Is new visibility state a change?
if (visible != getFlag(FLAG_VISIBLE))
{
// record component's visibility change
addStateChange();
// Change visibility
setFlag(FLAG_VISIBLE, visible);
onVisibleStateChanged();
}
return this;
}
void clearVisibleInHierarchyCache()
{
setRequestFlag(RFLAG_VISIBLE_IN_HIERARCHY_SET, false);
}
void onVisibleStateChanged()
{
clearVisibleInHierarchyCache();
}
/**
* Gets the string representation of this component.
*
* @return The path to this component
*/
@Override
public String toString()
{
return toString(false);
}
/**
* @param detailed
* True if a detailed string is desired
* @return The string
*/
public String toString(final boolean detailed)
{
try
{
final StringBuilder buffer = new StringBuilder();
buffer.append("[Component id = ").append(getId());
if (detailed)
{
final Page page = findPage();
if (page == null)
{
buffer.append(", page = <No Page>, path = ")
.append(getPath())
.append('.')
.append(Classes.simpleName(getClass()));
}
else
{
buffer.append(", page = ")
.append(Classes.name(getPage().getPageClass()))
.append(", path = ")
.append(getPageRelativePath())
.append(", type = ")
.append(Classes.name(getClass()))
.append(", isVisible = ")
.append((determineVisibility()))
.append(", isVersioned = ")
.append(isVersioned());
}
if (markup != null)
{
buffer.append(", markup = ").append(new MarkupStream(getMarkup()).toString());
}
}
buffer.append(']');
return buffer.toString();
}
catch (Exception e)
{
log.warn("Error while building toString()", e);
return String.format(
"[Component id = %s <attributes are not available because exception %s was thrown during toString()>]",
getId(), e.getClass().getName());
}
}
/**
* Returns a bookmarkable URL that references a given page class using a given set of page
* parameters. Since the URL which is returned contains all information necessary to instantiate
* and render the page, it can be stored in a user's browser as a stable bookmark.
*
* @param <C>
*
* @see RequestCycle#urlFor(Class, org.apache.wicket.request.mapper.parameter.PageParameters)
*
* @param pageClass
* Class of page
* @param parameters
* Parameters to page
* @return Bookmarkable URL to page
*/
public final <C extends Page> CharSequence urlFor(final Class<C> pageClass,
final PageParameters parameters)
{
return getRequestCycle().urlFor(pageClass, parameters);
}
/**
* Gets a URL for the listener interface on a behavior (e.g. {@link IRequestListener} on
* {@link org.apache.wicket.ajax.markup.html.navigation.paging.AjaxPagingNavigationBehavior}).
*
* @param behaviour
* The behavior that the URL should point to
* @param parameters
* The parameters that should be rendered into the urls
* @return The URL
*/
public final CharSequence urlForListener(final Behavior behaviour, final PageParameters parameters)
{
int id = getBehaviorId(behaviour);
IRequestHandler handler = createRequestHandler(parameters, id);
return getRequestCycle().urlFor(handler);
}
/**
* Create a suitable request handler depending whether the page is stateless or bookmarkable.
*/
private IRequestHandler createRequestHandler(PageParameters parameters, Integer id)
{
Page page = getPage();
PageAndComponentProvider provider = new PageAndComponentProvider(page, this, parameters);
if (page.isPageStateless()
|| (page.isBookmarkable() && page.wasCreatedBookmarkable()))
{
return new BookmarkableListenerRequestHandler(provider, id);
}
else
{
return new ListenerRequestHandler(provider, id);
}
}
/**
* Returns a URL that references the given request target.
*
* @see RequestCycle#urlFor(IRequestHandler)
*
* @param requestHandler
* the request target to reference
*
* @return a URL that references the given request target
*/
public final CharSequence urlFor(final IRequestHandler requestHandler)
{
return getRequestCycle().urlFor(requestHandler);
}
/**
* Gets a URL for this {@link IRequestListener}.
*
* @see RequestCycle#urlFor(IRequestHandler)
*
* @param parameters
* The parameters that should be rendered into the URL
* @return The URL
*/
public final CharSequence urlForListener(final PageParameters parameters)
{
IRequestHandler handler = createRequestHandler(parameters, null);
return getRequestCycle().urlFor(handler);
}
/**
* Returns a URL that references a shared resource through the provided resource reference.
*
* @see RequestCycle#urlFor(IRequestHandler)
*
* @param resourceReference
* The resource reference
* @param parameters
* parameters or {@code null} if none
* @return The url for the shared resource
*/
public final CharSequence urlFor(final ResourceReference resourceReference,
PageParameters parameters)
{
return getRequestCycle().urlFor(resourceReference, parameters);
}
/**
* Traverses all parent components of the given class in this parentClass, calling the visitor's
* visit method at each one.
*
* @param <R>
* the type of the result object
* @param parentClass
* Class
* @param visitor
* The visitor to call at each parent of the given type
* @return First non-null value returned by visitor callback
*/
public final <R, C extends MarkupContainer> R visitParents(final Class<C> parentClass,
final IVisitor<C, R> visitor)
{
return visitParents(parentClass, visitor, IVisitFilter.ANY);
}
/**
* Traverses all parent components of the given class in this parentClass, calling the visitor's
* visit method at each one.
*
* @param <R>
* the type of the result object
* @param parentClass
* the class of the parent component
* @param visitor
* The visitor to call at each parent of the given type
* @param filter
* a filter that adds an additional logic to the condition whether a parent container
* matches
* @return First non-null value returned by visitor callback
*/
@SuppressWarnings("unchecked")
public final <R, C extends MarkupContainer> R visitParents(final Class<C> parentClass,
final IVisitor<C, R> visitor, IVisitFilter filter)
{
Args.notNull(filter, "filter");
// Start here
MarkupContainer current = getParent();
Visit<R> visit = new Visit<R>();
// Walk up containment hierarchy
while (current != null)
{
// Is current an instance of this class?
if (parentClass.isInstance(current) && filter.visitObject(current))
{
visitor.component((C)current, visit);
if (visit.isStopped())
{
return visit.getResult();
}
}
// Check parent
current = current.getParent();
}
return null;
}
/**
* Registers a warning feedback message for this component.
*
* @param message
* The feedback message
*/
@Override
public final void warn(final Serializable message)
{
getFeedbackMessages().warn(this, message);
addStateChange();
}
/**
* {@link Behavior#beforeRender(Component)} Notify all behaviors that are assigned to this
* component that the component is about to be rendered.
*/
private void notifyBehaviorsComponentBeforeRender()
{
for (Behavior behavior : getBehaviors())
{
if (isBehaviorAccepted(behavior))
{
behavior.beforeRender(this);
}
}
}
/**
* {@link Behavior#afterRender(Component)} Notify all behaviors that are assigned to this
* component that the component has rendered.
*/
private void notifyBehaviorsComponentRendered()
{
// notify the behaviors that component has been rendered
for (Behavior behavior : getBehaviors())
{
if (isBehaviorAccepted(behavior))
{
behavior.afterRender(this);
}
}
}
/**
* TODO WICKET-NG rename to something more useful - like componentChanged(), this method used to
* be called with a Change object
*
* Adds state change to page.
*/
protected final void addStateChange()
{
checkHierarchyChange(this);
final Page page = findPage();
if (page != null)
{
page.componentStateChanging(this);
}
}
/**
* Checks whether the given type has the expected name.
*
* @param tag
* The tag to check
* @param name
* The expected tag name
* @throws MarkupException
* Thrown if the tag is not of the right name
*/
protected final void checkComponentTag(final ComponentTag tag, final String name)
{
if (!tag.getName().equalsIgnoreCase(name))
{
String msg = String.format("Component [%s] (path = [%s]) must be "
+ "applied to a tag of type [%s], not: %s", getId(), getPath(), name,
tag.toUserDebugString());
findMarkupStream().throwMarkupException(msg);
}
}
/**
* Checks that a given tag has a required attribute value.
*
* @param tag
* The tag
* @param key
* The attribute key
* @param values
* The required value for the attribute key
* @throws MarkupException
* Thrown if the tag does not have the required attribute value
*/
protected final void checkComponentTagAttribute(final ComponentTag tag, final String key,
final String... values)
{
if (key != null)
{
final String tagAttributeValue = tag.getAttributes().getString(key);
boolean found = false;
if (tagAttributeValue != null)
{
for (String value : values)
{
if (value.equalsIgnoreCase(tagAttributeValue))
{
found = true;
break;
}
}
}
if (found == false)
{
String msg = String.format("Component [%s] (path = [%s]) must be applied to a tag "
+ "with [%s] attribute matching any of %s, not [%s]", getId(), getPath(), key,
Arrays.toString(values), tagAttributeValue);
findMarkupStream().throwMarkupException(msg);
}
}
}
/**
* Checks whether the hierarchy may be changed at all, and throws an exception if this is not
* the case.
*
* @param component
* the component which is about to be added or removed
*/
protected void checkHierarchyChange(final Component component)
{
// Throw exception if modification is attempted during rendering
if (getRequestFlag(RFLAG_RENDERING) && !component.isAuto())
{
throw new WicketRuntimeException(
"Cannot modify component hierarchy after render phase has started (page version cant change then anymore)");
}
}
/**
* Detaches the model for this component if it is detachable.
*/
protected void detachModel()
{
IModel<?> model = getModelImpl();
if (model != null)
{
model.detach();
}
// also detach the wrapped model of a component assigned wrap (not
// inherited)
if (model instanceof IWrapModel && !getFlag(FLAG_INHERITABLE_MODEL))
{
((IWrapModel<?>)model).getWrappedModel().detach();
}
}
/**
* Suffixes an exception message with useful information about this. component.
*
* @param message
* The message
* @return The modified message
*/
protected final String exceptionMessage(final String message)
{
return message + ":\n" + toString();
}
/**
* Finds the markup stream for this component.
*
* @return The markup stream for this component. Since a Component cannot have a markup stream,
* we ask this component's parent to search for it.
*/
protected final MarkupStream findMarkupStream()
{
return new MarkupStream(getMarkup());
}
/**
* If this Component is a Page, returns self. Otherwise, searches for the nearest Page parent in
* the component hierarchy. If no Page parent can be found, {@code null} is returned.
*
* @return The Page or {@code null} if none can be found
*/
protected final Page findPage()
{
// Search for page
return (Page)(this instanceof Page ? this : findParent(Page.class));
}
/**
* Gets the subset of the currently coupled {@link Behavior}s that are of the provided type as
* an unmodifiable list. Returns an empty list if there are no behaviors coupled to this
* component.
*
* @param type
* The type or null for all
* @return The subset of the currently coupled behaviors that are of the provided type as an
* unmodifiable list
* @param <M>
* A class derived from Behavior
*/
public <M extends Behavior> List<M> getBehaviors(Class<M> type)
{
return Behaviors.getBehaviors(this, type);
}
/**
* THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT!
*
* @param flag
* The flag to test
* @return True if the flag is set
*/
protected final boolean getFlag(final int flag)
{
return (flags & flag) != 0;
}
/**
* THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT!
*
* @param flag
* The flag to test
* @return True if the flag is set
*/
protected final boolean getRequestFlag(final short flag)
{
return (requestFlags & flag) != 0;
}
/**
* Finds the innermost IModel object for an IModel that might contain nested IModel(s).
*
* @param model
* The model
* @return The innermost (most nested) model
*/
protected final IModel<?> getInnermostModel(final IModel<?> model)
{
IModel<?> nested = model;
while (nested != null && nested instanceof IWrapModel)
{
final IModel<?> next = ((IWrapModel<?>)nested).getWrappedModel();
if (nested == next)
{
throw new WicketRuntimeException("Model for " + nested + " is self-referential");
}
nested = next;
}
return nested;
}
/**
* Gets the component's current model comparator. Implementations can be used for testing the
* current value of the components model data with the new value that is given.
*
* @return the value defaultModelComparator
*/
public IModelComparator getModelComparator()
{
return defaultModelComparator;
}
/**
* Returns whether the component can be stateless. Also the component behaviors must be
* stateless, otherwise the component will be treat as stateful. In order for page to be
* stateless (and not to be stored in session), all components (and component behaviors) must be
* stateless.
*
* @return whether the component can be stateless
*/
protected boolean getStatelessHint()
{
return true;
}
/**
* Called when a null model is about to be retrieved in order to allow a subclass to provide an
* initial model.
* <p>
* By default this implementation looks components in the parent chain owning a
* {@link IComponentInheritedModel} to provide a model for this component via
* {@link IComponentInheritedModel#wrapOnInheritance(Component)}.
* <p>
* For example a {@link FormComponent} has the opportunity to instantiate a model on the fly
* using its {@code id} and the containing {@link Form}'s model, if the form holds a
* {@link CompoundPropertyModel}.
*
* @return The model
*/
protected IModel<?> initModel()
{
IModel<?> foundModel = null;
// Search parents for IComponentInheritedModel (i.e. CompoundPropertyModel)
for (Component current = getParent(); current != null; current = current.getParent())
{
// Get model
// Don't call the getModel() that could initialize many in between
// completely useless models.
// IModel model = current.getDefaultModel();
IModel<?> model = current.getModelImpl();
if (model instanceof IWrapModel && !(model instanceof IComponentInheritedModel))
{
model = ((IWrapModel<?>)model).getWrappedModel();
}
if (model instanceof IComponentInheritedModel)
{
// return the shared inherited
foundModel = ((IComponentInheritedModel<?>)model).wrapOnInheritance(this);
setFlag(FLAG_INHERITABLE_MODEL, true);
break;
}
}
// No model for this component!
return foundModel;
}
/**
* THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT CALL OR OVERRIDE.
*
* <p>
* Called anytime a model is changed via setModel or setModelObject.
* </p>
*/
protected void internalOnModelChanged()
{
}
/**
* Components are allowed to reject behavior modifiers.
*
* @param behavior
* @return False, if the component should not apply this behavior
*/
protected boolean isBehaviorAccepted(final Behavior behavior)
{
// Ignore AttributeModifiers when FLAG_IGNORE_ATTRIBUTE_MODIFIER is set
if ((behavior instanceof AttributeModifier) &&
(getFlag(FLAG_IGNORE_ATTRIBUTE_MODIFIER) != false))
{
return false;
}
return behavior.isEnabled(this);
}
/**
* If true, all attribute modifiers will be ignored
*
* @return True, if attribute modifiers are to be ignored
*/
protected final boolean isIgnoreAttributeModifier()
{
return getFlag(FLAG_IGNORE_ATTRIBUTE_MODIFIER);
}
/**
* Called immediately after a component and all its children have been rendered,
* regardless of any exception.
*/
protected void onAfterRender()
{
setRequestFlag(RFLAG_AFTER_RENDER_SUPER_CALL_VERIFIED, true);
}
/**
* Called on all visible components before any component is rendered.
* <p>
* <strong>NOTE</strong>: If you override this, you *must* call super.onBeforeRender() within
* your implementation.
*
* Because this method is responsible for cascading {@link #onBeforeRender()} call to its
* children it is strongly recommended that super call is made at the end of the override.
* </p>
*
* Changes to the component tree can be made only <strong>before</strong> calling
* super.onBeforeRender().
*
* @see org.apache.wicket.MarkupContainer#addOrReplace(Component...)
*/
protected void onBeforeRender()
{
setRequestFlag(RFLAG_PREPARED_FOR_RENDER, true);
onBeforeRenderChildren();
setRequestFlag(RFLAG_BEFORE_RENDER_SUPER_CALL_VERIFIED, true);
}
/**
* Processes the component tag.
*
* Overrides of this method most likely should call the super implementation.
*
* @param tag
* Tag to modify
*/
protected void onComponentTag(final ComponentTag tag)
{
// We can't try to get the ID from markup. This could be different than
// id returned from getMarkupId() prior first rendering the component
// (due to transparent resolvers and borders which break the 1:1
// component <-> markup relation)
if (getFlag(FLAG_OUTPUT_MARKUP_ID))
{
tag.putInternal(MARKUP_ID_ATTR_NAME, getMarkupId());
}
DebugSettings debugSettings = getApplication().getDebugSettings();
String componentPathAttributeName = debugSettings.getComponentPathAttributeName();
if (Strings.isEmpty(componentPathAttributeName) == false)
{
String path = getPageRelativePath();
path = path.replace("_", "__");
path = path.replace(':', '_');
tag.put(componentPathAttributeName, path);
}
// The markup sourcing strategy may also want to work on the tag
getMarkupSourcingStrategy().onComponentTag(this, tag);
}
/**
* Processes the body.
*
* @param markupStream
* The markup stream
* @param openTag
* The open tag for the body
*/
public void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag)
{
}
/**
* Called to allow a component to detach resources after use.
*
* Overrides of this method MUST call the super implementation, the most logical place to do
* this is the last line of the override method.
*/
protected void onDetach()
{
setRequestFlag(RFLAG_DETACHING, false);
}
/**
* Called to notify the component it is being removed from the component hierarchy
*
* Overrides of this method MUST call the super implementation, the most logical place to do
* this is the last line of the override method.
*/
protected void onRemove()
{
setRequestFlag(RFLAG_REMOVING_FROM_HIERARCHY, false);
}
/**
* Called anytime a model is changed after the change has occurred
*/
protected void onModelChanged()
{
}
/**
* Called anytime a model is changed, but before the change actually occurs
*/
protected void onModelChanging()
{
}
/**
* Implementation that renders this component.
*/
protected abstract void onRender();
/**
* Writes a simple tag out to the response stream. Any components that might be referenced by
* the tag are ignored. Also undertakes any tag attribute modifications if they have been added
* to the component.
*
* @param tag
* The tag to write
*/
protected final void renderComponentTag(ComponentTag tag)
{
if (needToRenderTag(tag))
{
// apply behaviors that are attached to the component tag.
if (tag.hasBehaviors())
{
Iterator<? extends Behavior> tagBehaviors = tag.getBehaviors();
while (tagBehaviors.hasNext())
{
final Behavior behavior = tagBehaviors.next();
if (behavior.isEnabled(this))
{
behavior.onComponentTag(this, tag);
}
behavior.detach(this);
}
}
// Apply behavior modifiers
List<? extends Behavior> behaviors = getBehaviors();
if ((behaviors != null) && !behaviors.isEmpty() && !tag.isClose() &&
(isIgnoreAttributeModifier() == false))
{
tag = tag.mutable();
for (Behavior behavior : behaviors)
{
// Components may reject some behavior components
if (isBehaviorAccepted(behavior))
{
behavior.onComponentTag(this, tag);
}
}
}
if ((tag instanceof WicketTag) && !tag.isClose() &&
!getFlag(FLAG_IGNORE_ATTRIBUTE_MODIFIER))
{
ExceptionSettings.NotRenderableErrorStrategy notRenderableErrorStrategy = ExceptionSettings.NotRenderableErrorStrategy.LOG_WARNING;
if (Application.exists())
{
notRenderableErrorStrategy = getApplication().getExceptionSettings().getNotRenderableErrorStrategy();
}
String tagName = tag.getNamespace() + ":" + tag.getName();
String componentId = getId();
if (getFlag(FLAG_OUTPUT_MARKUP_ID))
{
String message = String.format("Markup id set on a component that is usually not rendered into markup. " +
"Markup id: %s, component id: %s, component tag: %s.",
getMarkupId(), componentId, tagName);
if (notRenderableErrorStrategy == ExceptionSettings.NotRenderableErrorStrategy.THROW_EXCEPTION)
{
throw new IllegalStateException(message);
}
log.warn(message);
}
if (getFlag(FLAG_PLACEHOLDER))
{
String message = String.format(
"Placeholder tag set on a component that is usually not rendered into markup. " +
"Component id: %s, component tag: %s.", componentId, tagName);
if (notRenderableErrorStrategy == ExceptionSettings.NotRenderableErrorStrategy.THROW_EXCEPTION)
{
throw new IllegalStateException(message);
}
log.warn(message);
}
}
// Write the tag
tag.writeOutput(getResponse(), !needToRenderTag(null),
getMarkup().getMarkupResourceStream().getWicketNamespace());
}
}
/**
* Replaces the body with the given one.
*
* @param markupStream
* The markup stream to replace the tag body in
* @param tag
* The tag
* @param body
* The new markup
*/
protected final void replaceComponentTagBody(final MarkupStream markupStream,
final ComponentTag tag, final CharSequence body)
{
// The tag might have been changed from open-close to open. Hence
// we'll need what was in the markup itself
ComponentTag markupOpenTag = null;
// If tag has a body
if (tag.isOpen())
{
// Get what tag was in the markup; not what the user it might
// have changed it to.
markupOpenTag = markupStream.getPreviousTag();
// If it was an open tag in the markup as well, than ...
if (markupOpenTag.isOpen())
{
// skip any raw markup in the body
markupStream.skipRawMarkup();
}
}
if (body != null)
{
// Write the new body
getResponse().write(body);
}
// If we had an open tag (and not an openclose tag) and we found a
// close tag, we're good
if (tag.isOpen())
{
// If it was an open tag in the markup, than there must be
// a close tag as well.
if ((markupOpenTag != null) && markupOpenTag.isOpen() && !markupStream.atCloseTag())
{
// There must be a component in this discarded body
markupStream.throwMarkupException("Expected close tag for '" + markupOpenTag +
"' Possible attempt to embed component(s) '" + markupStream.get() +
"' in the body of this component which discards its body");
}
}
}
/**
* @param auto
* True to put component into auto-add mode
*/
protected final Component setAuto(final boolean auto)
{
setFlag(FLAG_AUTO, auto);
return this;
}
/**
* THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT!
*
* @param flag
* The flag to set
* @param set
* True to turn the flag on, false to turn it off
*/
protected final Component setFlag(final int flag, final boolean set)
{
if (set)
{
flags |= flag;
}
else
{
flags &= ~flag;
}
return this;
}
/**
* @param flag
* The flag to set
* @param set
* True to turn the flag on, false to turn it off
*/
final Component setRequestFlag(final short flag, final boolean set)
{
if (set)
{
requestFlags |= flag;
}
else
{
requestFlags &= ~flag;
}
return this;
}
/**
* If true, all attribute modifiers will be ignored
*
* @param ignore
* If true, all attribute modifiers will be ignored
* @return This
*/
protected final Component setIgnoreAttributeModifier(final boolean ignore)
{
setFlag(FLAG_IGNORE_ATTRIBUTE_MODIFIER, ignore);
return this;
}
/**
* @param <V>
* The model type
* @param model
* The model to wrap if need be
* @return The wrapped model
*/
protected final <V> IModel<V> wrap(final IModel<V> model)
{
if (model instanceof IComponentAssignedModel)
{
return ((IComponentAssignedModel<V>)model).wrapOnAssignment(this);
}
return model;
}
/**
* Detaches any child components
*/
void detachChildren()
{
}
/**
* Signals this components removal from hierarchy to all its children.
*/
void removeChildren()
{
}
/**
* Gets the component at the given path.
*
* @param path
* Path to component
* @return The component at the path
*/
@Override
public Component get(final String path)
{
// Path to this component is an empty path
if (path.length() == 0)
{
return this;
}
throw new IllegalArgumentException(
exceptionMessage("Component is not a container and so does not contain the path " +
path));
}
/**
* @param setRenderingFlag
* rendering flag
*/
void internalMarkRendering(boolean setRenderingFlag)
{
// WICKET-5460 no longer prepared for render
setRequestFlag(RFLAG_PREPARED_FOR_RENDER, false);
setRequestFlag(RFLAG_RENDERING, setRenderingFlag);
}
/**
* @return True if this component or any of its parents is in auto-add mode
*/
public final boolean isAuto()
{
// Search up hierarchy for FLAG_AUTO
for (Component current = this; current != null; current = current.getParent())
{
if (current.getFlag(FLAG_AUTO))
{
return true;
}
}
return false;
}
/**
*
* @return <code>true</code> if component has been prepared for render
*/
boolean isPreparedForRender()
{
return getRequestFlag(RFLAG_PREPARED_FOR_RENDER);
}
/**
* This method is here for {@link MarkupContainer}. It is broken out of
* {@link #onBeforeRender()} so we can guarantee that it executes as the last in
* onBeforeRender() chain no matter where user places the <code>super.onBeforeRender()</code>
* call.
*/
void onBeforeRenderChildren()
{
}
/**
* Renders the close tag at the current position in the markup stream.
*
* @param markupStream
* the markup stream
* @param openTag
* the tag to render
* @param renderBodyOnly
* if true, the tag will not be written to the output
*/
final void renderClosingComponentTag(final MarkupStream markupStream,
final ComponentTag openTag, final boolean renderBodyOnly)
{
// Tag should be open tag and not openclose tag
if (openTag.isOpen())
{
// If we found a close tag and it closes the open tag, we're good
if (markupStream.atCloseTag() && markupStream.getTag().closes(openTag))
{
// Render the close tag
if ((renderBodyOnly == false) && needToRenderTag(openTag))
{
getResponse().write(openTag.syntheticCloseTagString());
}
}
else if (openTag.requiresCloseTag())
{
// Missing close tag. Some tags, e.g. <p> are handled like <p/> by most browsers and
// thus will not throw an exception.
markupStream.throwMarkupException("Expected close tag for " + openTag);
}
}
}
/**
* Sets the id of this component.
*
* @param id
* The non-null id of this component
*/
private void checkId(final String id)
{
if (!(this instanceof Page))
{
if (Strings.isEmpty(id))
{
throw new WicketRuntimeException("Null or empty component ID's are not allowed.");
}
}
if ((id != null) && (id.indexOf(':') != -1 || id.indexOf('~') != -1))
{
throw new WicketRuntimeException("The component ID must not contain ':' or '~' chars.");
}
}
/**
* THIS IS A WICKET INTERNAL API. DO NOT USE IT.
*
* Sets the parent of a component. Typically what you really want is parent.add(child).
* <p/>
* Note that calling setParent() and not parent.add() will connect the child to the parent, but
* the parent will not know the child. This might not be a problem in some cases, but e.g.
* child.onDetach() will not be invoked (since the parent doesn't know it is his child).
*
* @param parent
* The parent container
*/
public final void setParent(final MarkupContainer parent)
{
if (this.parent != null && log.isDebugEnabled())
{
log.debug("Replacing parent " + this.parent + " with " + parent);
}
this.parent = parent;
}
/**
* Sets the render allowed flag.
*
* @param renderAllowed
*/
final void setRenderAllowed(boolean renderAllowed)
{
setFlag(FLAG_IS_RENDER_ALLOWED, renderAllowed);
}
/**
* Sets the render allowed flag.
*
* Visit all this page's children (overridden in MarkupContainer) to check rendering
* authorization, as appropriate. We set any result; positive or negative as a temporary boolean
* in the components, and when a authorization exception is thrown it will block the rendering
* of this page
*/
void setRenderAllowed()
{
setRenderAllowed(isActionAuthorized(RENDER));
}
/**
* Sets whether or not this component is allowed to be visible. This method is meant to be used
* by components to control visibility of other components. A call to
* {@link #setVisible(boolean)} will not always have a desired effect because that component may
* have {@link #isVisible()} overridden. Both {@link #setVisibilityAllowed(boolean)} and
* {@link #isVisibilityAllowed()} are <code>final</code> so their contract is enforced always.
*
* @param allowed
* @return <code>this</code> for chaining
*/
public final Component setVisibilityAllowed(boolean allowed)
{
if (allowed != getFlag(FLAG_VISIBILITY_ALLOWED))
{
setFlag(FLAG_VISIBILITY_ALLOWED, allowed);
onVisibleStateChanged();
}
return this;
}
/**
* Gets whether or not visibility is allowed on this component. See
* {@link #setVisibilityAllowed(boolean)} for details.
*
* @return true if this component is allowed to be visible, false otherwise.
*/
public final boolean isVisibilityAllowed()
{
return getFlag(FLAG_VISIBILITY_ALLOWED);
}
/**
* Determines whether or not a component should be visible, taking into account all the factors:
* {@link #isVisible()}, {@link #isVisibilityAllowed()}, {@link #isRenderAllowed()}
*
* @return <code>true</code> if the component should be visible, <code>false</code> otherwise
*/
public final boolean determineVisibility()
{
return isVisible() && isRenderAllowed() && isVisibilityAllowed();
}
/**
* Calculates enabled state of the component taking its hierarchy into account. A component is
* enabled iff it is itself enabled ({@link #isEnabled()} and {@link #isEnableAllowed()} both
* return <code>true</code>), and all of its parents are enabled.
*
* @return <code>true</code> if this component is enabled</code>
*/
public boolean isEnabledInHierarchy()
{
if (getRequestFlag(RFLAG_ENABLED_IN_HIERARCHY_SET))
{
return getRequestFlag(RFLAG_ENABLED_IN_HIERARCHY_VALUE);
}
final boolean state;
Component parent = getParent();
if (parent != null && !parent.isEnabledInHierarchy())
{
state = false;
}
else
{
state = isEnabled() && isEnableAllowed();
}
setRequestFlag(RFLAG_ENABLED_IN_HIERARCHY_SET, true);
setRequestFlag(RFLAG_ENABLED_IN_HIERARCHY_VALUE, state);
return state;
}
/**
* Says if the component is rendering currently.
*
* @return true if this component is rendering, false otherwise.
*/
public final boolean isRendering()
{
return getRequestFlag(RFLAG_PREPARED_FOR_RENDER) || getRequestFlag(RFLAG_RENDERING);
}
/**
* Checks whether or not an {@link IRequestListener} can be invoked on this component. Usually components
* deny these invocations if they are either invisible or disabled in hierarchy.
* <p>
* WARNING: be careful when overriding this method because it may open security holes - such as
* allowing a user to click on a link that should be disabled.
* </p>
* <p>
* Example usecase for overriding: Suppose you are building an component that displays images.
* The component generates a callback to itself using {@link IRequestListener} interface and
* uses this callback to stream image data. If such a component is placed inside a disabled
* {@code WebMarkupContainer} we still want to allow the invocation of the request listener callback
* method so that image data can be streamed. Such a component would override this method and
* return {@literal true}.
* </p>
*
* @return {@literal true} iff the listener method can be invoked on this component
*/
public boolean canCallListener()
{
return isEnabledInHierarchy() && isVisibleInHierarchy();
}
/**
* Render to the web response whatever the component wants to contribute to the head section.
*
* @param response
* Response object
*/
@Override
public void renderHead(IHeaderResponse response)
{
// noop
}
/** {@inheritDoc} */
@Override
public void onEvent(IEvent<?> event)
{
}
/** {@inheritDoc} */
@Override
public final <T> void send(IEventSink sink, Broadcast type, T payload)
{
new ComponentEventSender(this, getApplication().getFrameworkSettings()).send(sink, type,
payload);
}
/**
* Removes behavior from component
*
* @param behaviors
* behaviors to remove
*
* @return this (to allow method call chaining)
*/
public Component remove(final Behavior... behaviors)
{
for (Behavior behavior : behaviors)
{
Behaviors.remove(this, behavior);
}
return this;
}
/** {@inheritDoc} */
@Override
public final Behavior getBehaviorById(int id)
{
return Behaviors.getBehaviorById(this, id);
}
/** {@inheritDoc} */
@Override
public final int getBehaviorId(Behavior behavior)
{
if (behavior.isTemporary(this))
{
throw new IllegalArgumentException(
"Cannot get a stable id for temporary behavior " + behavior);
}
return Behaviors.getBehaviorId(this, behavior);
}
/**
* Adds a behavior modifier to the component.
*
* @param behaviors
* The behavior modifier(s) to be added
* @return this (to allow method call chaining)
*/
public Component add(final Behavior... behaviors)
{
Behaviors.add(this, behaviors);
return this;
}
/**
* Gets the currently coupled {@link Behavior}s as a unmodifiable list. Returns an empty list
* rather than null if there are no behaviors coupled to this component.
*
* @return The currently coupled behaviors as a unmodifiable list
*/
public final List<? extends Behavior> getBehaviors()
{
return getBehaviors(null);
}
@Override
public boolean canCallListenerAfterExpiry()
{
return getApplication().getPageSettings()
.getCallListenerAfterExpiry() || isStateless();
}
/**
* This method is called whenever a component is re-added to the page's component tree, if it
* had been removed at some earlier time, i.e., if it is already initialized
* (see {@link org.apache.wicket.Component#isInitialized()}).
*
* This is similar to onInitialize, but only comes after the component has been removed and
* then added again:
*
* <ul>
* <li>onInitialize is only called the very first time a component is added</li>
* <li>onReAdd is not called the first time, but every time it is re-added after having been
* removed</li>
* </ul>
*
* You can think of it as the opposite of onRemove. A component that was once removed will
* not be re-initialized but only re-added.
*
* Subclasses that override this must call super.onReAdd().
*/
protected void onReAdd()
{
setRequestFlag(RFLAG_ON_RE_ADD_SUPER_CALL_VERIFIED, true);
}
}