You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@click.apache.org by sa...@apache.org on 2010/06/12 11:36:43 UTC

svn commit: r953972 - in /click/trunk/click/framework/src/org/apache/click: ActionEventDispatcher.java CallbackDispatcher.java ClickServlet.java

Author: sabob
Date: Sat Jun 12 09:36:42 2010
New Revision: 953972

URL: http://svn.apache.org/viewvc?rev=953972&view=rev
Log:
added Ajax support to ClickServlet and core classes. CLK-653

Added:
    click/trunk/click/framework/src/org/apache/click/CallbackDispatcher.java
Modified:
    click/trunk/click/framework/src/org/apache/click/ActionEventDispatcher.java
    click/trunk/click/framework/src/org/apache/click/ClickServlet.java

Modified: click/trunk/click/framework/src/org/apache/click/ActionEventDispatcher.java
URL: http://svn.apache.org/viewvc/click/trunk/click/framework/src/org/apache/click/ActionEventDispatcher.java?rev=953972&r1=953971&r2=953972&view=diff
==============================================================================
--- click/trunk/click/framework/src/org/apache/click/ActionEventDispatcher.java (original)
+++ click/trunk/click/framework/src/org/apache/click/ActionEventDispatcher.java Sat Jun 12 09:36:42 2010
@@ -19,36 +19,28 @@
 package org.apache.click;
 
 import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
 
 import java.util.List;
+import java.util.Set;
+import org.apache.click.service.ConfigService;
+import org.apache.click.service.LogService;
+import org.apache.click.util.HtmlStringBuffer;
+import org.apache.commons.lang.ClassUtils;
 import org.apache.commons.lang.Validate;
 
 /**
- * Provides a control ActionListener event dispatcher.
- * <p/>
- * <b>Please note:</b> this class is meant for component development and can be
- * ignored otherwise.
- * <p/>
- * When registering an ActionListener you can specify the callback to occur for
- * a specific event. For example ActionListeners can be registered to fire
- * <tt>after</tt> the <tt>onProcess</tt> event. This event can be specified
- * through the constant {@link #POST_ON_PROCESS_EVENT}.
- * <p/>
- * The ClickServlet will notify the ActionEventDispatcher which ActionListeners
- * to fire. For example, after the <tt>onProcess</tt> event, the ClickServlet
- * will notify the dispatcher to fire ActionListeners registered for the
- * {@link #POST_ON_PROCESS_EVENT} (this is the default event when listeners are
- * fired).
- * <p/>
- * Out of the box ActionEventDispatcher only supports the event
- * {@link #POST_ON_PROCESS_EVENT} (the default).
+ * Provides a control ActionListener and Behavior dispatcher. The
+ * ClickServlet will dispatch registered ActionListeners and Behaviors after
+ * page controls have been processed.
  *
  * <h4>Example Usage</h4>
  * The following example shows how to register an ActionListener with a custom
  * Control:
  *
  * <pre class="prettyprint">
- * public class MyLink extends AbstractControl {
+ * public class MyControl extends AbstractControl {
  *     ...
  *
  *     public boolean onProcess() {
@@ -64,10 +56,10 @@ import org.apache.commons.lang.Validate;
  *     }
  * } </pre>
  *
- * In this example if the link is clicked, it then calls
+ * When the link is clicked it invokes the method
  * {@link org.apache.click.control.AbstractControl#dispatchActionEvent()}.
- * This method registers the Control's action listener with ActionEventDispatcher.
- * The ClickServlet will subsequently invoke the registered
+ * This method registers the Control's action listener with the
+ * ActionEventDispatcher. The ClickServlet will subsequently invoke the registered
  * {@link ActionListener#onAction(Control)} method after all the Page controls
  * <tt>onProcess()</tt> method have been invoked.
  */
