blob: b49bd95b873c301ae8bf224bf1f70c19956a6f13 [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.myfaces.view.facelets.tag.jsf;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.faces.FacesException;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UIPanel;
import javax.faces.component.UIViewRoot;
import javax.faces.component.UniqueIdVendor;
import javax.faces.context.FacesContext;
import javax.faces.view.facelets.FaceletContext;
import javax.faces.view.facelets.TagAttribute;
import javax.faces.view.facelets.TagAttributeException;
import org.apache.myfaces.config.MyfacesConfig;
import org.apache.myfaces.view.facelets.ComponentState;
import org.apache.myfaces.view.facelets.DefaultFaceletsStateManagementStrategy;
import org.apache.myfaces.view.facelets.FaceletCompositionContext;
import org.apache.myfaces.view.facelets.FaceletViewDeclarationLanguage;
import org.apache.myfaces.view.facelets.FaceletViewDeclarationLanguageBase;
/**
*
* @author Jacob Hookom
* @version $Id$
*/
public final class ComponentSupport
{
private static final Logger LOG = Logger.getLogger(ComponentSupport.class.getName());
private static final Method SET_CACHED_FACES_CONTEXT;
static
{
Method method = null;
try
{
// The trick here is define this method in UIComponent as a class scoped method
// and get it from there. Even if the real implementation is in UIComponentBase,
// the jvm will always call the right one and in case of a instance not extending
// from UIComponentBase it will call a dummy method.
method = UIComponent.class.getDeclaredMethod("setCachedFacesContext", FacesContext.class);
method.setAccessible(true);
}
catch (NoSuchMethodException | SecurityException ex)
{
LOG.log(Level.SEVERE, ex.getMessage(), ex);
method = null;
}
SET_CACHED_FACES_CONTEXT = method;
}
public final static String MARK_CREATED = "oam.vf.MARK_ID";
/**
* The UIPanel components, which are dynamically generated to serve as a container for
* facets with multiple non panel children, are marked with this attribute.
* This constant is duplicate in javax.faces.webapp.UIComponentClassicTagBase
*/
public final static String FACET_CREATED_UIPANEL_MARKER = "oam.vf.createdUIPanel";
/**
* Special myfaces core marker to indicate the component is handled by a facelet tag handler,
* so its creation is not handled by user programatically and PSS remove listener should
* not register it when a remove happens.
*/
public final static String COMPONENT_ADDED_BY_HANDLER_MARKER = "oam.vf.addedByHandler";
/**
* The key under the facelet state map is stored
*/
public final static String FACELET_STATE_INSTANCE = "oam.FACELET_STATE_INSTANCE";
/**
* A lighter-weight version of UIComponent's findChild.
*
* @param parent
* parent to start searching from
* @param id
* to match to
* @return UIComponent found or null
*/
public static UIComponent findChild(UIComponent parent, String id)
{
int childCount = parent.getChildCount();
if (childCount > 0)
{
for (int i = 0; i < childCount; i++)
{
UIComponent child = parent.getChildren().get(i);
if (id.equals(child.getId()))
{
return child;
}
}
}
return null;
}
public static String findFacetNameByComponentInstance(UIComponent parent, UIComponent instance)
{
if (parent.getFacetCount() > 0)
{
for (Map.Entry<String, UIComponent> entry : parent.getFacets().entrySet())
{
UIComponent facet = entry.getValue();
// check if this is a dynamically generated UIPanel
if (Boolean.TRUE.equals(facet.getAttributes().get(FACET_CREATED_UIPANEL_MARKER)))
{
// only check the children and facets of the panel
if (facet.getChildCount() > 0)
{
for (int i = 0, childCount = facet.getChildCount(); i < childCount; i ++)
{
UIComponent child = facet.getChildren().get(i);
if (instance.equals(child))
{
return entry.getKey();
}
}
}
if (facet.getFacetCount() > 0)
{
Iterator<UIComponent> itr2 = facet.getFacets().values().iterator();
while (itr2.hasNext())
{
UIComponent child = itr2.next();
if (instance.equals(child))
{
return entry.getKey();
}
}
}
}
else if (instance.equals(facet))
{
return entry.getKey();
}
}
return null;
}
return null;
}
public static UIComponent findChildInFacetByTagId(UIComponent parent, String id, String facetName)
{
if (parent.getFacetCount() > 0)
{
UIComponent facet = parent.getFacet(facetName);
if (facet != null)
{
// check if this is a dynamically generated UIPanel
if (Boolean.TRUE.equals(facet.getAttributes().get(FACET_CREATED_UIPANEL_MARKER)))
{
// only check the children and facets of the panel
if (facet.getChildCount() > 0)
{
for (int i = 0, childCount = facet.getChildCount(); i < childCount; i ++)
{
UIComponent child = facet.getChildren().get(i);
if (id.equals(child.getAttributes().get(MARK_CREATED)))
{
return child;
}
}
}
if (facet.getFacetCount() > 0)
{
Iterator<UIComponent> itr2 = facet.getFacets().values().iterator();
while (itr2.hasNext())
{
UIComponent child = itr2.next();
if (id.equals(child.getAttributes().get(MARK_CREATED)))
{
return child;
}
}
}
}
else if (id.equals(facet.getAttributes().get(MARK_CREATED)))
{
return facet;
}
}
}
return null;
}
public static UIComponent findChildInChildrenByTagId(UIComponent parent, String id)
{
if (parent.getChildCount() > 0)
{
for (int i = 0, childCount = parent.getChildCount(); i < childCount; i ++)
{
UIComponent child = parent.getChildren().get(i);
if (id.equals(child.getAttributes().get(MARK_CREATED)))
{
return child;
}
}
}
return null;
}
/**
* By TagId, find Child
*
* @param parent
* @param id
* @return
*/
public static UIComponent findChildByTagId(UIComponent parent, String id)
{
Iterator<UIComponent> itr = null;
if (parent.getChildCount() > 0)
{
for (int i = 0, childCount = parent.getChildCount(); i < childCount; i ++)
{
UIComponent child = parent.getChildren().get(i);
if (id.equals(child.getAttributes().get(MARK_CREATED)))
{
return child;
}
}
}
if (parent.getFacetCount() > 0)
{
itr = parent.getFacets().values().iterator();
while (itr.hasNext())
{
UIComponent facet = itr.next();
// check if this is a dynamically generated UIPanel
if (Boolean.TRUE.equals(facet.getAttributes().get(FACET_CREATED_UIPANEL_MARKER)))
{
// only check the children and facets of the panel
if (facet.getChildCount() > 0)
{
for (int i = 0, childCount = facet.getChildCount(); i < childCount; i ++)
{
UIComponent child = facet.getChildren().get(i);
if (id.equals(child.getAttributes().get(MARK_CREATED)))
{
return child;
}
}
}
if (facet.getFacetCount() > 0)
{
Iterator<UIComponent> itr2 = facet.getFacets().values().iterator();
while (itr2.hasNext())
{
UIComponent child = itr2.next();
if (id.equals(child.getAttributes().get(MARK_CREATED)))
{
return child;
}
}
}
}
else if (id.equals(facet.getAttributes().get(MARK_CREATED)))
{
return facet;
}
}
}
return null;
}
public static String findChildInFacetsByTagId(UIComponent parent, String id)
{
Iterator<Map.Entry<String, UIComponent>> itr = null;
if (parent.getFacetCount() > 0)
{
itr = parent.getFacets().entrySet().iterator();
while (itr.hasNext())
{
Map.Entry<String, UIComponent> entry = itr.next();
UIComponent facet = entry.getValue();
// check if this is a dynamically generated UIPanel
if (Boolean.TRUE.equals(facet.getAttributes().get(FACET_CREATED_UIPANEL_MARKER)))
{
// only check the children and facets of the panel
if (facet.getChildCount() > 0)
{
for (int i = 0, childCount = facet.getChildCount(); i < childCount; i ++)
{
UIComponent child = facet.getChildren().get(i);
if (id.equals(child.getAttributes().get(MARK_CREATED)))
{
return entry.getKey();
}
}
}
if (facet.getFacetCount() > 0)
{
Iterator<UIComponent> itr2 = facet.getFacets().values().iterator();
while (itr2.hasNext())
{
UIComponent child = itr2.next();
if (id.equals(child.getAttributes().get(MARK_CREATED)))
{
return entry.getKey();
}
}
}
}
else if (id.equals(facet.getAttributes().get(MARK_CREATED)))
{
return entry.getKey();
}
}
}
return null;
}
/**
* According to JSF 1.2 tag specs, this helper method will use the TagAttribute passed in determining the Locale
* intended.
*
* @param ctx
* FaceletContext to evaluate from
* @param attr
* TagAttribute representing a Locale
* @return Locale found
* @throws TagAttributeException
* if the Locale cannot be determined
*/
public static Locale getLocale(FaceletContext ctx, TagAttribute attr) throws TagAttributeException
{
Object obj = attr.getObject(ctx);
if (obj instanceof Locale)
{
return (Locale) obj;
}
if (obj instanceof String)
{
String s = (String) obj;
if (s.length() == 2)
{
return new Locale(s);
}
if (s.length() == 5)
{
return new Locale(s.substring(0, 2), s.substring(3, 5).toUpperCase());
}
if (s.length() >= 7)
{
return new Locale(s.substring(0, 2), s.substring(3, 5).toUpperCase(), s.substring(6, s.length()));
}
throw new TagAttributeException(attr, "Invalid Locale Specified: " + s);
}
else
{
throw new TagAttributeException(attr, "Attribute did not evaluate to a String or Locale: " + obj);
}
}
/**
* Tries to walk up the parent to find the UIViewRoot, if not found, then go to FaceletContext's FacesContext for
* the view root.
*
* @param ctx
* FaceletContext
* @param parent
* UIComponent to search from
* @return UIViewRoot instance for this evaluation
*/
public static UIViewRoot getViewRoot(FaceletContext ctx, UIComponent parent)
{
UIComponent c = parent;
do
{
if (c instanceof UIViewRoot)
{
return (UIViewRoot) c;
}
else
{
c = c.getParent();
}
} while (c != null);
UIViewRoot root = ctx.getFacesContext().getViewRoot();
if (root == null)
{
root = FaceletCompositionContext.getCurrentInstance(ctx).getViewRoot(ctx.getFacesContext());
}
return root;
}
public static void encodeRecursive(FacesContext context, UIComponent toRender) throws IOException, FacesException
{
if (toRender.isRendered())
{
toRender.encodeBegin(context);
if (toRender.getRendersChildren())
{
toRender.encodeChildren(context);
}
else if (toRender.getChildCount() > 0)
{
for (int i = 0, childCount = toRender.getChildCount(); i < childCount; i++)
{
UIComponent child = toRender.getChildren().get(i);
encodeRecursive(context, child);
}
}
toRender.encodeEnd(context);
}
}
public static void removeTransient(UIComponent component)
{
if (component.getChildCount() > 0)
{
for (Iterator<UIComponent> itr = component.getChildren().iterator(); itr.hasNext();)
{
UIComponent child = itr.next();
if (child.isTransient())
{
itr.remove();
}
else
{
removeTransient(child);
}
}
}
if (component.getFacetCount() > 0)
{
Map<String, UIComponent> facets = component.getFacets();
for (Iterator<UIComponent> itr = facets.values().iterator(); itr.hasNext();)
{
UIComponent facet = itr.next();
if (facet.isTransient())
{
itr.remove();
}
else
{
removeTransient(facet);
}
}
}
}
/**
* Create a new UIPanel for the use as a dynamically
* created container for multiple children in a facet.
* Duplicate in javax.faces.webapp.UIComponentClassicTagBase.
* @param facesContext
* @return
*/
private static UIComponent createFacetUIPanel(FaceletContext ctx, UIComponent parent, String facetName)
{
FacesContext facesContext = ctx.getFacesContext();
UIComponent panel = facesContext.getApplication().createComponent(facesContext, UIPanel.COMPONENT_TYPE, null);
// The panel created by this method is special. To be restored properly and do not
// create duplicate ids or any other unwanted conflicts, it requires an unique id.
// This code is usually called when more than one component is added to a facet and
// it is necessary to create a shared container.
// Use FaceletCompositionContext.generateUniqueComponentId() is not possible, because
// <c:if> blocks inside a facet will make component ids unstable. Use UniqueIdVendor
// is feasible but also will be affected by <c:if> blocks inside a facet.
// The only solution that will generate real unique ids is use the parent id and the
// facet name and derive an unique id that cannot be generated by SectionUniqueIdCounter,
// doing the same trick as with metadata: use a double __ and add a prefix (f).
// Note this id will never be printed into the response, because this is just a container.
FaceletCompositionContext mctx = FaceletCompositionContext.getCurrentInstance(ctx);
UniqueIdVendor uniqueIdVendor = mctx.getUniqueIdVendorFromStack();
if (uniqueIdVendor == null)
{
uniqueIdVendor = ComponentSupport.getViewRoot(ctx, parent);
}
if (uniqueIdVendor != null)
{
// UIViewRoot implements UniqueIdVendor, so there is no need to cast to UIViewRoot
// and call createUniqueId(). See ComponentTagHandlerDelegate
int index = facetName.indexOf('.');
String cleanFacetName = facetName;
if (index >= 0)
{
cleanFacetName = facetName.replace('.', '_');
}
panel.setId(uniqueIdVendor.createUniqueId(facesContext,
mctx.getSharedStringBuilder()
.append(parent.getId())
.append("__f_")
.append(cleanFacetName).toString()));
}
panel.getAttributes().put(FACET_CREATED_UIPANEL_MARKER, Boolean.TRUE);
panel.getAttributes().put(ComponentSupport.COMPONENT_ADDED_BY_HANDLER_MARKER, Boolean.TRUE);
return panel;
}
public static void addFacet(FaceletContext ctx, UIComponent parent, UIComponent c, String facetName)
{
// facets now can have multiple children and the direct
// child of a facet is always an UIPanel (since 2.0)
UIComponent facet = parent.getFacets().get(facetName);
if (facet == null)
{
//Just set it directly like before
parent.getFacets().put(facetName, c);
}
else if (!(facet instanceof UIPanel))
{
// there is a facet, but it is not an instance of UIPanel
UIComponent child = facet;
facet = createFacetUIPanel(ctx, parent, facetName);
facet.getChildren().add(child);
facet.getChildren().add(c);
parent.getFacets().put(facetName, facet);
}
else
{
// we have a facet, which is an instance of UIPanel at this point
// check if it is a facet marked UIPanel
if (Boolean.TRUE.equals(facet.getAttributes().get(FACET_CREATED_UIPANEL_MARKER)))
{
facet.getChildren().add(c);
}
else
{
// the facet is an instance of UIPanel, but it is not marked,
// so we have to create a new UIPanel and store this one in it
UIComponent oldPanel = facet;
facet = createFacetUIPanel(ctx, parent, facetName);
facet.getChildren().add(oldPanel);
facet.getChildren().add(c);
parent.getFacets().put(facetName, facet);
}
}
}
public static void removeFacet(FaceletContext ctx, UIComponent parent, UIComponent c, String facetName)
{
UIComponent facet = parent.getFacet(facetName);
if (Boolean.TRUE.equals(facet.getAttributes().get(FACET_CREATED_UIPANEL_MARKER)))
{
facet.getChildren().remove(c);
}
else
{
parent.getFacets().remove(facetName);
}
}
public static void markComponentToRestoreFully(FacesContext context, UIComponent component)
{
if (MyfacesConfig.getCurrentInstance(context).isRefreshTransientBuildOnPSSPreserveState())
{
component.getAttributes().put(DefaultFaceletsStateManagementStrategy.COMPONENT_ADDED_AFTER_BUILD_VIEW,
ComponentState.REMOVE_ADD);
}
if (FaceletViewDeclarationLanguage.isRefreshTransientBuildOnPSSAuto(context))
{
FaceletViewDeclarationLanguage.cleanTransientBuildOnRestore(context);
}
}
public static void markComponentToRefreshDynamically(FacesContext context, UIComponent component)
{
FaceletViewDeclarationLanguageBase.dynamicComponentNeedsRefresh(context);
}
public static UIComponent findComponentChildOrFacetFrom(FacesContext facesContext, UIComponent parent, String expr)
{
final char separatorChar = facesContext.getNamingContainerSeparatorChar();
int separator = expr.indexOf(separatorChar);
if (separator == -1)
{
return ComponentSupport.findComponentChildOrFacetFrom(parent, expr, null);
}
else
{
return ComponentSupport.findComponentChildOrFacetFrom(
parent, expr.substring(0,separator), expr);
}
}
public static UIComponent findComponentChildOrFacetFrom(UIComponent parent, String id, String innerExpr)
{
if (parent.getFacetCount() > 0)
{
for (UIComponent facet : parent.getFacets().values())
{
if (id.equals(facet.getId()))
{
if (innerExpr == null)
{
return facet;
}
else if (facet instanceof NamingContainer)
{
UIComponent find = facet.findComponent(innerExpr);
if (find != null)
{
return find;
}
}
}
else if (!(facet instanceof NamingContainer))
{
UIComponent find = findComponentChildOrFacetFrom(facet, id, innerExpr);
if (find != null)
{
return find;
}
}
}
}
if (parent.getChildCount() > 0)
{
for (int i = 0, childCount = parent.getChildCount(); i < childCount; i++)
{
UIComponent child = parent.getChildren().get(i);
if (id.equals(child.getId()))
{
if (innerExpr == null)
{
return child;
}
else if (child instanceof NamingContainer)
{
UIComponent find = child.findComponent(innerExpr);
if (find != null)
{
return find;
}
}
}
else if (!(child instanceof NamingContainer))
{
UIComponent find = findComponentChildOrFacetFrom(child, id, innerExpr);
if (find != null)
{
return find;
}
}
}
}
return null;
}
public static String getFindComponentExpression(FacesContext facesContext, UIComponent component)
{
char separatorChar = facesContext.getNamingContainerSeparatorChar();
UIComponent parent = component.getParent();
StringBuilder sb = new StringBuilder();
sb.append(component.getId());
while (parent != null)
{
if (parent instanceof NamingContainer)
{
sb.insert(0, separatorChar);
sb.insert(0, parent.getId());
}
parent = parent.getParent();
}
return sb.toString();
}
public static Object restoreInitialTagState(FaceletContext ctx, FaceletCompositionContext fcc,
UIComponent parent, String uniqueId)
{
Object value = null;
if (fcc.isUsingPSSOnThisView() && !fcc.isRefreshTransientBuildOnPSSPreserveState())
{
UIViewRoot root = getViewRoot(ctx, parent);
FaceletState map = (FaceletState) root.getAttributes().get(FACELET_STATE_INSTANCE);
if (map == null)
{
value = null;
}
else
{
value = map.getState(uniqueId);
}
}
return value;
}
public static void saveInitialTagState(FaceletContext ctx, FaceletCompositionContext fcc,
UIComponent parent, String uniqueId, Object value)
{
// Only save the value when the view was built the first time, to ensure PSS algorithm
// work correctly. If preserve state is enabled, just ignore it, because this tag will
// force full restore over the parent
//if (fcc.isUsingPSSOnThisView()) {
// if (!fcc.isRefreshingTransientBuild() && !ctx.getFacesContext().isPostback()
// && !MyfacesConfig.getCurrentInstance(
// ctx.getFacesContext().getExternalContext()).isRefreshTransientBuildOnPSSPreserveState())
// If we save the value each time the view is updated, we can use PSS on the dynamic parts,
// just calling markInitialState() on the required components, simplifying the algorithm.
if (fcc.isUsingPSSOnThisView() && !fcc.isRefreshTransientBuildOnPSSPreserveState())
{
UIViewRoot root = getViewRoot(ctx, parent);
FaceletState map = (FaceletState) root.getAttributes().get(FACELET_STATE_INSTANCE);
if (map == null)
{
map = new FaceletState();
root.getAttributes().put(FACELET_STATE_INSTANCE, map);
}
map.putState(uniqueId, value);
}
}
public static FaceletState getFaceletState(FaceletContext ctx, UIComponent parent, boolean create)
{
UIViewRoot root = getViewRoot(ctx, parent);
FaceletState map = (FaceletState) root.getAttributes().get(FACELET_STATE_INSTANCE);
if (map == null && create)
{
map = new FaceletState();
root.getAttributes().put(FACELET_STATE_INSTANCE, map);
}
return map;
}
public static void setCachedFacesContext(UIComponent component, FacesContext context)
{
if (SET_CACHED_FACES_CONTEXT != null)
{
try
{
SET_CACHED_FACES_CONTEXT.invoke(component, context);
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex)
{
}
}
}
}