/* * Copyright 2011-2012 Blazebit */ package com.blazebit.blazefaces.behavior.ajax; import java.beans.BeanDescriptor; import java.beans.BeanInfo; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import javax.el.MethodExpression; import javax.faces.application.Application; import javax.faces.component.UIComponent; import javax.faces.component.behavior.ClientBehaviorHolder; import javax.faces.view.AttachedObjectHandler; import javax.faces.view.AttachedObjectTarget; import javax.faces.view.BehaviorHolderAttachedObjectTarget; import javax.faces.view.facelets.BehaviorConfig; import javax.faces.view.facelets.BehaviorHandler; import javax.faces.view.facelets.ComponentHandler; import javax.faces.view.facelets.CompositeFaceletHandler; import javax.faces.view.facelets.FaceletContext; import javax.faces.view.facelets.TagAttribute; import javax.faces.view.facelets.TagException; import javax.faces.view.facelets.TagHandler; /** * * @author Christian Beikov */ public class AjaxBehaviorHandler extends BehaviorHandler { private final TagAttribute event; private final TagAttribute process; private final TagAttribute update; private final TagAttribute onstart; private final TagAttribute onerror; private final TagAttribute onsuccess; private final TagAttribute oncomplete; private final TagAttribute disabled; private final TagAttribute immediate; private final TagAttribute listener; private final TagAttribute global; private final TagAttribute async; private final TagAttribute partialSubmit; private final boolean wrapping; public AjaxBehaviorHandler(BehaviorConfig config) { super(config); this.event = this.getAttribute("event"); this.process = this.getAttribute("process"); this.update = this.getAttribute("update"); this.onstart = this.getAttribute("onstart"); this.onerror = this.getAttribute("onerror"); this.onsuccess = this.getAttribute("onsuccess"); this.oncomplete = this.getAttribute("oncomplete"); this.disabled = this.getAttribute("disabled"); this.immediate = this.getAttribute("immediate"); this.listener = this.getAttribute("listener"); this.global = this.getAttribute("global"); this.async = this.getAttribute("async"); this.partialSubmit = this.getAttribute("partialSubmit"); this.wrapping = isWrapping(); } @Override public void apply(FaceletContext ctx, UIComponent parent) throws IOException { String eventName = getEventName(); if (!this.wrapping) { applyNested(ctx, parent, eventName); } } // Tests whether the <b:ajax> is wrapping other tags. private boolean isWrapping() { // Would be nice if there was some easy way to determine whether // we are a leaf handler. However, even leaf handlers have a // non-null nextHandler - the CompilationUnit.LEAF instance. // We assume that if we've got a TagHandler or CompositeFaceletHandler // as our nextHandler, we are not a leaf. return ((this.nextHandler instanceof TagHandler) || (this.nextHandler instanceof CompositeFaceletHandler)); } // Applies a nested AjaxHandler by adding the AjaxBehavior to the // parent component. @SuppressWarnings("unchecked") private void applyNested(FaceletContext ctx, UIComponent parent, String eventName) { if (!ComponentHandler.isNew(parent)) { return; } // Composite component case if (UIComponent.isCompositeComponent(parent)) { // Check composite component event name: boolean tagApplied = false; if (parent instanceof ClientBehaviorHolder) { applyAttachedObject(ctx, parent, eventName); // error here will propagate up tagApplied = true; } BeanInfo componentBeanInfo = (BeanInfo) parent.getAttributes().get(UIComponent.BEANINFO_KEY); if (null == componentBeanInfo) { throw new TagException(tag, "Composite component does not have BeanInfo attribute"); } BeanDescriptor componentDescriptor = componentBeanInfo.getBeanDescriptor(); if (null == componentDescriptor) { throw new TagException(tag, "Composite component BeanInfo does not have BeanDescriptor"); } List<AttachedObjectTarget> targetList = (List<AttachedObjectTarget>) componentDescriptor.getValue(AttachedObjectTarget.ATTACHED_OBJECT_TARGETS_KEY); if (null == targetList && !tagApplied) { throw new TagException(tag, "Composite component does not support behavior events"); } boolean supportedEvent = false; for (AttachedObjectTarget target : targetList) { if (target instanceof BehaviorHolderAttachedObjectTarget) { BehaviorHolderAttachedObjectTarget behaviorTarget = (BehaviorHolderAttachedObjectTarget) target; if ((null != eventName && eventName.equals(behaviorTarget.getName())) || (null == eventName && behaviorTarget.isDefaultEvent())) { supportedEvent = true; break; } } } if (supportedEvent) { try { // Special handling for myfaces Class<?> faceletCompositionContextClass = Class.forName("org.apache.myfaces.view.facelets.FaceletCompositionContext"); Object mctx = faceletCompositionContextClass.getMethod("getCurrentInstance", FaceletContext.class).invoke(null, ctx); faceletCompositionContextClass.getMethod("addAttachedObjectHandler", UIComponent.class, AttachedObjectHandler.class).invoke(mctx, parent, this); } catch (Exception ex) { // Fall back to mojarra solution getAttachedObjectHandlers(parent, true).add(this); } } else { if (!tagApplied) { throw new TagException(tag, "Composite component does not support event " + eventName); } } } else if (parent instanceof ClientBehaviorHolder) { applyAttachedObject(ctx, parent, eventName); } else { throw new TagException(this.tag, "Unable to attach <b:ajax> to non-ClientBehaviorHolder parent"); } } @Override public String getEventName() { return (this.event != null) ? this.event.getValue() : null; } @SuppressWarnings("unchecked") public static List<AttachedObjectHandler> getAttachedObjectHandlers(UIComponent component, boolean create) { Map<String, Object> attrs = component.getAttributes(); List<AttachedObjectHandler> result = (List<AttachedObjectHandler>) attrs.get("javax.faces.RetargetableHandlers"); if (result == null) { if (create) { result = new ArrayList<AttachedObjectHandler>(); attrs.put("javax.faces.RetargetableHandlers", result); } else { result = Collections.EMPTY_LIST; } } return result; } public void applyAttachedObject(FaceletContext context, UIComponent component, String eventName) { ClientBehaviorHolder holder = (ClientBehaviorHolder) component; if(null == eventName) { eventName = holder.getDefaultEventName(); if (null == eventName) { throw new TagException(this.tag, "Event attribute could not be determined: " + eventName); } } else { Collection<String> eventNames = holder.getEventNames(); if (!eventNames.contains(eventName)) { throw new TagException(this.tag, "Event:" + eventName + " is not supported."); } } AjaxBehavior ajaxBehavior = createAjaxBehavior(context, eventName); holder.addClientBehavior(eventName, ajaxBehavior); } private AjaxBehavior createAjaxBehavior(FaceletContext ctx, String eventName) { Application application = ctx.getFacesContext().getApplication(); AjaxBehavior behavior = (AjaxBehavior) application.createBehavior(AjaxBehavior.BEHAVIOR_ID); setBehaviorAttribute(ctx, behavior, this.process, String.class); setBehaviorAttribute(ctx, behavior, this.update, String.class); setBehaviorAttribute(ctx, behavior, this.onstart, String.class); setBehaviorAttribute(ctx, behavior, this.onerror, String.class); setBehaviorAttribute(ctx, behavior, this.onsuccess, String.class); setBehaviorAttribute(ctx, behavior, this.oncomplete, String.class); setBehaviorAttribute(ctx, behavior, this.disabled, Boolean.class); setBehaviorAttribute(ctx, behavior, this.immediate, Boolean.class); setBehaviorAttribute(ctx, behavior, this.global, Boolean.class); setBehaviorAttribute(ctx, behavior, this.async, Boolean.class); setBehaviorAttribute(ctx, behavior, this.partialSubmit, Boolean.class); setBehaviorAttribute(ctx, behavior, this.listener, MethodExpression.class); if (listener != null) { behavior.addAjaxBehaviorListener(new AjaxBehaviorListenerImpl( this.listener.getMethodExpression(ctx, Object.class, new Class<?>[]{}))); } return behavior; } private void setBehaviorAttribute(FaceletContext ctx, AjaxBehavior behavior, TagAttribute attr, Class<?> type) { if (attr != null) { behavior.setValueExpression(attr.getLocalName(), attr.getValueExpression(ctx, type)); } } }