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}