@@ -75,77 +67,114 @@ public class ActionEventDispatcher {
 
     // Constants --------------------------------------------------------------
 
+     /** The thread local dispatcher holder. */
+    private static final ThreadLocal<DispatcherStack> THREAD_LOCAL_DISPATCHER
+        = new ThreadLocal<DispatcherStack>();
+
+    // Variables --------------------------------------------------------------
+
+    /** The list of registered event sources. */
+    List<Control> eventSourceList;
+
+    /** The list of registered event listeners. */
+    List<ActionListener> eventListenerList;
+
+    /** The set of Controls with attached Behaviors. */
+    Set<Control> behaviorSourceSet;
+
     /**
-     * Indicates the listener should fire <tt>AFTER</tt> the onProcess event.
-     * The <tt>POST_ON_PROCESS_EVENT</tt> is the event during which control
-     * listeners will fire.
+     * The {@link org.apache.click.Partial} response to render. This partial is
+     * set from the target Behavior.
      */
-    public static final int POST_ON_PROCESS_EVENT = 300;
+    Partial partial;
 
-    // Variables --------------------------------------------------------------
+    /** The application log service. */
+    LogService logger;
 
-    /** The thread local dispatcher holder. */
-    private static final ThreadLocal<DispatcherStack> THREAD_LOCAL_DISPATCHER
-        = new ThreadLocal<DispatcherStack>();
+    // Constructors -----------------------------------------------------------
 
-    /** The POST_PROCESS events holder. */
-    private EventHolder postProcessEventHolder;
+    public ActionEventDispatcher(ConfigService configService) {
+        this.logger = configService.getLogService();
+    }
 
-    //  Public Methods ---------------------------------------------------------
+    // Public Methods ---------------------------------------------------------
 
     /**
      * Register the event source and event ActionListener to be fired by the
      * ClickServlet once all the controls have been processed.
-     * <p/>
-     * Listeners registered by this method will be fired in the
-     * {@link #POST_ON_PROCESS_EVENT}.
-     *
-     * @see #dispatchActionEvent(org.apache.click.Control, org.apache.click.ActionListener, int)
      *
      * @param source the action event source
      * @param listener the event action listener
      */
     public static void dispatchActionEvent(Control source, ActionListener listener) {
-        dispatchActionEvent(source, listener, POST_ON_PROCESS_EVENT);
+        Validate.notNull(source, "Null source parameter");
+        Validate.notNull(listener, "Null listener parameter");
+
+        ActionEventDispatcher instance = getThreadLocalDispatcher();
+        instance.registerActionEvent(source, listener);
     }
 
     /**
-     * Register the event source and event ActionListener to be fired by the
-     * ClickServlet in the specified event.
+     * Register the source control which behaviors should be fired by the
+     * ClickServlet.
      *
-     * @param source the action event source
-     * @param listener the event action listener
-     * @param event the specific event to trigger the action event
+     * @param source the source control which behaviors should be fired
      */
-    public static void dispatchActionEvent(Control source,
-        ActionListener listener, int event) {
-
+    public static void dispatchBehavior(Control source) {
         Validate.notNull(source, "Null source parameter");
-        Validate.notNull(listener, "Null listener parameter");
 
         ActionEventDispatcher instance = getThreadLocalDispatcher();
-        EventHolder eventHolder = instance.getEventHolder(event);
-        eventHolder.registerActionEvent(source, listener);
+        instance.registerBehaviorSource(source);
     }
 
-    // Protected Methods ------------------------------------------------------
+    /**
+     * Fire all the registered action events after the Page Controls have been
+     * processed and return true if the page should continue processing.
+     *
+     * @see #fireActionEvents(org.apache.click.Context, int)
+     *
+     * @param context the request context
+     *
+     * @return true if the page should continue processing, false otherwise
+     */
+    public boolean fireActionEvents(Context context) {
+
+        if (!hasActionEvents()) {
+            return true;
+        }
+
+        return fireActionEvents(context, getEventSourceList(), getEventListenerList());
+    }
 
     /**
-     * Allow the dispatcher to handle the error that occurred.
+     * Fire all the registered behaviors after the Page Controls have been
+     * processed and return true if the page should continue processing.
      *
-     * @param throwable the error which occurred during processing
+     * @see #fireBehaviors(org.apache.click.Context, java.util.Set)
+     *
+     * @param context the request context
+     *
+     * @return true if the page should continue processing, false otherwise
      */
-    protected void errorOccurred(Throwable throwable) {
-        // Clear the POST_ON_PROCESS_EVENT control listeners from the dispatcher
-        // Registered listeners from other phases must still be invoked
-        getEventHolder(ActionEventDispatcher.POST_ON_PROCESS_EVENT).clear();
+    public boolean fireBehaviors(Context context) {
+
+        if (!hasBehaviorSourceSet()) {
+            return true;
+        }
+
+        return fireBehaviors(context, getBehaviorSourceSet());
     }
 
+    // Protected Methods ------------------------------------------------------
+
     /**
-     * Clear the event list.
+     * Allow the dispatcher to handle the error that occurred.
+     *
+     * @param throwable the error which occurred during processing
      */
-    protected void clearEvents() {
-        getPostProcessEventHolder().clear();
+    protected void errorOccurred(Throwable throwable) {
+        // Clear the control listeners and behaviors from the dispatcher
+        clear();
     }
 
     /**
@@ -153,7 +182,7 @@ public class ActionEventDispatcher {
      *
      * @return the thread local dispatcher instance.
      * @throws RuntimeException if a ActionEventDispatcher is not available on the
-     * thread.
+     * thread
      */
     protected static ActionEventDispatcher getThreadLocalDispatcher() {
         return getDispatcherStack().peek();
@@ -163,10 +192,6 @@ public class ActionEventDispatcher {
      * Fire the actions for the given listener list and event source list which
      * return true if the page should continue processing.
      * <p/>
-     * This method will be passed the listener list and event source list
-     * of a specific event e.g. {@link #POST_ON_PROCESS_EVENT}.
-     * event.
-     * <p/>
      * This method can be overridden if you need to customize the way events
      * are fired.
      *
@@ -177,20 +202,20 @@ public class ActionEventDispatcher {
      *
      * @return true if the page should continue processing or false otherwise
      */
-    protected boolean fireActionEvents(Context context, List<Control> eventSourceList,
-        List<ActionListener> eventListenerList, int event) {
+    protected boolean fireActionEvents(Context context, List eventSourceList,
+        List eventListenerList) {
 
         boolean continueProcessing = true;
 
         for (int i = 0, size = eventSourceList.size(); i < size; i++) {
-            Control source = eventSourceList.get(0);
-            ActionListener listener = eventListenerList.get(0);
+            Control source = (Control) eventSourceList.get(0);
+            ActionListener listener = (ActionListener) eventListenerList.get(0);
 
             // Pop the first entry in the list
             eventSourceList.remove(0);
             eventListenerList.remove(0);
 
-            if (!fireActionEvent(context, source, listener, event)) {
+            if (!fireActionEvent(context, source, listener)) {
                 continueProcessing = false;
             }
         }
@@ -199,92 +224,225 @@ public class ActionEventDispatcher {
     }
 
     /**
-     * Fire all the registered action events after the Page Controls have been
-     * processed and return true if the page should continue processing.
+     * Fire the action for the given listener and event source which
+     * return true if the page should continue processing.
      * <p/>
-     * @see #fireActionEvents(org.apache.click.Context, int)
+     * This method can be overridden if you need to customize the way events
+     * are fired.
      *
      * @param context the request context
+     * @param source the source control
+     * @param listener the listener to fire
      *
-     * @return true if the page should continue processing or false otherwise
+     * @return true if the page should continue processing, false otherwise
      */
-    protected boolean fireActionEvents(Context context) {
-        return fireActionEvents(context, ActionEventDispatcher.POST_ON_PROCESS_EVENT);
+    protected boolean fireActionEvent(Context context, Control source,
+        ActionListener listener) {
+        return listener.onAction(source);
     }
 
     /**
-     * Fire all the registered action events for the specified event and return
-     * true if the page should continue processing.
+     * Fire the behaviors for the given control set and return true if the page
+     * should continue processing, false otherwise.
+     * <p/>
+     * This method can be overridden if you need to customize the way behaviors
+     * are fired.
+     *
+     * @see #fireBehavior(org.apache.click.Context, org.apache.click.Control)
      *
      * @param context the request context
-     * @param event the event which listeners to fire
+     * @param behaviorSourceSet the set of controls with attached behaviors
      *
-     * @return true if the page should continue processing or false otherwise
+     * @return true if the page should continue processing, false otherwise
      */
-    protected boolean fireActionEvents(Context context, int event) {
-        EventHolder eventHolder = getEventHolder(event);
-        return eventHolder.fireActionEvents(context);
+    protected boolean fireBehaviors(Context context, Set<Control> behaviorSourceSet) {
+
+        boolean continueProcessing = true;
+
+        for (Iterator<Control> it = behaviorSourceSet.iterator(); it.hasNext(); ) {
+            Control source = it.next();
+
+            // Pop the first entry in the set
+            it.remove();
+
+            if (!fireBehavior(context, source)) {
+                continueProcessing = false;
+            }
+        }
+
+        return continueProcessing;
     }
 
     /**
-     * Fire the action for the given listener and event source which
-     * return true if the page should continue processing.
-     * <p/>
-     * This method will be passed a listener and source of a specific event
-     * e.g. {@link #POST_ON_PROCESS_EVENT}.
+     * Fire the behavior for the given control and return true if the page
+     * should continue processing, false otherwise.
      * <p/>
-     * This method can be overridden if you need to customize the way events
+     * This method can be overridden if you need to customize the way behaviors
      * are fired.
      *
      * @param context the request context
-     * @param source the source control
-     * @param listener the listener to fire
-     * @param event the specific event which events to fire
+     * @param source the control which attached behaviors should be fired
      *
-     * @return true if the page should continue processing or false otherwise
+     * @return true if the page should continue processing, false otherwise
      */
-    protected boolean fireActionEvent(Context context, Control source,
-        ActionListener listener, int event) {
-        return listener.onAction(source);
+    protected boolean fireBehavior(Context context, Control source) {
+
+        boolean continueProcessing = true;
+
+        for (Behavior behavior : source.getBehaviors()) {
+
+            if (behavior.isRequestTarget(context)) {
+
+                partial = behavior.onAction(source);
+
+                if (logger.isTraceEnabled()) {
+                    String behaviorClassName = ClassUtils.getShortClassName(behavior.getClass());
+                    HtmlStringBuffer buffer = new HtmlStringBuffer();
+                    buffer.append("   invoked: ");
+                    buffer.append(behaviorClassName);
+                    buffer.append(".isRequestTarget() : true");
+                    buffer.append(" (Ajax Behavior found)");
+
+                    logger.trace(buffer.toString());
+
+                    if (partial == null) {
+                        buffer = new HtmlStringBuffer();
+                        buffer.append("   invoked: ");
+                        buffer.append(behaviorClassName);
+                        buffer.append(".onAction() : null");
+                        buffer.append(" (*no* Partial was returned by Behavior)");
+                        logger.trace(buffer.toString());
+                    }
+                }
+
+                continueProcessing = false;
+                break;
+            }
+        }
+
+        if (logger.isTraceEnabled()) {
+
+            // Provide trace if no target behavior was found
+            if (continueProcessing) {
+                HtmlStringBuffer buffer = new HtmlStringBuffer();
+                String sourceClassName = ClassUtils.getShortClassName(source.getClass());
+                buffer.append("   *no* target behavior found for '");
+                buffer.append(source.getName()).append("' ");
+                buffer.append(sourceClassName);
+                buffer.append(" - invoking Behavior.isRequestTarget() returned false for all behaviors");
+                logger.trace(buffer.toString());
+            }
+        }
+
+        // Ajax requests stops further processing
+        return continueProcessing;
     }
 
+    // Package Private Methods ------------------------------------------------
+
     /**
-     * Return the EventHolder for the specified event.
+     * Register the event source and event ActionListener.
      *
-     * @param event the event which EventHolder to retrieve
+     * @param source the action event source
+     * @param listener the event action listener
+     */
+    void registerActionEvent(Control source, ActionListener listener) {
+        Validate.notNull(source, "Null source parameter");
+        Validate.notNull(listener, "Null listener parameter");
+
+        getEventSourceList().add(source);
+        getEventListenerList().add(listener);
+    }
+
+    /**
+     * Register the behavior source control.
      *
-     * @return the EventHolder for the specified event
+     * @param source the behavior source control
      */
-    protected EventHolder getEventHolder(int event) {
-        if (event == POST_ON_PROCESS_EVENT) {
-            return getPostProcessEventHolder();
-        } else {
-            return null;
+    void registerBehaviorSource(Control source) {
+        Validate.notNull(source, "Null source parameter");
+
+        getBehaviorSourceSet().add(source);
+    }
+
+    /**
+     * Checks if any Action Events have been registered.
+     *
+     * @return true if the dispatcher has any Action Events registered
+     */
+    boolean hasActionEvents() {
+        if (eventListenerList == null || eventListenerList.isEmpty()) {
+            return false;
         }
+        return true;
     }
 
     /**
-     * Create a new EventHolder instance.
+     * Return the list of event listeners.
      *
-     * @param event the EventHolder's event
-     * @return new EventHolder instance
+     * @return list of event listeners
      */
-    protected EventHolder createEventHolder(int event) {
-        return new EventHolder(event);
+    List<ActionListener> getEventListenerList() {
+        if (eventListenerList == null) {
+            eventListenerList = new ArrayList<ActionListener>();
+        }
+        return eventListenerList;
     }
 
-    // Package Private Methods ------------------------------------------------
+    /**
+     * Return the list of event sources.
+     *
+     * @return list of event sources
+     */
+    List<Control> getEventSourceList() {
+        if (eventSourceList == null) {
+            eventSourceList = new ArrayList<Control>();
+        }
+        return eventSourceList;
+    }
+
+    /**
+     * Clear the events and behaviors.
+     */
+    void clear() {
+        if (hasActionEvents()) {
+            getEventSourceList().clear();
+            getEventListenerList().clear();
+            getBehaviorSourceSet().clear();
+        }
+    }
+
+    /**
+     * Return the Partial Ajax response or null if no behavior was dispatched.
+     *
+     * @return the Partial Ajax response or null if no behavior was dispatched
+     */
+    Partial getPartial() {
+        return partial;
+    }
+
+    /**
+     * Return true if a control with behaviors was registered, false otherwise.
+     *
+     * @return true if a control with behaviors was registered, false otherwise.
+     */
+    boolean hasBehaviorSourceSet() {
+        if (behaviorSourceSet == null || behaviorSourceSet.isEmpty()) {
+            return false;
+        }
+        return true;
+    }
 
     /**
-     * Return the {@link #POST_ON_PROCESS_EVENT} {@link EventHolder}.
+     * Return the set of controls with attached behaviors.
      *
-     * @return the {@link #POST_ON_PROCESS_EVENT} {@link EventHolder}
+     * @return set of control with attached behaviors
      */
-    EventHolder getPostProcessEventHolder() {
-        if (postProcessEventHolder == null) {
-            postProcessEventHolder = createEventHolder(POST_ON_PROCESS_EVENT);
+    Set<Control> getBehaviorSourceSet() {
+        if (behaviorSourceSet == null) {
+            behaviorSourceSet = new LinkedHashSet<Control>();
         }
-        return postProcessEventHolder;
+        return behaviorSourceSet;
     }
 
     /**
@@ -332,107 +490,6 @@ public class ActionEventDispatcher {
     // Inner Classes ----------------------------------------------------------
 
     /**
-     * Holds the list of listeners and event sources.
-     */
-    public class EventHolder {
-
-        /** The EventHolder's event. */
-        protected int event;
-
-        /** The list of registered event sources. */
-        private List<Control> eventSourceList;
-
-        /** The list of registered event listeners. */
-        private List<ActionListener> eventListenerList;
-
-        // ------------------------------------------------------- Constructors
-
-        /**
-         * Create a new EventHolder for the given event.
-         *
-         * @param event the EventHolder's event
-         */
-        public EventHolder(int event) {
-            this.event = event;
-        }
-
-        /**
-         * Register the event source and event ActionListener to be fired in the
-         * specified event.
-         *
-         * @param source the action event source
-         * @param listener the event action listener
-         */
-        public void registerActionEvent(Control source, ActionListener listener) {
-            Validate.notNull(source, "Null source parameter");
-            Validate.notNull(listener, "Null listener parameter");
-
-            getEventSourceList().add(source);
-            getEventListenerList().add(listener);
-        }
-
-        /**
-         * Checks if any Action Events have been registered.
-         *
-         * @return true if the dispatcher has any Action Events registered
-         */
-        public boolean hasActionEvents() {
-            return !(eventListenerList == null || eventListenerList.isEmpty());
-        }
-
-        /**
-         * Return the list of event listeners.
-         *
-         * @return list of event listeners
-         */
-        public List<ActionListener> getEventListenerList() {
-            if (eventListenerList == null) {
-                eventListenerList = new ArrayList<ActionListener>();
-            }
-            return eventListenerList;
-        }
-
-        /**
-         * Return the list of event sources.
-         *
-         * @return list of event sources
-         */
-        public List<Control> getEventSourceList() {
-            if (eventSourceList == null) {
-                eventSourceList = new ArrayList<Control>();
-            }
-            return eventSourceList;
-        }
-
-        /**
-         * Clear the events.
-         */
-        public void clear() {
-            if (hasActionEvents()) {
-                getEventSourceList().clear();
-                getEventListenerList().clear();
-            }
-        }
-
-        /**
-         * Fire all the registered action events and return true if the page should
-         * continue processing.
-         *
-         * @param context the page request context
-         * @return true if the page should continue processing or false otherwise
-         */
-        public boolean fireActionEvents(Context context) {
-
-            if (!hasActionEvents()) {
-                return true;
-            }
-
-            return ActionEventDispatcher.this.fireActionEvents(context,
-                getEventSourceList(), getEventListenerList(), event);
-        }
-    }
-
-    /**
      * Provides an unsynchronized Stack.
      */
     static class DispatcherStack extends ArrayList<ActionEventDispatcher> {

Added: click/trunk/click/framework/src/org/apache/click/CallbackDispatcher.java
URL: http://svn.apache.org/viewvc/click/trunk/click/framework/src/org/apache/click/CallbackDispatcher.java?rev=953972&view=auto
==============================================================================
--- click/trunk/click/framework/src/org/apache/click/CallbackDispatcher.java (added)
+++ click/trunk/click/framework/src/org/apache/click/CallbackDispatcher.java Sat Jun 12 09:36:42 2010
@@ -0,0 +1,336 @@
+/*
+ * 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.click;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import org.apache.click.service.ConfigService;
+import org.apache.click.service.LogService;
+import org.apache.commons.lang.Validate;
+
+/**
+ * Provides a Control event callback dispatcher.
+ *
+ * TODO: javadoc
+ */
+public class CallbackDispatcher {
+
+    // -------------------------------------------------------------- Constants
+
+    /** The thread local dispatcher holder. */
+    private static final ThreadLocal THREAD_LOCAL_DISPATCHER = new ThreadLocal();
+
+    // -------------------------------------------------------------- Variables
+
+    /** The set of registered behavior enabled controls. */
+    Set<Control> behaviorEnabledControls;
+
+    /** The list of registered callbacks. */
+    List<CallbackHolder> callbacks;
+
+    /** The application log service. */
+    LogService logger;
+
+    // Constructors -----------------------------------------------------------
+
+    public CallbackDispatcher(ConfigService configService) {
+        this.logger = configService.getLogService();
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+    public static void registerBehavior(Control control) {
+        CallbackDispatcher instance = getThreadLocalDispatcher();
+        instance.internalRegisterBehavior(control);
+    }
+
+    public static void registerCallback(Control control, Callback callback) {
+        CallbackDispatcher instance = getThreadLocalDispatcher();
+        instance.internalRegisterCallback(control, callback);
+    }
+
+    // protected methods ------------------------------------------------------
+
+    /**
+     * Allow the dispatcher to handle the error that occurred.
+     *
+     * @param throwable the error which occurred during processing
+     */
+    protected void errorOccurred(Throwable throwable) {
+        getBehaviorEnabledControls().clear();
+        getCallbacks().clear();
+    }
+
+    // ------------------------------------------------ package private methods
+
+    /**
+     * Register the behavior source control.
+     *
+     * @param source the behavior source control
+     */
+    void internalRegisterBehavior(Control source) {
+        Validate.notNull(source, "Null source parameter");
+        getBehaviorEnabledControls().add(source);
+    }
+
+    /**
+     * Register the behavior source control.
+     *
+     * @param source the behavior source control
+     */
+    void internalRegisterCallback(Control source, Callback callback) {
+        Validate.notNull(source, "Null source parameter");
+        Validate.notNull(callback, "Null callback parameter");
+
+        CallbackHolder callbackHolder = new CallbackHolder(source, callback);
+        getCallbacks().add(callbackHolder);
+    }
+
+    void processBeforeResponse(Context context) {
+        if (hasBehaviorEnabledControls()) {
+            for(Control control : getBehaviorEnabledControls()) {
+                List<Behavior> behaviors = control.getBehaviors();
+                for (Behavior behavior : behaviors) {
+                    behavior.preResponse(control);
+                }
+            }
+        }
+
+        if (hasCallbacks()) {
+            for (CallbackHolder callbackHolder : getCallbacks()) {
+                Callback callback = callbackHolder.getCallback();
+                Control control = callbackHolder.getControl();
+                callback.preResponse(control);
+            }
+        }
+    }
+
+    void processBeforeGetHeadElements(Context context) {
+        if (hasBehaviorEnabledControls()) {
+            for(Control control : getBehaviorEnabledControls()) {
+                List<Behavior> behaviors = control.getBehaviors();
+                for (Behavior behavior : behaviors) {
+                    behavior.preGetHeadElements(control);
+                }
+            }
+        }
+
+        if (hasCallbacks()) {
+            for (CallbackHolder callbackHolder : getCallbacks()) {
+                Callback callback = callbackHolder.getCallback();
+                Control control = callbackHolder.getControl();
+                callback.preGetHeadElements(control);
+            }
+        }
+    }
+
+    void processPreDestroy(Context context) {
+        if (hasBehaviorEnabledControls()) {
+            for(Control control : getBehaviorEnabledControls()) {
+                List<Behavior> behaviors = control.getBehaviors();
+                for (Behavior behavior : behaviors) {
+                    behavior.preDestroy(control);
+                }
+            }
+        }
+
+        if (hasCallbacks()) {
+            for (CallbackHolder callbackHolder : getCallbacks()) {
+                Callback callback = callbackHolder.getCallback();
+                Control control = callbackHolder.getControl();
+                callback.preDestroy(control);
+            }
+        }
+    }
+
+    /**
+     * Checks if any control callbacks have been registered.
+     */
+    boolean hasBehaviorEnabledControls() {
+        if (behaviorEnabledControls == null || behaviorEnabledControls.isEmpty()) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Return the set of behavior enabled controls.
+     *
+     * @return the set of behavior enabled controls.
+     */
+    Set<Control> getBehaviorEnabledControls() {
+        if (behaviorEnabledControls == null) {
+            behaviorEnabledControls = new LinkedHashSet<Control>();
+        }
+        return behaviorEnabledControls;
+    }
+
+    /**
+     * Checks if any control callbacks have been registered.
+     */
+    boolean hasCallbacks() {
+        if (callbacks == null || callbacks.isEmpty()) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Return the set of registered callbacks.
+     *
+     * @return set of registered callbacks
+     */
+    List<CallbackHolder> getCallbacks() {
+        if (callbacks == null) {
+            callbacks = new ArrayList<CallbackHolder>();
+        }
+        return callbacks;
+    }
+
+    static CallbackDispatcher getThreadLocalDispatcher() {
+        return getDispatcherStack().peek();
+    }
+
+    /**
+     * Adds the specified CallbackDispatcher on top of the dispatcher stack.
+     *
+     * @param callbackDispatcher the CallbackDispatcher to add
+     */
+    static void pushThreadLocalDispatcher(CallbackDispatcher callbackDispatcher) {
+        getDispatcherStack().push(callbackDispatcher);
+    }
+
+    /**
+     * Remove and return the callbackDispatcher instance on top of the
+     * dispatcher stack.
+     *
+     * @return the callbackDispatcher instance on top of the dispatcher stack
+     */
+    static CallbackDispatcher popThreadLocalDispatcher() {
+        DispatcherStack dispatcherStack = getDispatcherStack();
+        CallbackDispatcher callbackDispatcher = dispatcherStack.pop();
+
+        if (dispatcherStack.isEmpty()) {
+            THREAD_LOCAL_DISPATCHER.set(null);
+        }
+
+        return callbackDispatcher;
+    }
+
+    static DispatcherStack getDispatcherStack() {
+        DispatcherStack dispatcherStack = (DispatcherStack) THREAD_LOCAL_DISPATCHER.get();
+
+        if (dispatcherStack == null) {
+            dispatcherStack = new DispatcherStack(2);
+            THREAD_LOCAL_DISPATCHER.set(dispatcherStack);
+        }
+
+        return dispatcherStack;
+    }
+
+    /**
+     * Provides an unsynchronized Stack.
+     */
+    static class DispatcherStack extends ArrayList {
+
+        /** Serialization version indicator. */
+        private static final long serialVersionUID = 1L;
+
+        /**
+         * Create a new DispatcherStack with the given initial capacity.
+         *
+         * @param initialCapacity specify initial capacity of this stack
+         */
+        private DispatcherStack(int initialCapacity) {
+            super(initialCapacity);
+        }
+
+        /**
+         * Pushes the CallbackDispatcher onto the top of this stack.
+         *
+         * @param callbackDispatcher the CallbackDispatcher to push onto this stack
+         * @return the CallbackDispatcher pushed on this stack
+         */
+        private CallbackDispatcher push(CallbackDispatcher callbackDispatcher) {
+            add(callbackDispatcher);
+
+            return callbackDispatcher;
+        }
+
+        /**
+         * Removes and return the CallbackDispatcher at the top of this stack.
+         *
+         * @return the CallbackDispatcher at the top of this stack
+         */
+        private CallbackDispatcher pop() {
+            CallbackDispatcher callbackDispatcher = peek();
+
+            remove(size() - 1);
+
+            return callbackDispatcher;
+        }
+
+        /**
+         * Looks at the CallbackDispatcher at the top of this stack without
+         * removing it.
+         *
+         * @return the CallbackDispatcher at the top of this stack
+         */
+        private CallbackDispatcher peek() {
+            int length = size();
+
+            if (length == 0) {
+                String msg = "No CallbackDispatcher available on ThreadLocal Dispatcher Stack";
+                throw new RuntimeException(msg);
+            }
+
+            return (CallbackDispatcher) get(length - 1);
+        }
+    }
+
+    static class CallbackHolder {
+
+        private Callback callback;
+
+        private Control control;
+
+        public CallbackHolder(Control control, Callback callback) {
+            this.control = control;
+            this.callback = callback;
+        }
+
+        public Callback getCallback() {
+            return callback;
+        }
+
+        public void setCallback(Callback callback) {
+            this.callback = callback;
+        }
+
+        public Control getControl() {
+            return control;
+        }
+
+        public void setControl(Control control) {
+            this.control = control;
+        }
+    }
+}

Modified: click/trunk/click/framework/src/org/apache/click/ClickServlet.java
URL: http://svn.apache.org/viewvc/click/trunk/click/framework/src/org/apache/click/ClickServlet.java?rev=953972&r1=953971&r2=953972&view=diff
==============================================================================
--- click/trunk/click/framework/src/org/apache/click/ClickServlet.java (original)
+++ click/trunk/click/framework/src/org/apache/click/ClickServlet.java Sat Jun 12 09:36:42 2010
@@ -20,6 +20,7 @@ package org.apache.click;
 
 import java.io.IOException;
 import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
 import java.io.Writer;
 import java.lang.reflect.Field;
 import java.util.Collections;
@@ -339,6 +340,10 @@ public class ClickServlet extends HttpSe
             // Bind ActionEventDispatcher to current thread
             ActionEventDispatcher.pushThreadLocalDispatcher(eventDispatcher);
 
+            CallbackDispatcher callbackDispatcher = createCallbackDispatcher();
+            // Bind CallbackDispatcher to current thread
+            CallbackDispatcher.pushThreadLocalDispatcher(callbackDispatcher);
+
             Context context = createContext(request, response, isPost);
             // Bind context to current thread
             Context.pushThreadLocalContext(context);
@@ -419,6 +424,7 @@ public class ClickServlet extends HttpSe
                 if (request.getAttribute(MOCK_MODE_ENABLED) == null) {
                     Context.popThreadLocalContext();
                 }
+                CallbackDispatcher.popThreadLocalDispatcher();
                 ActionEventDispatcher.popThreadLocalDispatcher();
             }
         }
@@ -449,6 +455,12 @@ public class ClickServlet extends HttpSe
         HttpServletResponse response, boolean isPost, Throwable exception,
         Class<? extends Page> pageClass) {
 
+        if (isAjaxRequest(request)) {
+            handleAjaxException(request, response, isPost, exception, pageClass);
+            // Exit after handling ajax exception
+            return;
+        }
+
         if (exception instanceof TemplateException) {
             TemplateException te = (TemplateException) exception;
             if (!te.isParseError()) {
@@ -531,6 +543,8 @@ public class ClickServlet extends HttpSe
      * <p/>
      * This method does not invoke the "onDestroy()" callback method.
      *
+     * @see #processPageEvents(org.apache.click.Page, org.apache.click.Context)
+     *
      * @param page the Page to process
      * @throws Exception if an error occurs
      */
@@ -538,12 +552,31 @@ public class ClickServlet extends HttpSe
     protected void processPage(Page page) throws Exception {
 
         final Context context = page.getContext();
-        final boolean isPost = context.isPost();
 
         PageImports pageImports = createPageImports(page);
         page.setPageImports(pageImports);
 
+        if (context.isAjaxRequest()) {
+            processAjaxPageEvents(page, context);
+        } else {
+            processPageEvents(page, context);
+        }
+    }
+
+    /**
+     * Process the given page events, invoking the "on" event callback methods
+     * and directing the response.
+     * <p/>
+     * This method does not invoke the "onDestroy()" callback method.
+     *
+     * @param page the Page which events to process
+     * @param context the request context
+     * @throws Exception if an error occurs
+     */
+    protected void processPageEvents(Page page, Context context) throws Exception {
+
         ActionEventDispatcher eventDispatcher = ActionEventDispatcher.getThreadLocalDispatcher();
+        CallbackDispatcher callbackDispatcher = CallbackDispatcher.getThreadLocalDispatcher();
 
         boolean errorOccurred = page instanceof ErrorPage;
         // Support direct access of click-error.htm
@@ -551,8 +584,9 @@ public class ClickServlet extends HttpSe
             ErrorPage errorPage = (ErrorPage) page;
             errorPage.setMode(configService.getApplicationMode());
 
-            // Notify the dispatcher of the error
+            // Notify the dispatcher and callbackRegistry of the error
             eventDispatcher.errorOccurred(errorPage.getError());
+            callbackDispatcher.errorOccurred(errorPage.getError());
         }
 
         boolean continueProcessing = performOnSecurityCheck(page, context);
@@ -574,12 +608,14 @@ public class ClickServlet extends HttpSe
             continueProcessing = performOnProcess(page, context, eventDispatcher);
 
             if (continueProcessing) {
-                performOnPostOrGet(page, context, isPost);
+                performOnPostOrGet(page, context, context.isPost());
 
                 performOnRender(page, context);
             }
         }
 
+        callbackDispatcher.processBeforeResponse(context);
+        callbackDispatcher.processBeforeGetHeadElements(context);
         performRender(page, context, partial);
     }
 
@@ -656,25 +692,31 @@ public class ClickServlet extends HttpSe
             for (int i = 0, size = controls.size(); i < size; i++) {
                 Control control = controls.get(i);
 
+                int initialListenerCount = 0;
+                if (logger.isTraceEnabled()) {
+                    initialListenerCount = eventDispatcher.getEventSourceList().size();
+                }
+
                 boolean onProcessResult = control.onProcess();
                 if (!onProcessResult) {
                     continueProcessing = false;
                 }
 
                 if (logger.isTraceEnabled()) {
-                    String controlClassName = control.getClass().getName();
-                    controlClassName = controlClassName.substring(
-                        controlClassName.lastIndexOf('.') + 1);
+                    String controlClassName = ClassUtils.getShortClassName(control.getClass());
 
                     String msg = "   invoked: '" + control.getName() + "' "
                         + controlClassName + ".onProcess() : " + onProcessResult;
                     logger.trace(msg);
+
+                    if (initialListenerCount != eventDispatcher.getEventSourceList().size()) {
+                        logger.trace("    listener was registered while processing control");
+                    }
                 }
             }
 
             if (continueProcessing) {
-                // Fire registered action events for the POST_ON_PROCESS event,
-                // which is also the default event
+                // Fire registered action events
                 continueProcessing = eventDispatcher.fireActionEvents(context);
 
                 if (logger.isTraceEnabled()) {
@@ -752,6 +794,17 @@ public class ClickServlet extends HttpSe
      *
      * @param page page to render
      * @param context the request context
+     * @throws java.lang.Exception if error occurs
+     */
+    protected void performRender(Page page, Context context) throws Exception {
+        performRender(page, context, null);
+    }
+
+    /**
+     * Performs rendering of the specified page.
+     *
+     * @param page page to render
+     * @param context the request context
      * @param partial the partial response object
      * @throws java.lang.Exception if error occurs
      */
@@ -804,7 +857,7 @@ public class ClickServlet extends HttpSe
             }
 
         } else if (partial != null) {
-            partial.render(context);
+            renderPartial(partial, context);
 
         } else if (page.getPath() != null) {
             // Render template unless the request was a page action. This check
@@ -860,7 +913,7 @@ public class ClickServlet extends HttpSe
 
         response.setContentType(page.getContentType());
 
-        Writer writer = getWriter(context);
+        Writer writer = getWriter(response);
 
         if (page.getHeaders() != null) {
             setPageResponseHeaders(response, page.getHeaders());
@@ -935,6 +988,43 @@ public class ClickServlet extends HttpSe
     }
 
     /**
+     * Render the given Partial response. If the partial is null, nothing is
+     * rendered.
+     *
+     * @param partial the partial resopnse to render
+     * @param context the request context
+     */
+    protected void renderPartial(Partial partial, Context context) {
+        if (partial == null) {
+            return;
+        }
+
+        long startTime = System.currentTimeMillis();
+
+        partial.render(context);
+
+        if (!configService.isProductionMode()) {
+            HtmlStringBuffer buffer = new HtmlStringBuffer(50);
+            if (logger.isTraceEnabled()) {
+                buffer.append("   ");
+            }
+
+            buffer.append("renderPartial (");
+            buffer.append(partial.getContentType());
+            buffer.append(")");
+            String template = partial.getTemplate();
+            if (template != null) {
+                buffer.append(": ");
+                buffer.append(template);
+            }
+            buffer.append(" - ");
+            buffer.append(System.currentTimeMillis() - startTime);
+            buffer.append(" ms");
+            logger.info(buffer);
+        }
+    }
+
+    /**
      * Return a new Page instance for the given request context. This method will
      * invoke {@link #initPage(String, Class, HttpServletRequest)} to create
      * the Page instance and then set the properties on the page.
@@ -1010,6 +1100,11 @@ public class ClickServlet extends HttpSe
     @SuppressWarnings("deprecation")
     protected void processPageOnDestroy(Page page, long startTime) {
         if (page.hasControls()) {
+
+            // notify callbacks of destroy event
+            // TODO check that exceptions don't unnecessarily trigger preDestroy
+            CallbackDispatcher.getThreadLocalDispatcher().processPreDestroy(page.getContext());
+
             List<Control> controls = page.getControls();
 
             for (int i = 0, size = controls.size(); i < size; i++) {
@@ -1539,7 +1634,16 @@ public class ClickServlet extends HttpSe
      * @return the new ActionEventDispatcher instance
      */
     protected ActionEventDispatcher createActionEventDispatcher() {
-        return new ActionEventDispatcher();
+        return new ActionEventDispatcher(configService);
+    }
+
+    /**
+     * Creates and returns a new CallbackDispatcher instance.
+     *
+     * @return the new CallbackDispatcher instance
+     */
+    protected CallbackDispatcher createCallbackDispatcher() {
+        return new CallbackDispatcher(configService);
     }
 
     /**
@@ -1627,6 +1731,195 @@ public class ClickServlet extends HttpSe
         return new PageImports(page);
     }
 
+    // TODO refactor Page events into its a separate Livecycle class. This will
+    // take some of the responsibility off ClickServlet and remove code duplication
+
+    /**
+     * Process the given page events, invoking the "on" event callback methods
+     * and directing the response.
+     *
+     * @param page the page which events to process
+     * @param context the request context
+     * @throws Exception if an error occurs
+     */
+    protected void processAjaxPageEvents(Page page, Context context) throws Exception {
+
+        ActionEventDispatcher eventDispatcher = ActionEventDispatcher.getThreadLocalDispatcher();
+
+        CallbackDispatcher callbackDispatcher = CallbackDispatcher.getThreadLocalDispatcher();
+
+        // TODO Ajax requests shouldn't reach this code path
+        // Support direct access of click-error.htm
+        if (page instanceof ErrorPage) {
+            ErrorPage errorPage = (ErrorPage) page;
+            errorPage.setMode(configService.getApplicationMode());
+
+            // Notify the dispatcher and registry of the error
+            eventDispatcher.errorOccurred(errorPage.getError());
+            callbackDispatcher.errorOccurred(errorPage.getError());
+        }
+
+        boolean continueProcessing = performOnSecurityCheck(page, context);
+
+        Partial partial = null;
+        if (continueProcessing) {
+
+            // Handle page method
+            String pageAction = context.getRequestParameter(Page.PAGE_ACTION);
+            if (pageAction != null) {
+                continueProcessing = false;
+                partial = ClickUtils.invokeAction(page, pageAction);
+                callbackDispatcher.processBeforeResponse(context);
+                callbackDispatcher.processBeforeGetHeadElements(context);
+
+                renderPartial(partial, context);
+            }
+        }
+
+        if (continueProcessing) {
+            performOnInit(page, context);
+
+            // TODO: Ajax doesn't support forward. Is it still necessary to
+            // check isForward?
+            if (callbackDispatcher.hasBehaviorEnabledControls() && !context.isForward()) {
+
+                // Perform onProcess for regsitered Ajax controls
+                processAjaxControls(context, eventDispatcher, callbackDispatcher);
+
+                // Fire behaviors registered during the onProcess event
+                // The target behavior will set the eventDispatcher partial instance
+                // to render
+                eventDispatcher.fireBehaviors(context);
+
+                // Ensure we execute the beforeResponse and beforeGetHeadElements
+                // for Ajax requests
+                callbackDispatcher.processBeforeResponse(context);
+                callbackDispatcher.processBeforeGetHeadElements(context);
+
+                partial = eventDispatcher.getPartial();
+
+                // Render the partial
+                renderPartial(partial, context);
+
+            } else {
+
+                continueProcessing = performOnProcess(page, context, eventDispatcher);
+
+                if (continueProcessing) {
+                    performOnPostOrGet(page, context, context.isPost());
+
+                    performOnRender(page, context);
+                }
+
+                callbackDispatcher.processBeforeResponse(context);
+                callbackDispatcher.processBeforeGetHeadElements(context);
+                performRender(page, context);
+            }
+        } else {
+            // If security check fails for an Ajax request, Click returns without
+            // any rendering. It is up to the user to render a Partial response
+            // in the onSecurityCheck event
+            // Note: this code path is also followed if a pageAction is invoked
+        }
+    }
+
+    /**
+     * Process all ajax controls and return true if the page should continue
+     * processing, false otherwise.
+     *
+     * @param context the request context
+     * @param callbackDispatcher the callback dispatcher
+     * @return true if the page should continue processing, false otherwise
+     */
+    protected boolean processAjaxControls(Context context,
+        ActionEventDispatcher eventDispatcher, CallbackDispatcher callbackDispatcher) {
+
+        boolean continueProcessing = true;
+
+        Control ajaxTarget = null;
+
+        for (Control control : callbackDispatcher.getBehaviorEnabledControls()) {
+
+            if (control.isAjaxTarget(context)) {
+                ajaxTarget = control;
+                // The first matching control will be processed. Multiple matching
+                // controls are not supported
+                break;
+            }
+        }
+
+        if (ajaxTarget != null) {
+            if (logger.isTraceEnabled()) {
+                HtmlStringBuffer buffer = new HtmlStringBuffer();
+                buffer.append("   invoked: '");
+                buffer.append(ajaxTarget.getName()).append("' ");
+                String className = ClassUtils.getShortClassName(ajaxTarget.getClass());
+                buffer.append(className);
+                buffer.append(".isAjaxTarget() : true (Ajax Control found)");
+                logger.trace(buffer.toString());
+            }
+
+            // Process the control
+            if (!ajaxTarget.onProcess()) {
+                continueProcessing = false;
+            }
+
+            // Log a trace if no behavior was registered after processing the control
+            if (logger.isTraceEnabled()) {
+
+                HtmlStringBuffer buffer = new HtmlStringBuffer();
+                String controlClassName = ClassUtils.getShortClassName(ajaxTarget.getClass());
+                buffer.append("   invoked: '");
+                buffer.append(ajaxTarget.getName());
+                buffer.append("' ").append(controlClassName);
+                buffer.append(".onProcess() : ").append(continueProcessing);
+                logger.trace(buffer.toString());
+
+                if (!eventDispatcher.hasBehaviorSourceSet()) {
+                    logger.trace("   *no* behavior was registered while processing control");
+                }
+            }
+        } else {
+
+            if (logger.isTraceEnabled()) {
+                String msg = "   *no* target control was found for Ajax request";
+                logger.trace(msg);
+            }
+        }
+
+        return continueProcessing;
+    }
+
+    protected void handleAjaxException(HttpServletRequest request,
+        HttpServletResponse response, boolean isPost, Throwable exception,
+        Class<? extends Page> pageClass) {
+
+        // If an exception occurs during an Ajax request, stream
+        // the exception instead of creating an ErrorPage
+        try {
+
+            PrintWriter writer = null;
+
+            try {
+                writer = getPrintWriter(response);
+                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+
+                // TODO: use return an ErrorReport instance instead?
+                writer.write("<div>\n");
+                exception.printStackTrace(writer);
+                writer.write("\n</div>");
+            } finally {
+                if (writer != null) {
+                    writer.flush();
+                }
+            }
+        } catch (Throwable error) {
+            logger.error(error.getMessage(), error);
+            throw new RuntimeException(error);
+        }
+        logger.error("handleException: ", exception);
+    }
+
     // ------------------------------------------------ Package Private Methods
 
     /**
@@ -1773,22 +2066,56 @@ public class ClickServlet extends HttpSe
     }
 
     /**
+     * TODO turn into utility method?
      * Retrieve a writer instance for the given context.
      *
-     * @param context the request context
+     * @param response the servlet response
      * @return a writer instance
      * @throws IOException if an input or output exception occurred
      */
-    Writer getWriter(Context context) throws IOException {
+    Writer getWriter(HttpServletResponse response) throws IOException {
         try {
 
-            return context.getResponse().getWriter();
+            return response.getWriter();
 
         } catch (IllegalStateException ignore) {
             // If writer cannot be retrieved fallback to OutputStream. CLK-644
-            return new OutputStreamWriter(context.getResponse().getOutputStream(),
-                 context.getResponse().getCharacterEncoding());
+            return new OutputStreamWriter(response.getOutputStream(),
+                 response.getCharacterEncoding());
+        }
+    }
+
+    /**
+     * Return a PrintWriter instance for the given response.
+     *
+     * @param response the servlet response
+     * @return a PrintWriter instance
+     */
+    PrintWriter getPrintWriter(HttpServletResponse response) throws IOException {
+        Writer writer = getWriter(response);
+        if (writer instanceof PrintWriter) {
+            return (PrintWriter) writer;
+        }
+        return new PrintWriter(writer);
+    }
+
+    /**
+     * Return true if this is an ajax request, false otherwise.
+     *
+     * @param request the servlet request
+     * @return true if this is an ajax request, false otherwise
+     */
+    boolean isAjaxRequest(HttpServletRequest request) {
+        boolean isAjaxRequest = false;
+        if (Context.hasThreadLocalContext()) {
+            Context context = Context.getThreadLocalContext();
+            if (context.isAjaxRequest()) {
+                isAjaxRequest = true;
+            }
+        } else {
+            isAjaxRequest = ClickUtils.isAjaxRequest(request);
         }
+        return isAjaxRequest;
     }
 
     // ---------------------------------------------------------- Inner Classes