/** * @author Jacob Hookom * @version $Id$ */ final class ComponentRule extends MetaRule { static final class LiteralAttributeMetadata extends Metadata { private final String name; private final String value; public LiteralAttributeMetadata(String name, String value) { this.value = value; this.name = name; } public void applyMetadata(FaceletContext ctx, Object instance) { ((UIComponent) instance).getAttributes().put(this.name, this.value); } } static final class ValueExpressionMetadata extends Metadata { private final String name; private final TagAttribute attr; private final Class type; public ValueExpressionMetadata(String name, Class type, TagAttribute attr) { this.name = name; this.attr = attr; this.type = type; } public void applyMetadata(FaceletContext ctx, Object instance) { ((UIComponent) instance) .setValueExpression(this.name, this.attr.getValueExpression(ctx, this.type)); } } static final class ValueBindingMetadata extends Metadata { private final String name; private final TagAttribute attr; private final Class type; public ValueBindingMetadata(String name, Class type, TagAttribute attr) { this.name = name; this.attr = attr; this.type = type; } public void applyMetadata(FaceletContext ctx, Object instance) { ((UIComponent) instance) .setValueBinding( this.name, new LegacyValueBinding(this.attr.getValueExpression(ctx, this.type))); } } private static final Logger log = FacesLogger.FACELETS_COMPONENT.getLogger(); public static final ComponentRule Instance = new ComponentRule(); public ComponentRule() { super(); } public Metadata applyRule(String name, TagAttribute attribute, MetadataTarget meta) { if (meta.isTargetInstanceOf(UIComponent.class)) { // if component and dynamic, then must set expression if (!attribute.isLiteral()) { Class type = meta.getPropertyType(name); if (type == null) { type = Object.class; } return new ValueExpressionMetadata(name, type, attribute); } else if (meta.getWriteMethod(name) == null) { // this was an attribute literal, but not property warnAttr(attribute, meta.getTargetClass(), name); return new LiteralAttributeMetadata(name, attribute.getValue()); } } return null; } private static void warnAttr(TagAttribute attr, Class type, String n) { if (log.isLoggable(Level.FINER)) { log.finer(attr + " Property '" + n + "' is not on type: " + type.getName()); } } }
public class ComponentTagHandlerDelegateImpl extends TagHandlerDelegate { private ComponentHandler owner; private static final Logger log = FacesLogger.FACELETS_COMPONENT.getLogger(); private final TagAttribute binding; protected String componentType; protected final TagAttribute id; private final String rendererType; private CreateComponentDelegate createCompositeComponentDelegate; public ComponentTagHandlerDelegateImpl(ComponentHandler owner) { this.owner = owner; ComponentConfig config = owner.getComponentConfig(); this.componentType = config.getComponentType(); this.rendererType = config.getRendererType(); this.id = owner.getTagAttribute("id"); this.binding = owner.getTagAttribute("binding"); } /** * Method handles UIComponent tree creation in accordance with the JSF 1.2 spec. * * <ol> * <li>First determines this UIComponent's id by calling {@link * javax.faces.view.facelets.ComponentHandler#getTagId()}. * <li>Search the parent for an existing UIComponent of the id we just grabbed * <li>If found, {@link * com.sun.faces.facelets.tag.jsf.ComponentSupport#markForDeletion(javax.faces.component.UIComponent) * mark} its children for deletion. * <li>If <i>not</i> found, call {@link #createComponent(FaceletContext) createComponent}. * <ol> * <li>Only here do we apply {@link * com.sun.faces.facelets.tag.MetaTagHandlerImpl#setAttributes(FaceletContext, * Object)} * <li>Set the UIComponent's id * <li>Set the RendererType of this instance * </ol> * <li>Now apply the nextHandler, passing the UIComponent we've created/found. * <li>Now add the UIComponent to the passed parent * <li>Lastly, if the UIComponent already existed (found), then {@link * ComponentSupport#finalizeForDeletion(UIComponent) finalize} for deletion. * </ol> * * @throws TagException if the UIComponent parent is null */ @Override public void apply(FaceletContext ctx, UIComponent parent) throws IOException { FacesContext context = ctx.getFacesContext(); // make sure our parent is not null if (parent == null) { throw new TagException(owner.getTag(), "Parent UIComponent was null"); } // our id String id = ctx.generateUniqueId(owner.getTagId()); // grab our component UIComponent c = findChild(ctx, parent, id); if (null == c && context.isPostback() && UIComponent.isCompositeComponent(parent) && parent.getAttributes().get(id) != null) { c = findReparentedComponent(ctx, parent, id); } else { /** * If we found a child that is dynamic, the actual parent might have changed, so we need to * remove it from the actual parent. The reapplyDynamicActions will then replay the actions * and will make sure it ends up in the correct order. */ if (c != null && c.getParent() != parent && c.getAttributes().containsKey(DYNAMIC_COMPONENT)) { c.getParent().getChildren().remove(c); } } boolean componentFound = false; if (c != null) { componentFound = true; doExistingComponentActions(ctx, id, c); } else { c = this.createComponent(ctx); doNewComponentActions(ctx, id, c); assignUniqueId(ctx, parent, id, c); // hook method owner.onComponentCreated(ctx, c, parent); } CompositeComponentStackManager ccStackManager = CompositeComponentStackManager.getManager(context); boolean compcompPushed = pushComponentToEL(ctx, c, ccStackManager); if (ProjectStage.Development == context.getApplication().getProjectStage()) { ComponentSupport.setTagForComponent(context, c, this.owner.getTag()); } // If this this a naming container, stop generating unique Ids // for the repeated tags boolean setUniqueIds = false; boolean oldUnique = false; if (c instanceof NamingContainer) { oldUnique = ComponentSupport.setNeedUniqueIds(ctx, false); setUniqueIds = true; } try { // first allow c to get populated owner.applyNextHandler(ctx, c); } finally { if (setUniqueIds) ComponentSupport.setNeedUniqueIds(ctx, oldUnique); } // finish cleaning up orphaned children if (componentFound) { doOrphanedChildCleanup(ctx, parent, c); } this.privateOnComponentPopulated(ctx, c); owner.onComponentPopulated(ctx, c, parent); // add to the tree afterwards // this allows children to determine if it's // been part of the tree or not yet addComponentToView(ctx, parent, c, componentFound); adjustIndexOfDynamicChildren(context, c); popComponentFromEL(ctx, c, ccStackManager, compcompPushed); } private void adjustIndexOfDynamicChildren(FacesContext context, UIComponent parent) { StateContext stateContext = StateContext.getStateContext(context); if (!stateContext.hasOneOrMoreDynamicChild(parent)) { return; } List<UIComponent> children = parent.getChildren(); List<UIComponent> dynamicChildren = Collections.emptyList(); for (UIComponent cur : children) { if (stateContext.componentAddedDynamically(cur)) { if (dynamicChildren.isEmpty()) { dynamicChildren = new ArrayList<UIComponent>(children.size()); } dynamicChildren.add(cur); } } // First remove all the dynamic children, this puts the non-dynamic children at // their original position for (UIComponent cur : dynamicChildren) { int i = stateContext.getIndexOfDynamicallyAddedChildInParent(cur); if (-1 != i) { children.remove(cur); } } // Now that the non-dynamic children are in the correct position add the dynamic children // back in. for (UIComponent cur : dynamicChildren) { int i = stateContext.getIndexOfDynamicallyAddedChildInParent(cur); if (-1 != i) { if (i < children.size()) { children.add(i, cur); } else { children.add(cur); } } } } @Override public MetaRuleset createMetaRuleset(Class type) { Util.notNull("type", type); MetaRuleset m = new MetaRulesetImpl(owner.getTag(), type); // ignore standard component attributes m.ignore("binding").ignore("id"); // add auto wiring for attributes m.addRule(ComponentRule.Instance); // if it's an ActionSource if (ActionSource.class.isAssignableFrom(type)) { m.addRule(ActionSourceRule.Instance); } // if it's a ValueHolder if (ValueHolder.class.isAssignableFrom(type)) { m.addRule(ValueHolderRule.Instance); // if it's an EditableValueHolder if (EditableValueHolder.class.isAssignableFrom(type)) { m.ignore("submittedValue"); m.ignore("valid"); m.addRule(EditableValueHolderRule.Instance); } } // if it's a selectone or selectmany if (UISelectOne.class.isAssignableFrom(type) || UISelectMany.class.isAssignableFrom(type)) { m.addRule(RenderPropertyRule.Instance); } return m; } // ------------------------------------------------------- Protected Methods protected void addComponentToView( FaceletContext ctx, UIComponent parent, UIComponent c, boolean componentFound) { FacesContext context = ctx.getFacesContext(); boolean suppressEvents = ComponentSupport.suppressViewModificationEvents(context); boolean compcomp = UIComponent.isCompositeComponent(c); if (suppressEvents && componentFound && !compcomp) { context.setProcessingEvents(false); } ComponentSupport.addComponent(ctx, parent, c); if (suppressEvents && componentFound && !compcomp) { context.setProcessingEvents(true); } } protected boolean pushComponentToEL( FaceletContext ctx, UIComponent c, CompositeComponentStackManager ccStackManager) { c.pushComponentToEL(ctx.getFacesContext(), c); boolean compcompPushed = false; if (UIComponent.isCompositeComponent(c)) { compcompPushed = ccStackManager.push(c, TreeCreation); } return compcompPushed; } protected void popComponentFromEL( FaceletContext ctx, UIComponent c, CompositeComponentStackManager ccStackManager, boolean compCompPushed) { c.popComponentFromEL(ctx.getFacesContext()); if (compCompPushed) { ccStackManager.pop(TreeCreation); } } protected void doOrphanedChildCleanup(FaceletContext ctx, UIComponent parent, UIComponent c) { ComponentSupport.finalizeForDeletion(c); if (getFacetName(parent) == null) { FacesContext context = ctx.getFacesContext(); boolean suppressEvents = ComponentSupport.suppressViewModificationEvents(context); if (suppressEvents) { // if the component has already been found, it will be removed // and added back to the view. We don't want to publish events // for this case. context.setProcessingEvents(false); } // suppress the remove event for this case since it will be re-added parent.getChildren().remove(c); if (suppressEvents) { // re-enable event processing context.setProcessingEvents(true); } } } protected void assignUniqueId(FaceletContext ctx, UIComponent parent, String id, UIComponent c) { // If the id is specified as a literal, and the component is being // repeated (by c:forEach, for example), use generated unique ids // after the first instance if (this.id != null && !(this.id.isLiteral() && ComponentSupport.getNeedUniqueIds(ctx))) { c.setId(this.id.getValue(ctx)); } else { UIViewRoot root = ComponentSupport.getViewRoot(ctx, parent); if (root != null) { String uid; IdMapper mapper = IdMapper.getMapper(ctx.getFacesContext()); String mid = ((mapper != null) ? mapper.getAliasedId(id) : id); UIComponent ancestorNamingContainer = parent.getNamingContainer(); if (null != ancestorNamingContainer && ancestorNamingContainer instanceof UniqueIdVendor) { uid = ((UniqueIdVendor) ancestorNamingContainer).createUniqueId(ctx.getFacesContext(), mid); } else { uid = root.createUniqueId(ctx.getFacesContext(), mid); } c.setId(uid); } } if (this.rendererType != null) { c.setRendererType(this.rendererType); } } protected void doNewComponentActions(FaceletContext ctx, String id, UIComponent c) { if (log.isLoggable(Level.FINE)) { log.fine(owner.getTag() + " Component[" + id + "] Created: " + c.getClass().getName()); } // If this is NOT a composite component... if (null == createCompositeComponentDelegate) { // set the attributes and properties into the UIComponent instance. owner.setAttributes(ctx, c); } // otherwise, allow the composite component code to do it. // mark it owned by a facelet instance c.getAttributes().put(ComponentSupport.MARK_CREATED, id); if (ctx.getFacesContext().isProjectStage(ProjectStage.Development)) { // inject the location into the component c.getAttributes().put(UIComponent.VIEW_LOCATION_KEY, owner.getTag().getLocation()); } } protected void doExistingComponentActions(FaceletContext ctx, String id, UIComponent c) { // mark all children for cleaning if (log.isLoggable(Level.FINE)) { log.fine(owner.getTag() + " Component[" + id + "] Found, marking children for cleanup"); } ComponentSupport.markForDeletion(c); /* * Repply the id, for the case when the component tree was changed, and the id's are set explicitly. */ if (this.id != null) { c.setId(this.id.getValue(ctx)); } } @SuppressWarnings({"UnusedDeclaration"}) protected UIComponent findChild(FaceletContext ctx, UIComponent parent, String tagId) { return ComponentSupport.findChildByTagId(parent, tagId); } @SuppressWarnings({"UnusedDeclaration"}) protected UIComponent findReparentedComponent( FaceletContext ctx, UIComponent parent, String tagId) { UIComponent facet = parent.getFacets().get(UIComponent.COMPOSITE_FACET_NAME); if (facet != null) { UIComponent newParent = facet.findComponent((String) parent.getAttributes().get(tagId)); if (newParent != null) return ComponentSupport.findChildByTagId(newParent, tagId); } return null; } // ------------------------------------------------- Package Private Methods void setCreateCompositeComponentDelegate(CreateComponentDelegate createComponentDelegate) { this.createCompositeComponentDelegate = createComponentDelegate; } /** * If the binding attribute was specified, use that in conjuction with our componentType String * variable to call createComponent on the Application, otherwise just pass the componentType * String. * * <p>If the binding was used, then set the ValueExpression "binding" on the created UIComponent. * * @see Application#createComponent(javax.faces.el.ValueBinding, javax.faces.context.FacesContext, * java.lang.String) * @see Application#createComponent(java.lang.String) * @param ctx FaceletContext to use in creating a component * @return */ private UIComponent createComponent(FaceletContext ctx) { if (null != createCompositeComponentDelegate) { return createCompositeComponentDelegate.createComponent(ctx); } UIComponent c; FacesContext faces = ctx.getFacesContext(); Application app = faces.getApplication(); if (this.binding != null) { ValueExpression ve = this.binding.getValueExpression(ctx, Object.class); c = app.createComponent(ve, faces, this.componentType, this.rendererType); if (c != null) { // Make sure the component supports 1.2 c.setValueExpression("binding", ve); } } else { c = app.createComponent(faces, this.componentType, this.rendererType); } return c; } /* * Internal hook that allows us to perform common processing for all * components after they are populated. At the moment, the only common * processing we need to perform is applying wrapping AjaxBehaviors, * if any exist. */ private void privateOnComponentPopulated(FaceletContext ctx, UIComponent c) { if (c instanceof ClientBehaviorHolder) { FacesContext context = ctx.getFacesContext(); AjaxBehaviors ajaxBehaviors = AjaxBehaviors.getAjaxBehaviors(context, false); if (ajaxBehaviors != null) { ajaxBehaviors.addBehaviors(context, (ClientBehaviorHolder) c); } } if (c instanceof EditableValueHolder) { processValidators(ctx.getFacesContext(), (EditableValueHolder) c); } } /** * Process default validatior/wrapping validation information and install <code>Validators</code> * based off the result. */ private void processValidators(FacesContext ctx, EditableValueHolder editableValueHolder) { ComponentValidators componentValidators = ComponentValidators.getValidators(ctx, false); if (componentValidators != null) { // process any elements on the stack. componentValidators.addValidators(ctx, editableValueHolder); } else { // no custom handling required, so add the default validators ComponentValidators.addDefaultValidatorsToComponent(ctx, editableValueHolder); } } /** @return the Facet name we are scoped in, otherwise null */ private String getFacetName(UIComponent parent) { return (String) parent.getAttributes().get(FacetHandler.KEY); } interface CreateComponentDelegate { public UIComponent createComponent(FaceletContext ctx); public void setCompositeComponent(FacesContext context, UIComponent cc); public UIComponent getCompositeComponent(FacesContext context); } }