001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.ajax;
018
019import java.time.Duration;
020import java.util.List;
021import org.apache.wicket.Component;
022import org.apache.wicket.Page;
023import org.apache.wicket.WicketRuntimeException;
024import org.apache.wicket.ajax.attributes.AjaxAttributeName;
025import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
026import org.apache.wicket.ajax.attributes.AjaxRequestAttributes.Method;
027import org.apache.wicket.ajax.attributes.CallbackParameter;
028import org.apache.wicket.ajax.attributes.IAjaxCallListener;
029import org.apache.wicket.ajax.attributes.ThrottlingSettings;
030import org.apache.wicket.ajax.json.JSONFunction;
031import org.apache.wicket.ajax.json.JsonUtils;
032import org.apache.wicket.behavior.AbstractAjaxBehavior;
033import org.apache.wicket.markup.head.IHeaderResponse;
034import org.apache.wicket.markup.head.JavaScriptHeaderItem;
035import org.apache.wicket.markup.html.IComponentAwareHeaderContributor;
036import org.apache.wicket.markup.html.form.Form;
037import org.apache.wicket.protocol.http.WebApplication;
038import org.apache.wicket.request.Url;
039import org.apache.wicket.request.cycle.RequestCycle;
040import org.apache.wicket.request.resource.PackageResourceReference;
041import org.apache.wicket.request.resource.ResourceReference;
042import org.apache.wicket.resource.CoreLibrariesContributor;
043import org.apache.wicket.util.string.Strings;
044import com.github.openjson.JSONArray;
045import com.github.openjson.JSONException;
046import com.github.openjson.JSONObject;
047import org.slf4j.Logger;
048import org.slf4j.LoggerFactory;
049
050import javax.servlet.http.HttpServletRequest;
051
052/**
053 * The base class for Wicket's default AJAX implementation.
054 * 
055 * @since 1.2
056 * 
057 * @author Igor Vaynberg (ivaynberg)
058 * 
059 */
060public abstract class AbstractDefaultAjaxBehavior extends AbstractAjaxBehavior
061{
062        private static final long serialVersionUID = 1L;
063
064        private static final Logger LOG = LoggerFactory.getLogger(AbstractDefaultAjaxBehavior.class);
065
066        /** reference to the default indicator gif file. */
067        public static final ResourceReference INDICATOR = new PackageResourceReference(
068                AbstractDefaultAjaxBehavior.class, "indicator.gif");
069
070        private static final String DYNAMIC_PARAMETER_FUNCTION_SIGNATURE = "function(attrs)";
071        private static final String PRECONDITION_FUNCTION_SIGNATURE = "function(attrs)";
072        private static final String COMPLETE_HANDLER_FUNCTION_SIGNATURE = "function(attrs, jqXHR, textStatus)";
073        private static final String FAILURE_HANDLER_FUNCTION_SIGNATURE = "function(attrs, jqXHR, errorMessage, textStatus)";
074        private static final String SUCCESS_HANDLER_FUNCTION_SIGNATURE = "function(attrs, jqXHR, data, textStatus)";
075        private static final String AFTER_HANDLER_FUNCTION_SIGNATURE = "function(attrs)";
076        private static final String BEFORE_SEND_HANDLER_FUNCTION_SIGNATURE = "function(attrs, jqXHR, settings)";
077        private static final String BEFORE_HANDLER_FUNCTION_SIGNATURE = "function(attrs)";
078        private static final String INIT_HANDLER_FUNCTION_SIGNATURE = "function(attrs)";
079        private static final String DONE_HANDLER_FUNCTION_SIGNATURE = "function(attrs)";
080
081        /**
082         * Subclasses should call super.onBind()
083         * 
084         * @see org.apache.wicket.behavior.AbstractAjaxBehavior#onBind()
085         */
086        @Override
087        protected void onBind()
088        {
089                final Component component = getComponent();
090                
091                component.setOutputMarkupId(true);
092                
093                if (getStatelessHint(component))
094                {
095                        //generate behavior id
096                        component.getBehaviorId(this);
097                }
098        }
099
100        /**
101         * @see org.apache.wicket.behavior.AbstractAjaxBehavior#renderHead(Component,
102         *      org.apache.wicket.markup.head.IHeaderResponse)
103         */
104        @Override
105        public void renderHead(final Component component, final IHeaderResponse response)
106        {
107                super.renderHead(component, response);
108
109                CoreLibrariesContributor.contributeAjax(component.getApplication(), response);
110
111                RequestCycle requestCycle = component.getRequestCycle();
112                Url baseUrl = requestCycle.getUrlRenderer().getBaseUrl();
113                CharSequence ajaxBaseUrl = Strings.escapeMarkup(baseUrl.toString());
114                response.render(JavaScriptHeaderItem.forScript("Wicket.Ajax.baseUrl=\"" + ajaxBaseUrl
115                        + "\";", "wicket-ajax-base-url"));
116
117                renderExtraHeaderContributors(component, response);
118        }
119
120        /**
121         * Renders header contribution by IAjaxCallListener instances which additionally implement
122         * IComponentAwareHeaderContributor interface.
123         * 
124         * @param component
125         *            the component assigned to this behavior
126         * @param response
127         *            the current header response
128         */
129        private void renderExtraHeaderContributors(final Component component,
130                final IHeaderResponse response)
131        {
132                AjaxRequestAttributes attributes = getAttributes();
133
134                List<IAjaxCallListener> ajaxCallListeners = attributes.getAjaxCallListeners();
135                for (IAjaxCallListener ajaxCallListener : ajaxCallListeners)
136                {
137                        if (ajaxCallListener instanceof IComponentAwareHeaderContributor)
138                        {
139                                IComponentAwareHeaderContributor contributor = (IComponentAwareHeaderContributor)ajaxCallListener;
140                                contributor.renderHead(component, response);
141                        }
142                }
143        }
144
145        /**
146         * @return the Ajax settings for this behavior
147         * @since 6.0
148         */
149        protected final AjaxRequestAttributes getAttributes()
150        {
151                AjaxRequestAttributes attributes = new AjaxRequestAttributes();
152                WebApplication application = (WebApplication)getComponent().getApplication();
153                AjaxRequestTargetListenerCollection ajaxRequestTargetListeners = application
154                        .getAjaxRequestTargetListeners();
155                for (AjaxRequestTarget.IListener listener : ajaxRequestTargetListeners)
156                {
157                        listener.updateAjaxAttributes(this, attributes);
158                }
159                updateAjaxAttributes(attributes);
160                return attributes;
161        }
162
163        /**
164         * This method decides whether to continue processing or to abort the Ajax request when the method
165         * is different than the {@link AjaxRequestAttributes#getMethod()}'s method.
166         *
167         * @return response that can either abort or continue the processing of the Ajax request
168         */
169        protected Form.MethodMismatchResponse onMethodMismatch()
170        {
171                return Form.MethodMismatchResponse.CONTINUE;
172        }
173
174        /**
175         * Gives a chance to the specializations to modify the attributes.
176         * 
177         * @param attributes
178         * @since 6.0
179         */
180        protected void updateAjaxAttributes(AjaxRequestAttributes attributes)
181        {
182        }
183
184        /**
185         * <pre>
186         *                              {
187         *                                      u: 'editable-label?6-1.IBehaviorListener.0-text1-label',  // url
188         *                                      m: 'POST',          // method name. Default: 'GET'
189         *                                      c: 'label7',        // component id (String) or window for page
190         *                                      e: 'click',         // event name
191         *                                      sh: [],             // list of success handlers
192         *                                      fh: [],             // list of failure handlers
193         *                                      pre: [],            // list of preconditions. If empty set default : Wicket.$(settings{c}) !== null
194         *                                      ep: {},             // extra parameters
195         *                                      async: true|false,  // asynchronous XHR or not
196         *                                      ch: 'someName|d',   // AjaxChannel
197         *                                      i: 'indicatorId',   // indicator component id
198         *                                      ad: true,           // allow default
199         *                              }
200         * </pre>
201         * 
202         * @param component
203         *            the component with that behavior
204         * @return the attributes as string in JSON format
205         */
206        protected final CharSequence renderAjaxAttributes(final Component component)
207        {
208                AjaxRequestAttributes attributes = getAttributes();
209                return renderAjaxAttributes(component, attributes);
210        }
211
212        /**
213         * 
214         * @param component
215         * @param attributes
216         * @return the attributes as string in JSON format
217         */
218        protected final CharSequence renderAjaxAttributes(final Component component,
219                AjaxRequestAttributes attributes)
220        {
221                JSONObject attributesJson = new JSONObject();
222
223                try
224                {
225                        attributesJson.put(AjaxAttributeName.URL.jsonName(), getCallbackUrl());
226                        Method method = attributes.getMethod();
227                        if (Method.POST == method)
228                        {
229                                attributesJson.put(AjaxAttributeName.METHOD.jsonName(), method);
230                        }
231
232                        if (component instanceof Page == false)
233                        {
234                                String componentId = component.getMarkupId();
235                                attributesJson.put(AjaxAttributeName.MARKUP_ID.jsonName(), componentId);
236                        }
237
238                        String formId = attributes.getFormId();
239                        if (Strings.isEmpty(formId) == false)
240                        {
241                                attributesJson.put(AjaxAttributeName.FORM_ID.jsonName(), formId);
242                        }
243
244                        if (attributes.isMultipart())
245                        {
246                                attributesJson.put(AjaxAttributeName.IS_MULTIPART.jsonName(), true);
247                        }
248
249                        String submittingComponentId = attributes.getSubmittingComponentName();
250                        if (Strings.isEmpty(submittingComponentId) == false)
251                        {
252                                attributesJson.put(AjaxAttributeName.SUBMITTING_COMPONENT_NAME.jsonName(),
253                                        submittingComponentId);
254                        }
255
256                        CharSequence childSelector = attributes.getChildSelector();
257                        if (Strings.isEmpty(childSelector) == false)
258                        {
259                                attributesJson.put(AjaxAttributeName.CHILD_SELECTOR.jsonName(),
260                                                childSelector);
261                        }
262
263                        if (attributes.isSerializeRecursively())
264                        {
265                                attributesJson.put(AjaxAttributeName.SERIALIZE_RECURSIVELY.jsonName(), true);
266                        }
267
268                        String indicatorId = findIndicatorId();
269                        if (Strings.isEmpty(indicatorId) == false)
270                        {
271                                attributesJson.put(AjaxAttributeName.INDICATOR_ID.jsonName(), indicatorId);
272                        }
273
274                        for (IAjaxCallListener ajaxCallListener : attributes.getAjaxCallListeners())
275                        {
276                                if (ajaxCallListener != null)
277                                {
278                                        CharSequence initHandler = ajaxCallListener.getInitHandler(component);
279                                        appendListenerHandler(initHandler, attributesJson,
280                                                AjaxAttributeName.INIT_HANDLER.jsonName(),
281                                                INIT_HANDLER_FUNCTION_SIGNATURE);
282
283                                                CharSequence beforeHandler = ajaxCallListener.getBeforeHandler(component);
284                                        appendListenerHandler(beforeHandler, attributesJson,
285                                                AjaxAttributeName.BEFORE_HANDLER.jsonName(),
286                                                BEFORE_HANDLER_FUNCTION_SIGNATURE);
287
288                                        CharSequence beforeSendHandler = ajaxCallListener
289                                                .getBeforeSendHandler(component);
290                                        appendListenerHandler(beforeSendHandler, attributesJson,
291                                                AjaxAttributeName.BEFORE_SEND_HANDLER.jsonName(),
292                                                BEFORE_SEND_HANDLER_FUNCTION_SIGNATURE);
293
294                                        CharSequence afterHandler = ajaxCallListener.getAfterHandler(component);
295                                        appendListenerHandler(afterHandler, attributesJson,
296                                                AjaxAttributeName.AFTER_HANDLER.jsonName(), AFTER_HANDLER_FUNCTION_SIGNATURE);
297
298                                        CharSequence successHandler = ajaxCallListener.getSuccessHandler(component);
299                                        appendListenerHandler(successHandler, attributesJson,
300                                                AjaxAttributeName.SUCCESS_HANDLER.jsonName(),
301                                                SUCCESS_HANDLER_FUNCTION_SIGNATURE);
302
303                                        CharSequence failureHandler = ajaxCallListener.getFailureHandler(component);
304                                        appendListenerHandler(failureHandler, attributesJson,
305                                                AjaxAttributeName.FAILURE_HANDLER.jsonName(),
306                                                FAILURE_HANDLER_FUNCTION_SIGNATURE);
307
308                                        CharSequence completeHandler = ajaxCallListener.getCompleteHandler(component);
309                                        appendListenerHandler(completeHandler, attributesJson,
310                                                AjaxAttributeName.COMPLETE_HANDLER.jsonName(),
311                                                COMPLETE_HANDLER_FUNCTION_SIGNATURE);
312
313                                        CharSequence precondition = ajaxCallListener.getPrecondition(component);
314                                        appendListenerHandler(precondition, attributesJson,
315                                                AjaxAttributeName.PRECONDITION.jsonName(), PRECONDITION_FUNCTION_SIGNATURE);
316
317                                        CharSequence doneHandler = ajaxCallListener.getDoneHandler(component);
318                                        appendListenerHandler(doneHandler, attributesJson,
319                                                AjaxAttributeName.DONE_HANDLER.jsonName(),
320                                                DONE_HANDLER_FUNCTION_SIGNATURE);
321
322                                }
323                        }
324
325                        JSONArray extraParameters = JsonUtils.asArray(attributes.getExtraParameters());
326
327                        if (extraParameters.length() > 0)
328                        {
329                                attributesJson.put(AjaxAttributeName.EXTRA_PARAMETERS.jsonName(), extraParameters);
330                        }
331
332                        List<CharSequence> dynamicExtraParameters = attributes.getDynamicExtraParameters();
333                        if (dynamicExtraParameters != null)
334                        {
335                                for (CharSequence dynamicExtraParameter : dynamicExtraParameters)
336                                {
337                                        JSONFunction function = getJsonFunction(DYNAMIC_PARAMETER_FUNCTION_SIGNATURE, dynamicExtraParameter);
338                                        attributesJson.append(AjaxAttributeName.DYNAMIC_PARAMETER_FUNCTION.jsonName(),
339                                                function);
340                                }
341                        }
342
343                        if (attributes.isAsynchronous() == false)
344                        {
345                                attributesJson.put(AjaxAttributeName.IS_ASYNC.jsonName(), false);
346                        }
347
348                        String[] eventNames = attributes.getEventNames();
349                        if (eventNames.length == 1)
350                        {
351                                attributesJson.put(AjaxAttributeName.EVENT_NAME.jsonName(), eventNames[0]);
352                        }
353                        else
354                        {
355                                for (String eventName : eventNames)
356                                {
357                                        attributesJson.append(AjaxAttributeName.EVENT_NAME.jsonName(), eventName);
358                                }
359                        }
360
361                        AjaxChannel channel = attributes.getChannel();
362                        if (channel != null && channel.equals(AjaxChannel.DEFAULT) == false)
363                        {
364                                attributesJson.put(AjaxAttributeName.CHANNEL.jsonName(), channel);
365                        }
366
367                        if (attributes.isPreventDefault())
368                        {
369                                attributesJson.put(AjaxAttributeName.IS_PREVENT_DEFAULT.jsonName(), true);
370                        }
371
372                        if (AjaxRequestAttributes.EventPropagation.STOP
373                                .equals(attributes.getEventPropagation()))
374                        {
375                                attributesJson.put(AjaxAttributeName.EVENT_PROPAGATION.jsonName(), "stop");
376                        }
377                        else if (AjaxRequestAttributes.EventPropagation.STOP_IMMEDIATE.equals(attributes
378                                .getEventPropagation()))
379                        {
380                                attributesJson.put(AjaxAttributeName.EVENT_PROPAGATION.jsonName(), "stopImmediate");
381                        }
382
383                        Duration requestTimeout = attributes.getRequestTimeout();
384                        if (requestTimeout != null)
385                        {
386                                attributesJson.put(AjaxAttributeName.REQUEST_TIMEOUT.jsonName(),
387                                        requestTimeout.toMillis());
388                        }
389
390                        boolean wicketAjaxResponse = attributes.isWicketAjaxResponse();
391                        if (wicketAjaxResponse == false)
392                        {
393                                attributesJson.put(AjaxAttributeName.IS_WICKET_AJAX_RESPONSE.jsonName(), false);
394                        }
395
396                        String dataType = attributes.getDataType();
397                        if (AjaxRequestAttributes.XML_DATA_TYPE.equals(dataType) == false)
398                        {
399                                attributesJson.put(AjaxAttributeName.DATATYPE.jsonName(), dataType);
400                        }
401
402                        ThrottlingSettings throttlingSettings = attributes.getThrottlingSettings();
403                        if (throttlingSettings != null)
404                        {
405                                JSONObject throttlingSettingsJson = new JSONObject();
406                                String throttleId = throttlingSettings.getId();
407                                if (throttleId == null)
408                                {
409                                        throttleId = component.getMarkupId();
410                                }
411                                throttlingSettingsJson.put(AjaxAttributeName.THROTTLING_ID.jsonName(), throttleId);
412                                throttlingSettingsJson.put(AjaxAttributeName.THROTTLING_DELAY.jsonName(),
413                                        throttlingSettings.getDelay().toMillis());
414                                if (throttlingSettings.getPostponeTimerOnUpdate())
415                                {
416                                        throttlingSettingsJson.put(
417                                                AjaxAttributeName.THROTTLING_POSTPONE_ON_UPDATE.jsonName(), true);
418                                }
419                                attributesJson.put(AjaxAttributeName.THROTTLING.jsonName(), throttlingSettingsJson);
420                        }
421
422                        postprocessConfiguration(attributesJson, component);
423                }
424                catch (JSONException e)
425                {
426                        throw new WicketRuntimeException(e);
427                }
428
429                String attributesAsJson = attributesJson.toString();
430
431                return attributesAsJson;
432        }
433
434        private void appendListenerHandler(final CharSequence handler, final JSONObject attributesJson,
435                final String propertyName, final String signature) throws JSONException
436        {
437                if (Strings.isEmpty(handler) == false)
438                {
439                        final JSONFunction function;
440                        if (handler instanceof JSONFunction)
441                        {
442                                function = (JSONFunction)handler;
443                        }
444                        else
445                        {
446                                function = getJsonFunction(signature, handler);
447                        }
448                        attributesJson.append(propertyName, function);
449                }
450        }
451
452        private JSONFunction getJsonFunction(String signature, CharSequence body) {
453                String func = signature + "{" + body + "}";
454                return new JSONFunction(func);
455        }
456
457        /**
458         * Gives a chance to modify the JSON attributesJson that is going to be used as attributes for
459         * the Ajax call.
460         * 
461         * @param attributesJson
462         *            the JSON object created by #renderAjaxAttributes()
463         * @param component
464         *            the component with the attached Ajax behavior
465         * @throws JSONException
466         *             thrown if an error occurs while modifying {@literal attributesJson} argument
467         */
468        protected void postprocessConfiguration(JSONObject attributesJson, Component component)
469                throws JSONException
470        {
471        }
472
473        /**
474         * @return javascript that will generate an ajax GET request to this behavior with its assigned
475         *         component
476         */
477        public CharSequence getCallbackScript()
478        {
479                return getCallbackScript(getComponent());
480        }
481
482        /**
483         * @param component
484         *            the component to use when generating the attributes
485         * @return script that can be used to execute this Ajax behavior.
486         */
487        // 'protected' because this method is intended to be called by other Behavior methods which
488        // accept the component as parameter
489        protected CharSequence getCallbackScript(final Component component)
490        {
491                CharSequence ajaxAttributes = renderAjaxAttributes(component);
492                return "Wicket.Ajax.ajax(" + ajaxAttributes + ");";
493        }
494
495        /**
496         * Generates a javascript function that can take parameters and performs an AJAX call which
497         * includes these parameters. The generated code looks like this:
498         * 
499         * <pre>
500         * function(param1, param2) {
501         *    var attrs = attrsJson;
502         *    var params = {'param1': param1, 'param2': param2};
503         *    attrs.ep = jQuery.extend(attrs.ep, params);
504         *    Wicket.Ajax.ajax(attrs);
505         * }
506         * </pre>
507         * 
508         * @param extraParameters
509         * @return A function that can be used as a callback function in javascript
510         */
511        public CharSequence getCallbackFunction(CallbackParameter... extraParameters)
512        {
513                StringBuilder sb = new StringBuilder();
514                sb.append("function (");
515                boolean first = true;
516                for (CallbackParameter curExtraParameter : extraParameters)
517                {
518                        if (curExtraParameter.getFunctionParameterName() != null)
519                        {
520                                if (!first)
521                                        sb.append(',');
522                                else
523                                        first = false;
524                                sb.append(curExtraParameter.getFunctionParameterName());
525                        }
526                }
527                sb.append(") {\n");
528                sb.append(getCallbackFunctionBody(extraParameters));
529                sb.append("}\n");
530                return sb;
531        }
532
533        /**
534         * Generates the body the {@linkplain #getCallbackFunction(CallbackParameter...) callback
535         * function}. To embed this code directly into a piece of javascript, make sure any context
536         * parameters are available as local variables, global variables or within the closure.
537         * 
538         * @param extraParameters
539         * @return The body of the {@linkplain #getCallbackFunction(CallbackParameter...) callback
540         *         function}.
541         */
542        public CharSequence getCallbackFunctionBody(CallbackParameter... extraParameters)
543        {
544                AjaxRequestAttributes attributes = getAttributes();
545                attributes.setEventNames();
546                CharSequence attrsJson = renderAjaxAttributes(getComponent(), attributes);
547                StringBuilder sb = new StringBuilder();
548                sb.append("var attrs = ");
549                sb.append(attrsJson);
550                sb.append(";\n");
551                JSONArray jsonArray = new JSONArray();
552                for (CallbackParameter curExtraParameter : extraParameters)
553                {
554                        if (curExtraParameter.getAjaxParameterName() != null)
555                        {
556                                try
557                                {
558                                        JSONObject object = new JSONObject();
559                                        object.put("name", curExtraParameter.getAjaxParameterName());
560                                        object.put("value", new JSONFunction(curExtraParameter.getAjaxParameterCode()));
561                                        jsonArray.put(object);
562                                }
563                                catch (JSONException e)
564                                {
565                                        throw new WicketRuntimeException(e);
566                                }
567                        }
568                }
569                sb.append("var params = ").append(jsonArray).append(";\n");
570                sb.append("attrs.").append(AjaxAttributeName.EXTRA_PARAMETERS)
571                                .append(" = params.concat(attrs.")
572                                .append(AjaxAttributeName.EXTRA_PARAMETERS).append(" || []);\n");
573                sb.append("Wicket.Ajax.ajax(attrs);\n");
574                return sb;
575        }
576
577        /**
578         * Finds the markup id of the indicator. The default search order is: component, behavior,
579         * component's parent hierarchy.
580         * 
581         * @return markup id or <code>null</code> if no indicator found
582         */
583        protected String findIndicatorId()
584        {
585                if (getComponent() instanceof IAjaxIndicatorAware)
586                {
587                        return ((IAjaxIndicatorAware)getComponent()).getAjaxIndicatorMarkupId();
588                }
589
590                if (this instanceof IAjaxIndicatorAware)
591                {
592                        return ((IAjaxIndicatorAware)this).getAjaxIndicatorMarkupId();
593                }
594
595                Component parent = getComponent().getParent();
596                while (parent != null)
597                {
598                        if (parent instanceof IAjaxIndicatorAware)
599                        {
600                                return ((IAjaxIndicatorAware)parent).getAjaxIndicatorMarkupId();
601                        }
602                        parent = parent.getParent();
603                }
604                return null;
605        }
606
607        @Override
608        public final void onRequest()
609        {
610                Form.MethodMismatchResponse methodMismatch = onMethodMismatch();
611                if (methodMismatch == Form.MethodMismatchResponse.ABORT)
612                {
613                        AjaxRequestAttributes attrs = getAttributes();
614                        String desiredMethod = attrs.getMethod().toString();
615                        String actualMethod = ((HttpServletRequest) RequestCycle.get().getRequest().getContainerRequest()).getMethod();
616                        if (!desiredMethod.equalsIgnoreCase(actualMethod))
617                        {
618                                LOG.debug("Ignoring the Ajax request because its method '{}' is different than the expected one '{}",
619                                          actualMethod, desiredMethod);
620                                return;
621                        }
622                }
623
624                WebApplication app = (WebApplication)getComponent().getApplication();
625                AjaxRequestTarget target = app.newAjaxRequestTarget(getComponent().getPage());
626
627                RequestCycle requestCycle = RequestCycle.get();
628                requestCycle.scheduleRequestHandlerAfterCurrent(target);
629
630                respond(target);
631        }
632
633        /**
634         * @param target
635         *            The AJAX target
636         */
637        protected abstract void respond(AjaxRequestTarget target);
638
639}