jakarta.faces.view.ViewDeclarationLanguage Maven / Gradle / Ivy
/*
* Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.faces.view;
import static jakarta.faces.application.ResourceVisitOption.TOP_LEVEL_VIEWS_ONLY;
import java.beans.BeanInfo;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import jakarta.faces.application.Resource;
import jakarta.faces.application.ViewVisitOption;
import jakarta.faces.component.UIComponent;
import jakarta.faces.component.UIViewRoot;
import jakarta.faces.context.FacesContext;
/**
*
* The contract that a view
* declaration language must implement to interact with the Jakarta Server Faces runtime. An implementation of this
* class must be thread-safe.
*
*
*
*
*
* Instances of this class are application scoped and must be obtained from the {@link ViewDeclarationLanguageFactory}.
*
*
*
*
* @since 2.0
*
*/
public abstract class ViewDeclarationLanguage {
/**
*
* Identifier for the Jakarta Server Pages view declaration language.
*
*
* @since 2.1
*/
public final static String JSP_VIEW_DECLARATION_LANGUAGE_ID = "java.faces.JSP";
/**
*
* Identifier for the Facelets view declaration language.
*
*
* @since 2.1
*/
public final static String FACELETS_VIEW_DECLARATION_LANGUAGE_ID = "java.faces.Facelets";
/**
*
* Restore a UIViewRoot
from a previously created view. See section JSF.7.7.2 for the specification of the
* default implementation.
*
*
* @param context the FacesContext
for this request.
* @param viewId the identifier for a previously rendered view.
*
* @throws NullPointerException if any of the arguments are null
*
* @return the restored view
*/
public abstract UIViewRoot restoreView(FacesContext context, String viewId);
/**
*
* Return a reference to the view metadata for the view represented by the argument viewId
, or
* null
if the metadata cannot be found. See section JSF.7.7.2 for the specification of the default
* implementation. Facelets for Jakarta Server Faces 2 implementation must return non-null
. Jakarta Server
* Pages implementations must return null
.
*
*
* @param context The FacesContext
for this request.
* @param viewId the view id from which to extract the metadata
* @since 2.0
*
* @throws NullPointerException if any of the arguments are null
.
*
* @throws jakarta.faces.FacesException if there is an error in obtaining the metadata
*
* @return the view metadata
*/
public abstract ViewMetadata getViewMetadata(FacesContext context, String viewId);
/**
*
* Create a UIViewRoot
from the VDL contained in the artifact
* referenced by the argument viewId
. See section JSF.7.7.2 for the
* specification of the default implementation.
*
*
* @param context the FacesContext
for this request.
* @param viewId the identifier of an artifact that contains the VDL syntax that describes this view.
*
* @throws NullPointerException if any of the arguments are null
*
* @since 2.0
*
* @return the newly created view root
*/
public abstract UIViewRoot createView(FacesContext context, String viewId);
/**
*
* Take any actions specific to this VDL implementation to cause the argument
* UIViewRoot
which must have been created via a call to {@link #createView}, to be populated with
* children.
*
*
*
*
*
* The Facelets implementation must insure that markup comprising the view must be executed, with the
* {@link jakarta.faces.component.UIComponent} instances in the view being encountered in the same depth-first order as
* in other lifecycle methods defined on UIComponent
, and added to the view (but not rendered) during the
* traversal. The runtime must guarantee that the view must be fully populated before any of the following happen.
*
*
*
* -
*
* The {@link jakarta.faces.event.PhaseListener#afterPhase} method of any PhaseListener
s attached to the
* application is called
*
*
* -
*
* The {@link jakarta.faces.component.UIViewRoot} phase listener installed via
* {@link jakarta.faces.component.UIViewRoot#setAfterPhaseListener} or
* {@link jakarta.faces.component.UIViewRoot#addPhaseListener} are called.
*
*
*
*
*
* If the root
is already populated with children, the view must still be re-built, but care must be taken
* to ensure that the existing components are correctly paired up with their VDL counterparts in the VDL page. Also, any
* system events that would normally be generated during the adding or removing of components from the view must be
* temporarily disabled during the creation of the view and then re-enabled when the view has been built.
*
*
*
* @param context the FacesContext
for this request
* @param root the UIViewRoot
to populate with children using techniques specific to this VDL
* implementation.
*
* @throws IOException if view cannot be built for any reason
*/
public abstract void buildView(FacesContext context, UIViewRoot root) throws IOException;
/**
*
* Render a view rooted at argumentview
. See section JSF.7.7.2 for the specification of the default
* implementation.
*
*
* @param context the FacesContext
for this request.
* @param view the UIViewRoot
from an early call to {@link #createView} or {@link #restoreView}.
*
* @throws NullPointerException if any of the arguments are null
*
* @throws IOException if the view cannot be rendered for any reason
*/
public abstract void renderView(FacesContext context, UIViewRoot view) throws IOException;
/**
*
* Return a reference to the component metadata for the composite component represented by the argument
* componentResource
, or null
if the metadata cannot be found. See section JSF.7.7.2 for the
* specification of the default implementation. Jakarta Server Pages implementations must throw
* UnsupportedOperationException
.
*
*
* @param context The FacesContext
for this request.
* @param componentResource The Resource
that represents the component.
* @since 2.0
*
* @throws NullPointerException if any of the arguments are null
.
*
* @throws jakarta.faces.FacesException if there is an error in obtaining the metadata
*
* @throws UnsupportedOperationException if this is a Jakarta Server Pages VDL implementation.
*
* @return the component metadata
*/
public abstract BeanInfo getComponentMetadata(FacesContext context, Resource componentResource);
/**
*
* Take implementation specific action to discover a Resource
given the argument
* componentResource
. See section JSF.7.7.2 for the specification of the default implementation. Jakarta
* Server Pages implementations must throw UnsupportedOperationException
.
*
*
* @param context The FacesContext
for this request.
* @param componentResource The Resource
that represents the component.
* @since 2.0
*
* @throws NullPointerException if any of the arguments are null
.
*
* @throws jakarta.faces.FacesException if there is an error in obtaining the script component resource
* @throws UnsupportedOperationException if this is a Jakarta Server Pages VDL implementation.
*
* @return the {@link Resource} corresponding to the argument {@code
* componentResource}
*/
public abstract Resource getScriptComponentResource(FacesContext context, Resource componentResource);
/**
*
* Create a component given a {@link ViewDeclarationLanguage} specific tag library URI and tag name. The runtime must
* support this method operating for the Facelets VDL. Other kinds of {@code ViewDeclarationLanguage} may be supported
* but are not required to be supported. For backward compatibility with decorated {@code ViewDeclrationLanguage}
* implementations that do not override this method, a default implementation is provided that returns {@code null}.
* However, any implementation that is compliant with the version of the specification in which this method was
* introduced must implement this method.
*
*
* @param context the {@link FacesContext} for this request
* @param taglibURI the fully qualified tag library URI that contains the component
* @param tagName the name of the tag within that library that exposes the component
* @param attributes any name=value pairs that would otherwise have been given on the markup that would cause the
* creation of this component or {@code null} if no attributes need be given.
*
* @throws NullPointerException if {@code context}, {@code taglibURI}, or {@code tagName} are {@code null}
*
* @since 2.2
*
* @return the newly created component
*/
public UIComponent createComponent(FacesContext context, String taglibURI, String tagName, Map attributes) {
return null;
}
/**
*
* Assuming the component metadata for argument
* topLevelComponent
has been made available by an earlier call to
* {@link ViewDeclarationLanguage#getComponentMetadata}, leverage the component metadata for the purpose of re-targeting
* attached objects from the top level composite component to the individual {@link AttachedObjectTarget} instances
* inside the composite component. This method must be called by the {@link ViewDeclarationLanguage} implementation when
* creating the UIComponent
tree when a composite component usage is encountered.
*
*
*
*
*
* An algorithm semantically equivalent to the following must be implemented.
*
*
*
*
* -
*
* Obtain the metadata for the composite component. Currently this entails getting the value of the
* {@link UIComponent#BEANINFO_KEY} component attribute, which will be an instance of BeanInfo
. If the
* metadata cannot be found, log an error message and return.
*
*
*
* -
*
* Get the BeanDescriptor
from the BeanInfo
.
*
*
*
* -
*
* Get the value of the {@link AttachedObjectTarget#ATTACHED_OBJECT_TARGETS_KEY} from the BeanDescriptor
's
* getValue()
method. This will be a List<{@link
*AttachedObjectTarget}>
. Let this be targetList.
*
*
*
* -
*
* For each curHandler entry in the argument handlers
*
*
*
*
* -
*
* Let forAttributeValue be the return from {@link AttachedObjectHandler#getFor}.
*
*
*
* -
*
* For each curTarget entry in targetList, the first of the following items that causes a match will
* take this action:
*
*
*
* For each UIComponent
in the list returned from curTarget.getTargets(), call
* curHandler.applyAttachedObject(), passing the
* FacesContext
and the UIComponent
.
*
*
*
* and cause this inner loop to terminate.
*
*
*
*
* -
*
* If curHandler is an instance of {@link ActionSource2AttachedObjectHandler} and curTarget is an
* instance of {@link ActionSource2AttachedObjectTarget}, and curTarget.getName() is equal to
* curTargetName, consider it a match.
*
*
*
* -
*
* If curHandler is an instance of {@link EditableValueHolderAttachedObjectHandler} and curTarget is
* an instance of {@link EditableValueHolderAttachedObjectTarget}, and
* curTarget.getName() is equal to curTargetName, consider it a match.
*
*
*
* -
*
* If curHandler is an instance of {@link ValueHolderAttachedObjectHandler} and curTarget is an
* instance of {@link ValueHolderAttachedObjectTarget}, and curTarget.getName() is equal to
* curTargetName, consider it a match.
*
*
*
* -
*
* If curHandler is an instance of {@link BehaviorHolderAttachedObjectHandler} and curTarget is an
* instance of {@link BehaviorHolderAttachedObjectTarget}, and either of the following conditions are true,
*
*
*
*
* - curHandler.getEventName() is not
null
and is equal to curTargetName.
*
* - curHandler.getEventName() is
null
and curTarget.isDefaultEvent() is
* true
.
*
*
*
*
* consider it a match.
*
*
*
*
*
*
*
*
*
*
* The implementation must support retargeting attached objects from the top level compsite component to targets that
* are composite and non-composite components.
*
*
*
* An implementation is provided that will throw UnsupportedOperationException
. A Faces implementation
* compliant with version 2.0 and beyond of the specification must override this method.
*
*
*
*
* @param context the FacesContext for this request.
*
* @param topLevelComponent The UIComponent in the view to which the attached objects must be attached. This UIComponent
* must have its component metadata already associated and available from via the JavaBeans API.
*
* @param handlers the tag handlers for the attached objects
*
* @throws NullPointerException if any of the arguments are null
.
*
* @since 2.0
*/
public void retargetAttachedObjects(FacesContext context, UIComponent topLevelComponent, List handlers) {
// no-op
}
/**
*
* Assuming the component metadata for argument topLevelComponent
has been made available by an earlier
* call to {@link ViewDeclarationLanguage#getComponentMetadata}, leverage the component metadata for the purpose of
* re-targeting any method expressions from the top level component to the appropriate inner component. For each
* attribute that is a MethodExpression
(as indicated by the presence of a "method-signature
"
* attribute and the absence of a "type
" attribute), the following action must be taken:
*
*
*
*
*
*
* -
*
* Get the value of the targets attribute. If the value is a ValueExpression
evaluate it. If there
* is no targets attribute, let the name of the metadata element be the evaluated value of the targets
* attribute.
*
*
*
* -
*
* Interpret targets as a space (not tab) separated list of ids. For each entry in the list:
*
*
*
*
* -
*
* Find the inner component of the topLevelComponent with the id equal to the current list entry. For
* discussion, this component is called target. If not found, log and error and continue to the next attribute.
*
*
*
* -
*
* For discussion the declared name of the attribute is called name.
*
*
*
* -
*
* In the attributes map of the topLevelComponent, look up the entry under the key name. Assume the
* result is a ValueExpression
. For discussion, this is attributeValueExpression. If not found,
* log an error and continue to the next attribute.
*
*
*
* -
*
* If name is equal to the string "action", or "actionListener" without the quotes, assume target is
* an {@link jakarta.faces.component.ActionSource2}.
*
*
*
* -
*
* If name is equal to the string "validator", or "valueChangeListener" without the quotes, assume
* target is an {@link jakarta.faces.component.EditableValueHolder}.
*
*
*
* -
*
* Call getExpressionString()
on the attributeValueExpression and use that string to create a
* MethodExpression
of the appropriate signature for name.
*
*
*
* -
*
* If name is not equal to any of the previously listed strings, call getExpressionString()
on the
* attributeValueExpression and use that string to create a MethodExpression
where the signature
* is created based on the value of the "method-signature
" attribute of the
* <composite:attribute />
tag.
*
*
*
* -
*
* Let the resultant MethodExpression
be called attributeMethodExpression for discussion.
*
*
*
* -
*
* If name is equal to the string "action" without the quotes, call
* {@link jakarta.faces.component.ActionSource2#setActionExpression} on target, passing
* attributeMethodExpression.
*
*
*
* -
*
* If name is equal to the string "actionListener" without the quotes, call
* {@link jakarta.faces.component.ActionSource#addActionListener} on target, passing
* attributeMethodExpression wrapped in a {@link jakarta.faces.event.MethodExpressionActionListener}.
*
*
*
* -
*
* If name is equal to the string "validator" without the quotes, call
* {@link jakarta.faces.component.EditableValueHolder#addValidator} on target, passing
* attributeMethodExpression wrapped in a {@link jakarta.faces.validator.MethodExpressionValidator}.
*
*
*
* -
*
* If name is equal to the string "valueChangeListener" without the quotes, call
* {@link jakarta.faces.component.EditableValueHolder#addValueChangeListener} on target, passing
* attributeMethodExpression wrapped in a {@link jakarta.faces.event.MethodExpressionValueChangeListener}.
*
*
*
* -
*
* Otherwise, assume that the MethodExpression
should be placed in the components attribute set. The runtme
* must create the MethodExpression
instance based on the value of the "method-signature
"
* attribute.
*
*
*
*
*
*
*
*
*
* An implementation is provided that will throw UnsupportedOperationException
. A Faces implementation
* compliant with version 2.0 and beyond of the specification must override this method.
*
*
*
*
* @param context the FacesContext for this request.
*
* @param topLevelComponent The UIComponent in the view to which the attached objects must be attached. This UIComponent
* must have its component metadata already associated and available from via the JavaBeans API.
*
* @throws NullPointerException if context
or topLevelComponent
is null
.
*
* @since 2.0
*/
public void retargetMethodExpressions(FacesContext context, UIComponent topLevelComponent) {
// no-op
}
/**
*
* Return the list of resource library contracts that will be made available for use in the view specified by the
* argument {@code viewId}. If no match is found, return an empty list. See section JSF.7.7.2 for the specification of
* the default implementation. For backward compatibility with prior implementations, an implementation is provided that
* returns {@code null}, but any implementation compliant with the version of the specification in which this method was
* introduced must implement it as specified in JSF.7.7.2.
*
*
* @param context the {@code FacesContext} for this request
* @param viewId the view id for which the applicable resource library contracts should be calculated.
*
* @since 2.2
*
* @return the calculated list of resource library contract names
*/
public List calculateResourceLibraryContracts(FacesContext context, String viewId) {
return null;
}
/**
*
* For implementations that want to control the implementation of state saving and restoring, the
* {@link StateManagementStrategy} allows them to do so. Returning null
indicates that the implementation
* wishes the runtime to handle the state saving and restoring. Implementations that provide the VDL for Facelets for
* Jakarta Server Faces 2.0 and later must return non-null
from this method.
*
*
* @param context the {@code FacesContext} for the current request.
*
* @param viewId the view id.
*
* @return the strategy as specified above
*
* @since 2.0
*/
public abstract StateManagementStrategy getStateManagementStrategy(FacesContext context, String viewId);
/**
*
* Tests whether a physical resource corresponding to the specified viewId
* exists.
*
*
*
* The default implementation uses {@link jakarta.faces.application.ResourceHandler#createViewResource} to locate the
* physical resource.
*
*
* @param context The FacesContext
for this request.
* @param viewId the view id to test
*
* @return the result as specified above
*
* @since 2.1
*/
public boolean viewExists(FacesContext context, String viewId) {
return context.getApplication().getResourceHandler().createViewResource(context, viewId) != null;
}
/**
*
* Return a {@code Stream} possibly lazily populated by walking the view tree rooted at a given initial path. The view
* tree is traversed breadth-first, the elements in the stream are logical view ids.
*
*
*
* This method works as if invoking it were equivalent to evaluating the expression:
*
*
* getViewResources(facesContext, start, Integer.MAX_VALUE, options)
*
*
*
Put differently, it visits all levels of the resource tree.
*
* @param facesContext The {@link FacesContext} for this request.
* @param path The initial path from which to start looking for views
* @param options The options to influence the traversal. See {@link ViewVisitOption} for details on those.
*
* @return the {@link Stream} of view ids
*
* @since 2.3
*/
public Stream getViews(FacesContext facesContext, String path, ViewVisitOption... options) {
return facesContext.getApplication().getResourceHandler().getViewResources(facesContext, path, TOP_LEVEL_VIEWS_ONLY);
}
/**
*
* Return a {@code Stream} possibly lazily populated by walking the view tree rooted at a given initial path. The view
* tree is traversed breadth-first, the elements in the stream are logical view ids.
*
*
*
* The {@code maxDepth} parameter is the maximum depth of directory levels to visit beyond the initial path,
* which is always visited. The value is relative to the root ({@code /}), not to the given initial path. E.g. given
* {@code maxDepth} = {@code 3} and initial path {@code /foo/}, visiting will proceed up to {@code /foo/bar/}, where
* {@code /} counts as depth {@code 1}, {@code /foo/} as depth {@code 2} and {@code /foo/bar/} as depth {@code 3}. A
* value lower or equal to the depth of the initial path means that only the initial path is visited. A value of
* {@link Integer#MAX_VALUE MAX_VALUE} may be used to indicate that all levels should be visited.
*
* @param facesContext The {@link FacesContext} for this request.
* @param path The initial path from which to start looking for views
* @param maxDepth The absolute maximum depth of nested directories to visit counted from the root ({@code /}).
* @param options The options to influence the traversal. See {@link ViewVisitOption} for details on those.
*
* @return the {@link Stream} of view ids
*
* @since 2.3
*/
public Stream getViews(FacesContext facesContext, String path, int maxDepth, ViewVisitOption... options) {
return facesContext.getApplication().getResourceHandler().getViewResources(facesContext, path, maxDepth, TOP_LEVEL_VIEWS_ONLY);
}
/**
*
* Returns a non-null String that can be used to identify this view declaration language.
*
*
*
* The default implementation returns the fully qualified class name of the view declaration language implementation.
* Subclasses may override to provide a more meaningful id.
*
*
* @return the id of this view declaration language
*
* @since 2.1
*/
public String getId() {
return getClass().getName();
}
}