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.List;
020
021import org.apache.wicket.Application;
022import org.apache.wicket.request.Request;
023import org.apache.wicket.request.Url;
024import org.apache.wicket.request.component.IRequestablePage;
025import org.apache.wicket.request.mapper.info.PageComponentInfo;
026import org.apache.wicket.request.mapper.parameter.IPageParametersEncoder;
027import org.apache.wicket.request.mapper.parameter.PageParameters;
028import org.apache.wicket.request.mapper.parameter.PageParametersEncoder;
029import org.apache.wicket.util.string.Strings;
030
031/**
032 * Decodes and encodes the following URLs:
033 * 
034 * <pre>
035 *  Page Class - Render (BookmarkablePageRequestHandler)
036 *  /wicket/bookmarkable/org.apache.wicket.MyPage
037 *  (will redirect to hybrid alternative if page is not stateless)
038 * 
039 *  Page Instance - Render Hybrid (RenderPageRequestHandler for pages that were created using bookmarkable URLs)
040 *  /wicket/bookmarkable/org.apache.wicket.MyPage?2
041 * 
042 *  Page Instance - Bookmarkable Listener (BookmarkableListenerRequestHandler)
043 *  /wicket/bookmarkable/org.apache.wicket.MyPage?2-click-foo-bar-baz
044 *  /wicket/bookmarkable/org.apache.wicket.MyPage?2-click.1-foo-bar-baz (1 is behavior index)
045 *  (these will redirect to hybrid if page is not stateless)
046 * </pre>
047 * 
048 * @author Matej Knopp
049 */
050public class BookmarkableMapper extends AbstractBookmarkableMapper
051{
052        /**
053         * Construct.
054         */
055        public BookmarkableMapper()
056        {
057                this(new PageParametersEncoder());
058        }
059
060        /**
061         * Construct.
062         *
063         * @param pageParametersEncoder
064         */
065        public BookmarkableMapper(IPageParametersEncoder pageParametersEncoder)
066        {
067                super("notUsed", pageParametersEncoder);
068        }
069
070        @Override
071        protected Url buildUrl(UrlInfo info)
072        {
073                Url url = new Url();
074                url.getSegments().add(getContext().getNamespace());
075                url.getSegments().add(getContext().getBookmarkableIdentifier());
076                url.getSegments().add(info.getPageClass().getName());
077
078                encodePageComponentInfo(url, info.getPageComponentInfo());
079
080                return encodePageParameters(url, info.getPageParameters(), pageParametersEncoder);
081        }
082
083        @Override
084        protected UrlInfo parseRequest(Request request)
085        {
086                if (Application.exists())
087                {
088                        if (Application.get().getSecuritySettings().getEnforceMounts())
089                        {
090                                return null;
091                        }
092                }
093
094                if (matches(request))
095                {
096                        Url url = request.getUrl();
097
098                        // try to extract page and component information from URL
099                        PageComponentInfo info = getPageComponentInfo(url);
100
101                        List<String> segments = url.getSegments();
102
103                        // load the page class
104                        String className;
105                        if (segments.size() >= 3)
106                        {
107                                className = segments.get(2);
108                        }
109                        else
110                        {
111                                className = segments.get(1);
112                        }
113
114                        if (Strings.isEmpty(className))
115                        {
116                                return null;
117                        }
118
119                        Class<? extends IRequestablePage> pageClass = getPageClass(className);
120
121                        if (pageClass != null && IRequestablePage.class.isAssignableFrom(pageClass))
122                        {
123                                // extract the PageParameters from URL if there are any
124                                PageParameters pageParameters = extractPageParameters(request, 3,
125                                        pageParametersEncoder);
126                                if (pageParameters != null)
127                                {
128                                        pageParameters.setLocale(resolveLocale());
129                                }
130
131                                return new UrlInfo(info, pageClass, pageParameters);
132                        }
133                }
134                return null;
135        }
136
137        @Override
138        protected boolean pageMustHaveBeenCreatedBookmarkable()
139        {
140                return true;
141        }
142
143        @Override
144        public int getCompatibilityScore(Request request)
145        {
146                int score = 0;
147                if (matches(request))
148                {
149                        score = Integer.MAX_VALUE;
150                }
151                return score;
152        }
153
154        private boolean matches(final Request request)
155        {
156                boolean matches = false;
157                Url url = request.getUrl();
158                Url baseUrl = request.getClientUrl();
159                String namespace = getContext().getNamespace();
160                String bookmarkableIdentifier = getContext().getBookmarkableIdentifier();
161                String pageIdentifier = getContext().getPageIdentifier();
162
163                List<String> segments = url.getSegments();
164                int segmentsSize = segments.size();
165
166                if (segmentsSize >= 3 && urlStartsWithAndHasPageClass(url, namespace, bookmarkableIdentifier))
167                {
168                        matches = true;
169                }
170                // baseUrl = 'wicket/bookmarkable/com.example.SomePage[?...]', requestUrl = 'bookmarkable/com.example.SomePage'
171                else if (baseUrl.getSegments().size() == 3 && urlStartsWith(baseUrl, namespace, bookmarkableIdentifier)
172                                && segmentsSize >= 2 && urlStartsWithAndHasPageClass(url, bookmarkableIdentifier))
173                {
174                        matches = true;
175                }
176                // baseUrl = 'bookmarkable/com.example.SomePage', requestUrl = 'bookmarkable/com.example.SomePage'
177                else if (baseUrl.getSegments().size() == 2 && urlStartsWith(baseUrl, bookmarkableIdentifier)
178                                && segmentsSize == 2 && urlStartsWithAndHasPageClass(url, bookmarkableIdentifier))
179                {
180                        matches = true;
181                }
182                // baseUrl = 'wicket/page[?...]', requestUrl = 'bookmarkable/com.example.SomePage'
183                else if (baseUrl.getSegments().size() == 2 && urlStartsWith(baseUrl, namespace, pageIdentifier)
184                                && segmentsSize >= 2 && urlStartsWithAndHasPageClass(url, bookmarkableIdentifier))
185                {
186                        matches = true;
187                }
188
189                return matches;
190        }
191
192        /**
193         * Checks whether the url starts with the given segments and additionally
194         * checks whether the following segment is non-empty
195         *
196         * @param url
197         *          The url to be checked
198         * @param segments
199         *          The expected leading segments
200         * @return {@code true} if the url starts with the given segments and there is non-empty segment after them
201         */
202        protected boolean urlStartsWithAndHasPageClass(Url url, String... segments)
203        {
204                boolean result = urlStartsWith(url, segments);
205
206                if (result)
207                {
208                        List<String> urlSegments = url.getSegments();
209                        if (urlSegments.size() == segments.length)
210                        {
211                                result = false;
212                        }
213                        else
214                        {
215                                String pageClassSegment = urlSegments.get(segments.length);
216                                if (Strings.isEmpty(pageClassSegment))
217                                {
218                                        result = false;
219                                }
220                        }
221                }
222
223                return result;
224        }
225}