By definition, metadata refers to data
about the data.
The JSF metadata are declared via view
metadata facet (lives in the view or in a template client).
The JSF view metadata facet is tied up to view parameters and view actions.
- view parameters used to set in
managed beans/ preserve over views,
convert and validate the GET parameters (<f:viewParam>)
- view actions or performing business action on GET parameters (<f:viewAction> and <f:event>)
History
Now, the JSF
view is programmatically represented by the component root (UIViewRoot).
Some of the supported metadata includes locale, content type, encoding, render
kit, phase listeners, etc (all these are information needed by the current
view). Probably, you are used to see something like this:
<f:view
locale="#{fooBean.fooLocale}" encoding="UTF-8"
contentType="text/html">
Starting
with JSF 2.2 a view can support stateless feature, and this is indicated as:
<f:view transient="true">
...
</f:view>
But these
"per attribute" based metadata are pretty simple, while the goal of
supporting view parameters/actions implies complex metadata. But, <f:view>
cannot handle such complex metadata, so JSF 2.0 introduced the view metadata facet.
So, this
facet (by default, ignored in a UI component tree visit) is dedicated to the UIViewRoot
and its name is, javax_faces_metadata. Since the metadata facet can
contain UI components, it practically opens a new perspective on supporting
metadata. In addition, metadata are "decoupled" from view and
accessible on demand. The view metadata facet can be declared as any other JSF
facet, only that it lives in the <f:view> tag:
<f:view>
<f:facet
name="javax_faces_metadata">
...
</f:facet>
...
</f:view>
Beside the
"per attribute" based metadata , starting with JSF 2.0 more metadata
are supported. First, we have the view parameters that instructs JSF about how request parameters should be
handled when a view is either requested or linked (e.g. from bookmark). Beside
the view parameters, in the meta-model we have view actions. The story behind
view actions is pretty simple: view parameters cannot provide action invocation
and navigation, which implies lazy loading the data. JSF 2.0 fixes this issue
via <f:event>,
but this has several drawbacks (e.g. the pointed listener is invoke at each
request, it doesn't support navigations, etc), so starting with JSF 2.2, we
have view actions (<f:viewAction>).
Well, after
the view parameters have been added, the view metadata facet also gets a
shortcut for the above <f:facet> form. This is
<f:metadata> tag. So, now we write:
<f:view>
<f:metadata>
...
</f:metadata>
...
</f:view>
Or, even
without the optional <f:view>:
<f:metadata>
...
</f:metadata>
Code
The ViewMetadata
contract is defined in java.faces.view as an abstract class. Beside
its abstract methods, this class implements the methods for obtaining the
current view parameters/actions and a flag method that determines if the
provided UIViewRoot
has a view metadata facet with children.
Let's take a
look to the ViewMetadata#getViewParameters()
- check out the highlighted comments:
// this method return a
collection of view parameters associated with the passed root
public
static Collection<UIViewParameter> getViewParameters(UIViewRoot root) {
// the collection of view parameters
Collection<UIViewParameter> params;
// public static final String
METADATA_FACET_NAME = "javax_faces_metadata";
UIComponent metadataFacet =
root.getFacet(UIViewRoot.METADATA_FACET_NAME);
// the presence of view metadata facet present
is mandatory
if (metadataFacet == null) {
// there is no view metadata facet
present,
// so return an empty collection of view
parameters
params = Collections.emptyList();
} else {
// start collecting view parameters
params = new
ArrayList<UIViewParameter>();
// view parameters are instances of
UIViewParamter class
// and children of view metadata facet
List<UIComponent> children =
metadataFacet.getChildren();
int len = children.size();
for (int i = 0; i < len; i++) {
UIComponent c = children.get(i);
// ensure that the current children
is a view parameter
if (c instanceof UIViewParameter) {
params.add((UIViewParameter) c);
}
}
}
// return the list of collected view parameters
return params;
}
Further,
let's take a look to the ViewMetadata#getViewActions() - check out the
highlighted comments:
// this method return a
collection of view actions associated with the passed root
public
static Collection<UIViewAction> getViewActions(UIViewRoot root) {
// the collection of view actions
Collection<UIViewAction> actions;
// public static final String
METADATA_FACET_NAME = "javax_faces_metadata";
UIComponent metadataFacet =
root.getFacet(UIViewRoot.METADATA_FACET_NAME);
// the presence of view metadata facet present
is mandatory
if (metadataFacet == null) {
// there is no view metadata facet present,
// so return an empty collection of view actions
actions = Collections.emptyList();
} else {
actions = new
ArrayList<UIViewAction>();
// view actions are instances of UIViewAction
class
// and children of view metadata facet
List<UIComponent> children =
metadataFacet.getChildren();
int len = children.size();
for (int i = 0; i < len; i++) {
UIComponent c = children.get(i);
// ensure that the current children
is a view action
if (c instanceof UIViewAction) {
actions.add((UIViewAction) c);
}
}
}
// return the list of collected view actions
return actions;
}
Finally,
let's have the same thing for ViewMetadata#hasMetadata():
// this method check the view
metadata facet presence for the passed root
public
static boolean hasMetadata(UIViewRoot root) {
// "suppose" it doesn't exit
boolean result = false;
// public static final String
METADATA_FACET_NAME = "javax_faces_metadata";
UIComponent metadataFacet =
root.getFacet(UIViewRoot.METADATA_FACET_NAME);
if (null != metadataFacet) {
// return true if the facet has children
// return false otherwise
result = 0 <
metadataFacet.getChildCount();
}
return result;
}
Now, an
implementation (extension) of ViewMetadata must create the facet (createMetadataView())
and return the view ID (getViewId()) for it. Before we will talk
about the Mojarra implementation (com.sun.faces.application.view.ViewMetadataImpl)
let's point out the moment when the view metadata facet enters into scene.
For this, we
need to focus on the Restore View phase execution. This is the first phase in
JSF lifecycle, and when the current request is not postback, JSF will create
the view metadata facet via ViewMetadata#createMetadataView(). So, the
view metadata facet is created only once per view, at initial request! This is
not re-created on postbacks! The relevant code is:
// Mojarra 2.2.9,
com.sun.faces.lifecycle.RestoreViewPhase#execute() snippet of code
public void
execute(FacesContext facesContext) throws FacesException {
...
UIViewRoot viewRoot =
facesContext.getViewRoot();
...
boolean isPostBack =
(facesContext.isPostback() && !isErrorPage(facesContext));
if (isPostBack) {
...
// this should be
an initial request only (not postback)
} else {
...
ViewMetadata metadata = null;
// vdl is ViewDeclarationLanguage instance
if (vdl != null) {
// instantiate the ViewMetadataImpl
with the current view ID
// behind vdl.getViewMetadata() we
have:
/*
@Override
public ViewMetadata
getViewMetadata(FacesContext context, String viewId) {
Util.notNull("context",
context);
Util.notNull("viewId",
viewId);
return new
ViewMetadataImpl(viewId);
}
*/
metadata =
vdl.getViewMetadata(facesContext, viewId);
if (metadata != null) {
// create the ViewRoot; this will
have, at most
// the UIViewRoot and its metadata
facet
viewRoot =
metadata.createMetadataView(facesContext);
// if there is no metadata go to
render response;
// so, if there is a metadata JSF
"forces" the execution of all JSF
// phases at the initial request
if
(!ViewMetadata.hasMetadata(viewRoot)) {
facesContext.renderResponse();
}
}
}
}
...
}
So, now you
know when the view metadata facet is created. The creation take place in ViewMetadataImpl#createMetadataView(),
via the javax.faces.view.facelets.Facelet:
// Mojarra 2.2.9,
ViewMetadataImpl#createMetadataView() snippet of code
import
javax.faces.view.facelets.Facelet;
...
public class
ViewMetadataImpl extends ViewMetadata {
...
@Override
public UIViewRoot
createMetadataView(FacesContext context) {
...
Facelet f = faceletFactory.getMetadataFacelet(context,
result.getViewId());
f.apply(context, result);
...
}
}
Now, you
should easily understand how the below code for validating view parameters
works:
<h:body>
#{facesContext.validationFailed ?
(vdl =
facesContext.application.viewHandler.getViewDeclarationLanguage(facesContext,
view.viewId);
viewMetadata =
vdl.getViewMetadata(facesContext, view.viewId);
allViewParams =
viewMetadata.getViewParameters(view);
allViewParams.size() gt 0 ? (allViewParams.stream().forEach((t)
->
(t.valid ? '':
(facesContext.externalContext.responseCommitted ? '' :
facesContext.externalContext.responseSendError(400,"One
of your view parameters is invalid !");
facesContext.responseComplete())))):''):''}
<h:outputText value="All view
parameters are valid!"/>
</h:body>
So, the main
advantages of view metadata facet are:
- we can use
complex metadata as view parameters/actions
- metadata
declarations can be shared across multiple views
- metadata
lives in a view or in a template client
- can be
easily processed
Metadata is
just a way of configuring the view parameters and actions that should take
place before the view is rendered without user interaction. Think to a view
parameter which is basically a UIInput, only that it doesn't render a <input
type="text" value="..."/> and it is submitted
without the need for the user to press a button. In a rough manner, you can
think that the content of <f:metadata> is auto-submitted.
Normally, an user cannot submit a form until the view is ready. So, <f:metadata>
is perfect for initializing the current view (and executing actions) based on
information provided from previous views. Think that the <f:metadata>
gives to JSF the current view's metadata. Since this tag is about current view
metadata it doesn't participate in XHTML templates (the page author must ensure
that the <f:metadata>
element does not appear on a template or included page; it can be in a template
client) and it is direct child of <f:view>.
Niciun comentariu :
Trimiteți un comentariu