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.core.request.mapper; 018 019import java.util.ArrayList; 020import java.util.List; 021import java.util.Locale; 022 023import org.apache.wicket.IRequestListener; 024import org.apache.wicket.Session; 025import org.apache.wicket.core.request.handler.BookmarkableListenerRequestHandler; 026import org.apache.wicket.core.request.handler.BookmarkablePageRequestHandler; 027import org.apache.wicket.core.request.handler.IPageRequestHandler; 028import org.apache.wicket.core.request.handler.ListenerRequestHandler; 029import org.apache.wicket.core.request.handler.PageAndComponentProvider; 030import org.apache.wicket.core.request.handler.PageProvider; 031import org.apache.wicket.core.request.handler.RenderPageRequestHandler; 032import org.apache.wicket.protocol.http.PageExpiredException; 033import org.apache.wicket.protocol.http.WebApplication; 034import org.apache.wicket.request.IRequestHandler; 035import org.apache.wicket.request.IRequestHandlerDelegate; 036import org.apache.wicket.request.Request; 037import org.apache.wicket.request.Url; 038import org.apache.wicket.request.component.IRequestablePage; 039import org.apache.wicket.request.cycle.RequestCycle; 040import org.apache.wicket.request.http.WebRequest; 041import org.apache.wicket.request.mapper.info.ComponentInfo; 042import org.apache.wicket.request.mapper.info.PageComponentInfo; 043import org.apache.wicket.request.mapper.info.PageInfo; 044import org.apache.wicket.request.mapper.parameter.INamedParameters; 045import org.apache.wicket.request.mapper.parameter.IPageParametersEncoder; 046import org.apache.wicket.request.mapper.parameter.PageParameters; 047import org.apache.wicket.request.mapper.parameter.PageParametersEncoder; 048import org.apache.wicket.util.lang.Args; 049 050/** 051 * Abstract encoder for Bookmarkable, Hybrid and BookmarkableListener URLs. 052 * 053 * @author Matej Knopp 054 */ 055public abstract class AbstractBookmarkableMapper extends AbstractComponentMapper 056{ 057 058 /** 059 * A flag that is used when comparing the mounted paths' segments against 060 * the request's url ones. 061 * 062 * @see #setCaseSensitiveMatch(boolean) 063 */ 064 private boolean isCaseSensitive = true; 065 066 /** 067 * Represents information stored in URL. 068 * 069 * @author Matej Knopp 070 */ 071 protected static final class UrlInfo 072 { 073 private final PageComponentInfo pageComponentInfo; 074 private final PageParameters pageParameters; 075 private final Class<? extends IRequestablePage> pageClass; 076 077 /** 078 * Construct. 079 * 080 * @param pageComponentInfo 081 * optional parameter providing the page instance and component information 082 * @param pageClass 083 * mandatory parameter 084 * @param pageParameters 085 * optional parameter providing pageParameters 086 */ 087 public UrlInfo(PageComponentInfo pageComponentInfo, 088 Class<? extends IRequestablePage> pageClass, PageParameters pageParameters) 089 { 090 Args.notNull(pageClass, "pageClass"); 091 092 this.pageComponentInfo = pageComponentInfo; 093 this.pageParameters = cleanPageParameters(pageParameters); 094 095 this.pageClass = pageClass; 096 } 097 098 /** 099 * Cleans the original parameters from entries used by Wicket internals. 100 * 101 * @param originalParameters 102 * the current request's non-modified parameters 103 * @return all parameters but Wicket internal ones 104 */ 105 private PageParameters cleanPageParameters(final PageParameters originalParameters) 106 { 107 PageParameters cleanParameters = null; 108 if (originalParameters != null) 109 { 110 cleanParameters = new PageParameters(originalParameters); 111 112 // WICKET-4038: Ajax related parameters are set by wicket-ajax.js when needed. 113 // They shouldn't be propagated to the next requests 114 cleanParameters.remove(WebRequest.PARAM_AJAX); 115 cleanParameters.remove(WebRequest.PARAM_AJAX_BASE_URL); 116 cleanParameters.remove(WebRequest.PARAM_AJAX_REQUEST_ANTI_CACHE); 117 118 if (cleanParameters.isEmpty()) 119 { 120 cleanParameters = null; 121 } 122 } 123 return cleanParameters; 124 } 125 126 /** 127 * @return PageComponentInfo instance or <code>null</code> 128 */ 129 public PageComponentInfo getPageComponentInfo() 130 { 131 return pageComponentInfo; 132 } 133 134 /** 135 * @return page class 136 */ 137 public Class<? extends IRequestablePage> getPageClass() 138 { 139 return pageClass; 140 } 141 142 /** 143 * @return PageParameters instance (never <code>null</code>) 144 */ 145 public PageParameters getPageParameters() 146 { 147 return pageParameters; 148 } 149 } 150 151 protected final List<MountPathSegment> pathSegments; 152 153 protected final String[] mountSegments; 154 155 protected final IPageParametersEncoder pageParametersEncoder; 156 157 /** 158 * Construct. 159 */ 160 public AbstractBookmarkableMapper() 161 { 162 this("notUsed", new PageParametersEncoder()); 163 } 164 165 public AbstractBookmarkableMapper(String mountPath, IPageParametersEncoder pageParametersEncoder) 166 { 167 Args.notEmpty(mountPath, "mountPath"); 168 169 this.pageParametersEncoder = Args.notNull(pageParametersEncoder, "pageParametersEncoder"); 170 mountSegments = getMountSegments(mountPath); 171 pathSegments = getPathSegments(mountSegments); 172 } 173 174 /** 175 * Parse the given request to an {@link UrlInfo} instance. 176 * 177 * @param request 178 * @return UrlInfo instance or <code>null</code> if this encoder can not handle the request 179 */ 180 protected abstract UrlInfo parseRequest(Request request); 181 182 /** 183 * Builds URL for the given {@link UrlInfo} instance. The URL this method produces must be 184 * parseable by the {@link #parseRequest(Request)} method. 185 * 186 * @param info 187 * @return Url result URL 188 */ 189 protected abstract Url buildUrl(UrlInfo info); 190 191 /** 192 * Indicates whether hybrid {@link RenderPageRequestHandler} URL for page will be generated only 193 * if page has been created with bookmarkable URL. 194 * <p> 195 * For generic bookmarkable encoders this method should return <code>true</code>. For explicit 196 * (mounted) encoders this method should return <code>false</code> 197 * 198 * @return <code>true</code> if hybrid URL requires page created bookmarkable, 199 * <code>false</code> otherwise. 200 */ 201 protected abstract boolean pageMustHaveBeenCreatedBookmarkable(); 202 203 @Override 204 public int getCompatibilityScore(Request request) 205 { 206 if (urlStartsWith(request.getUrl(), mountSegments)) 207 { 208 /* see WICKET-5056 - alter score with pathSegment type */ 209 int countOptional = 0; 210 int fixedSegments = 0; 211 for (MountPathSegment pathSegment : pathSegments) 212 { 213 fixedSegments += pathSegment.getFixedPartSize(); 214 countOptional += pathSegment.getOptionalParameters(); 215 } 216 return mountSegments.length - countOptional + fixedSegments; 217 } 218 else 219 { 220 return 0; 221 } 222 } 223 224 /** 225 * Creates a {@code IRequestHandler} that processes a bookmarkable request. 226 * 227 * @param pageClass 228 * @param pageParameters 229 * @return a {@code IRequestHandler} capable of processing the bookmarkable request. 230 */ 231 protected IRequestHandler processBookmarkable(Class<? extends IRequestablePage> pageClass, 232 PageParameters pageParameters) 233 { 234 PageProvider provider = new PageProvider(pageClass, pageParameters); 235 provider.setPageSource(getContext()); 236 return new RenderPageRequestHandler(provider); 237 } 238 239 /** 240 * Creates a {@code IRequestHandler} that processes a hybrid request. When the page identified 241 * by {@code pageInfo} was not available, the request should be treated as a bookmarkable 242 * request. 243 * 244 * @param pageInfo 245 * @param pageClass 246 * @param pageParameters 247 * @param renderCount 248 * @return a {@code IRequestHandler} capable of processing the hybrid request. 249 */ 250 protected IRequestHandler processHybrid(PageInfo pageInfo, 251 Class<? extends IRequestablePage> pageClass, PageParameters pageParameters, 252 Integer renderCount) 253 { 254 PageProvider provider = new PageProvider(pageInfo.getPageId(), pageClass, pageParameters, 255 renderCount); 256 provider.setPageSource(getContext()); 257 258 checkExpiration(provider, pageInfo); 259 260 /** 261 * https://issues.apache.org/jira/browse/WICKET-5734 262 * */ 263 PageParameters constructionPageParameters = provider.hasPageInstance() ? 264 provider.getPageInstance().getPageParameters() : new PageParameters(); 265 266 if (PageParameters.equals(constructionPageParameters, pageParameters) == false) 267 { 268 // create a fresh page instance because the request page parameters are different than the ones 269 // when the resolved page by id has been created 270 return new RenderPageRequestHandler(new PageProvider(pageClass, pageParameters)); 271 } 272 return new RenderPageRequestHandler(provider); 273 } 274 275 boolean getRecreateMountedPagesAfterExpiry() 276 { 277 return WebApplication.get().getPageSettings().getRecreateBookmarkablePagesAfterExpiry(); 278 } 279 280 /** 281 * Creates a {@code IRequestHandler} that notifies an {@link IRequestListener}. 282 * 283 * @param pageComponentInfo 284 * @param pageClass 285 * @param pageParameters 286 * @return a {@code IRequestHandler} that notifies an {@link IRequestListener}. 287 */ 288 protected IRequestHandler processListener(PageComponentInfo pageComponentInfo, 289 Class<? extends IRequestablePage> pageClass, PageParameters pageParameters) 290 { 291 PageInfo pageInfo = pageComponentInfo.getPageInfo(); 292 ComponentInfo componentInfo = pageComponentInfo.getComponentInfo(); 293 Integer renderCount = null; 294 295 if (componentInfo != null) 296 { 297 renderCount = componentInfo.getRenderCount(); 298 } 299 300 PageAndComponentProvider provider = new PageAndComponentProvider(pageInfo.getPageId(), 301 pageClass, pageParameters, renderCount, componentInfo.getComponentPath()); 302 303 provider.setPageSource(getContext()); 304 305 checkExpiration(provider, pageInfo); 306 307 return new ListenerRequestHandler(provider, componentInfo.getBehaviorId()); 308 } 309 310 private void checkExpiration(PageProvider provider, PageInfo pageInfo) 311 { 312 if (provider.wasExpired() && !getRecreateMountedPagesAfterExpiry()) 313 { 314 throw new PageExpiredException(String.format("Bookmarkable page with id '%d' has expired.", 315 pageInfo.getPageId())); 316 } 317 } 318 319 @Override 320 public IRequestHandler mapRequest(Request request) 321 { 322 UrlInfo urlInfo = parseRequest(request); 323 324 if (urlInfo != null) 325 { 326 PageComponentInfo info = urlInfo.getPageComponentInfo(); 327 Class<? extends IRequestablePage> pageClass = urlInfo.getPageClass(); 328 PageParameters pageParameters = urlInfo.getPageParameters(); 329 330 if (info == null) 331 { 332 // if there are is no page instance information 333 // then this is a simple bookmarkable URL 334 return processBookmarkable(pageClass, pageParameters); 335 } 336 else if (info.getPageInfo().getPageId() != null && info.getComponentInfo() == null) 337 { 338 // if there is page instance information in the URL but no component and listener 339 // interface then this is a hybrid URL - we need to try to reuse existing page 340 // instance 341 return processHybrid(info.getPageInfo(), pageClass, pageParameters, null); 342 } 343 else if (info.getComponentInfo() != null) 344 { 345 // with both page instance and component this is a request listener URL 346 return processListener(info, pageClass, pageParameters); 347 } 348 else if (info.getPageInfo().getPageId() == null) 349 { 350 return processBookmarkable(pageClass, pageParameters); 351 } 352 353 } 354 return null; 355 } 356 357 protected boolean checkPageInstance(IRequestablePage page) 358 { 359 return page != null && checkPageClass(page.getClass()); 360 } 361 362 protected boolean checkPageClass(Class<? extends IRequestablePage> pageClass) 363 { 364 return true; 365 } 366 367 @Override 368 public Url mapHandler(IRequestHandler requestHandler) 369 { 370 // TODO see if we can refactor this to remove dependency on instanceof checks below and 371 // eliminate the need for IRequestHandlerDelegate 372 while (requestHandler instanceof IRequestHandlerDelegate) 373 { 374 requestHandler = ((IRequestHandlerDelegate)requestHandler).getDelegateHandler(); 375 } 376 377 if (requestHandler instanceof BookmarkablePageRequestHandler) 378 { 379 // simple bookmarkable URL with no page instance information 380 BookmarkablePageRequestHandler handler = (BookmarkablePageRequestHandler)requestHandler; 381 382 if (!checkPageClass(handler.getPageClass())) 383 { 384 return null; 385 } 386 387 PageInfo info = new PageInfo(); 388 UrlInfo urlInfo = new UrlInfo(new PageComponentInfo(info, null), 389 handler.getPageClass(), handler.getPageParameters()); 390 391 return buildUrl(urlInfo); 392 } 393 else if (requestHandler instanceof RenderPageRequestHandler) 394 { 395 // possibly hybrid URL - bookmarkable URL with page instance information 396 // but only allowed if the page was created by bookmarkable URL 397 398 RenderPageRequestHandler handler = (RenderPageRequestHandler)requestHandler; 399 400 if (!checkPageClass(handler.getPageClass())) 401 { 402 return null; 403 } 404 405 if (!handler.getPageProvider().hasPageInstance()) 406 { 407 // no existing page instance available, don't bother creating new page instance 408 PageInfo info = new PageInfo(); 409 UrlInfo urlInfo = new UrlInfo(new PageComponentInfo(info, null), 410 handler.getPageClass(), handler.getPageParameters()); 411 412 return buildUrl(urlInfo); 413 } 414 415 IRequestablePage page = handler.getPage(); 416 417 if (checkPageInstance(page) && 418 (!pageMustHaveBeenCreatedBookmarkable() || page.wasCreatedBookmarkable())) 419 { 420 PageInfo info = getPageInfo(handler); 421 PageComponentInfo pageComponentInfo = new PageComponentInfo(info, null); 422 423 UrlInfo urlInfo = new UrlInfo(pageComponentInfo, page.getClass(), 424 handler.getPageParameters()); 425 return buildUrl(urlInfo); 426 } 427 else 428 { 429 return null; 430 } 431 432 } 433 else if (requestHandler instanceof BookmarkableListenerRequestHandler) 434 { 435 // request listener URL with page class information 436 BookmarkableListenerRequestHandler handler = (BookmarkableListenerRequestHandler)requestHandler; 437 Class<? extends IRequestablePage> pageClass = handler.getPageClass(); 438 439 if (!checkPageClass(pageClass)) 440 { 441 return null; 442 } 443 444 Integer renderCount = null; 445 if (handler.includeRenderCount()) 446 { 447 renderCount = handler.getRenderCount(); 448 } 449 450 PageInfo pageInfo = getPageInfo(handler); 451 ComponentInfo componentInfo = new ComponentInfo(renderCount, handler.getComponentPath(), handler.getBehaviorIndex()); 452 453 PageParameters parameters = getRecreateMountedPagesAfterExpiry() ? new PageParameters( 454 handler.getPage().getPageParameters()).mergeWith(handler.getPageParameters()) 455 : handler.getPageParameters(); 456 UrlInfo urlInfo = new UrlInfo(new PageComponentInfo(pageInfo, componentInfo), 457 pageClass, parameters); 458 return buildUrl(urlInfo); 459 } 460 461 return null; 462 } 463 464 protected final PageInfo getPageInfo(IPageRequestHandler handler) 465 { 466 Args.notNull(handler, "handler"); 467 468 Integer pageId = null; 469 if (handler.isPageInstanceCreated()) 470 { 471 IRequestablePage page = handler.getPage(); 472 473 if (page.isPageStateless() == false) 474 { 475 pageId = page.getPageId(); 476 } 477 } 478 479 return new PageInfo(pageId); 480 } 481 482 /** 483 * @return a new instance of {@link PageParameters} that will be passed to the page/resource 484 */ 485 protected PageParameters newPageParameters() 486 { 487 final PageParameters parameters = new PageParameters(); 488 parameters.setLocale(resolveLocale()); 489 return parameters; 490 } 491 492 /** 493 * Override {@link #resolveLocale()} to return the result of this method if you want to use 494 * the user's session or request locale for parsing numbers from the page parameters 495 * 496 * @return the Session or Request's locale to use for parsing any numbers in the request parameters 497 */ 498 protected Locale resolveUserLocale() 499 { 500 Locale locale = super.resolveLocale(); 501 if (Session.exists()) 502 { 503 locale = Session.get().getLocale(); 504 } 505 else 506 { 507 RequestCycle requestCycle = RequestCycle.get(); 508 if (requestCycle != null) 509 { 510 Request request = requestCycle.getRequest(); 511 if (request != null) 512 { 513 locale = request.getLocale(); 514 } 515 } 516 } 517 518 return locale; 519 } 520 521 protected static class MountPathSegment 522 { 523 private int segmentIndex; 524 private String fixedPart; 525 private int minParameters; 526 private int optionalParameters; 527 528 public MountPathSegment(int segmentIndex) 529 { 530 this.segmentIndex = segmentIndex; 531 } 532 533 public void setFixedPart(String fixedPart) 534 { 535 this.fixedPart = fixedPart; 536 } 537 538 public void addRequiredParameter() 539 { 540 minParameters++; 541 } 542 543 public void addOptionalParameter() 544 { 545 optionalParameters++; 546 } 547 548 public int getSegmentIndex() 549 { 550 return segmentIndex; 551 } 552 553 public String getFixedPart() 554 { 555 return fixedPart; 556 } 557 558 public int getMinParameters() 559 { 560 return minParameters; 561 } 562 563 public int getOptionalParameters() 564 { 565 return optionalParameters; 566 } 567 568 public int getMaxParameters() 569 { 570 return getOptionalParameters() + getMinParameters(); 571 } 572 573 public int getFixedPartSize() 574 { 575 return getFixedPart() == null ? 0 : 1; 576 } 577 578 @Override 579 public String toString() 580 { 581 return "(" + getSegmentIndex() + ") " + getMinParameters() + '-' + getMaxParameters() + 582 ' ' + (getFixedPart() == null ? "(end)" : getFixedPart()); 583 } 584 } 585 586 protected List<MountPathSegment> getPathSegments(String[] segments) 587 { 588 List<MountPathSegment> ret = new ArrayList<MountPathSegment>(); 589 int segmentIndex = 0; 590 MountPathSegment curPathSegment = new MountPathSegment(segmentIndex); 591 ret.add(curPathSegment); 592 for (String curSegment : segments) 593 { 594 if (isFixedSegment(curSegment)) 595 { 596 curPathSegment.setFixedPart(curSegment); 597 curPathSegment = new MountPathSegment(segmentIndex + 1); 598 ret.add(curPathSegment); 599 } 600 else if (getPlaceholder(curSegment) != null) 601 { 602 curPathSegment.addRequiredParameter(); 603 } 604 else 605 { 606 curPathSegment.addOptionalParameter(); 607 } 608 segmentIndex++; 609 } 610 return ret; 611 } 612 613 protected boolean isFixedSegment(String segment) 614 { 615 return getOptionalPlaceholder(segment) == null && getPlaceholder(segment) == null; 616 } 617 618 619 /** 620 * Extracts the PageParameters from URL if there are any 621 */ 622 protected PageParameters extractPageParameters(Request request, Url url) 623 { 624 int[] matchedParameters = getMatchedSegmentSizes(url); 625 626 int total = 0; 627 for (int curMatchSize : matchedParameters) 628 { 629 total += curMatchSize; 630 } 631 PageParameters pageParameters = extractPageParameters(request, total, pageParametersEncoder); 632 if (pageParameters != null) 633 { 634 pageParameters.setLocale(resolveLocale()); 635 } 636 637 int segmentIndex = 0; 638 for (int pathSegmentIndex = 0; pathSegmentIndex < pathSegments.size(); pathSegmentIndex++) 639 { 640 MountPathSegment pathSegment = pathSegments.get(pathSegmentIndex); 641 642 int totalAdded = 0; 643 int requiredAdded = 0; 644 for (int segmentParameterIndex = 0; segmentParameterIndex < pathSegment.getMaxParameters() && totalAdded < matchedParameters[pathSegmentIndex]; segmentParameterIndex++) 645 { 646 if (pageParameters == null) 647 { 648 pageParameters = newPageParameters(); 649 } 650 651 String curSegment = mountSegments[pathSegment.getSegmentIndex() + segmentParameterIndex]; 652 653 String placeholder = getPlaceholder(curSegment); 654 String optionalPlaceholder = getOptionalPlaceholder(curSegment); 655 // extract the parameter from URL 656 if (placeholder != null) 657 { 658 pageParameters.add(placeholder, 659 url.getSegments().get(segmentIndex), INamedParameters.Type.PATH); 660 segmentIndex++; 661 totalAdded++; 662 requiredAdded++; 663 } 664 else if (optionalPlaceholder != null && 665 matchedParameters[pathSegmentIndex] - segmentParameterIndex > pathSegment.getMinParameters() + pathSegment.getFixedPartSize() - requiredAdded) 666 { 667 pageParameters.add(optionalPlaceholder, 668 url.getSegments().get(segmentIndex), INamedParameters.Type.PATH); 669 segmentIndex++; 670 totalAdded++; 671 } 672 } 673 674 segmentIndex += pathSegment.getFixedPartSize(); 675 } 676 return pageParameters; 677 } 678 679 protected int[] getMatchedSegmentSizes(Url url) 680 { 681 int[] ret = new int[pathSegments.size()]; 682 int segmentIndex = 0; 683 int pathSegmentIndex = 0; 684 for (MountPathSegment curPathSegment : pathSegments.subList(0, pathSegments.size() - 1)) 685 { 686 boolean foundFixedPart = false; 687 segmentIndex += curPathSegment.getMinParameters(); 688 int max = Math.min(curPathSegment.getOptionalParameters() + 1, 689 url.getSegments().size() - segmentIndex); 690 691 for (int count = max - 1; count >= 0; count--) 692 { 693 if (segmentsMatch(url.getSegments() 694 .get(segmentIndex + count), curPathSegment.getFixedPart())) 695 { 696 foundFixedPart = true; 697 segmentIndex += count + 1; 698 ret[pathSegmentIndex] = count + curPathSegment.getMinParameters() + 1; 699 break; 700 } 701 } 702 if (!foundFixedPart) 703 return null; 704 pathSegmentIndex++; 705 } 706 MountPathSegment lastSegment = pathSegments.get(pathSegments.size() - 1); 707 segmentIndex += lastSegment.getMinParameters(); 708 if (segmentIndex > url.getSegments().size()) 709 return null; 710 ret[pathSegmentIndex] = Math.min(lastSegment.getMaxParameters(), url.getSegments().size() - 711 segmentIndex + lastSegment.getMinParameters()); 712 return ret; 713 } 714 715 /** 716 * Decides whether a segment from the mounted path matches with a segment 717 * from the requested url. 718 * 719 * A custom implementation of this class may use more complex logic to handle 720 * spelling errors 721 * 722 * @param mountedSegment 723 * the segment from the mounted path 724 * @param urlSegment 725 * the segment from the request url 726 * @return {@code true} if the segments match 727 */ 728 protected boolean segmentsMatch(String mountedSegment, String urlSegment) 729 { 730 final boolean result; 731 if (isCaseSensitiveMatch()) 732 { 733 result = mountedSegment.equals(urlSegment); 734 } 735 else 736 { 737 result = mountedSegment.equalsIgnoreCase(urlSegment); 738 } 739 return result; 740 } 741 742 /** 743 * @return whether the matching of mounted segments against request's url ones should be 744 * case sensitive or not 745 */ 746 protected boolean isCaseSensitiveMatch() 747 { 748 return isCaseSensitive; 749 } 750 751 /** 752 * Sets whether the matching of mounted segments against request's url ones should be 753 * case sensitive or not. 754 * 755 * @param isCaseSensitive 756 * a flag indicating whether the matching of mounted segments against request's 757 * url ones should be case sensitive or not 758 * @return this instance, for chaining 759 */ 760 public AbstractBookmarkableMapper setCaseSensitiveMatch(boolean isCaseSensitive) 761 { 762 this.isCaseSensitive = isCaseSensitive; 763 return this; 764 } 765 766 /** 767 * Replaces mandatory and optional parameters with their values. 768 * 769 * If a mandatory parameter is not provided then the method returns {@code false} 770 * indicating that there is a problem. 771 * Optional parameters with missing values are just dropped. 772 * 773 * @param parameters 774 * The parameters with the values 775 * @param url 776 * The url with the placeholders 777 * @return 778 * {@code true} if all mandatory parameters are properly substituted, 779 * {@code false} - otherwise 780 */ 781 protected boolean setPlaceholders(PageParameters parameters, Url url) 782 { 783 boolean mandatoryParametersSet = true; 784 785 int dropped = 0; 786 for (int i = 0; i < mountSegments.length; ++i) 787 { 788 String placeholder = getPlaceholder(mountSegments[i]); 789 String optionalPlaceholder = getOptionalPlaceholder(mountSegments[i]); 790 if (placeholder != null) 791 { 792 if (parameters.contains(placeholder)) 793 { 794 url.getSegments().set(i - dropped, parameters.get(placeholder).toString()); 795 parameters.remove(placeholder); 796 } 797 else 798 { 799 mandatoryParametersSet = false; 800 break; 801 } 802 } 803 else if (optionalPlaceholder != null) 804 { 805 if (parameters.contains(optionalPlaceholder)) 806 { 807 url.getSegments().set(i - dropped, parameters.get(optionalPlaceholder).toString("")); 808 parameters.remove(optionalPlaceholder); 809 } 810 else 811 { 812 url.getSegments().remove(i - dropped); 813 dropped++; 814 } 815 } 816 } 817 818 return mandatoryParametersSet; 819 } 820 821 protected boolean urlStartsWithMountedSegments(Url url) 822 { 823 if (url == null) 824 { 825 return false; 826 } 827 else 828 { 829 return getMatchedSegmentSizes(url) != null; 830 } 831 } 832}