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.request.mapper;
018
019import java.util.List;
020import java.util.Locale;
021
022import org.apache.wicket.request.IRequestMapper;
023import org.apache.wicket.request.Request;
024import org.apache.wicket.request.Url;
025import org.apache.wicket.request.Url.QueryParameter;
026import org.apache.wicket.request.mapper.parameter.IPageParametersEncoder;
027import org.apache.wicket.request.mapper.parameter.PageParameters;
028import org.apache.wicket.util.lang.Args;
029import org.apache.wicket.util.string.Strings;
030
031/**
032 * 
033 */
034public abstract class AbstractMapper implements IRequestMapper
035{
036
037        /**
038         * If the string is in a placeholder format ${key} this method returns the key.
039         * 
040         * @param s
041         * @return placeholder key or <code>null</code> if string is not in right format
042         */
043        protected String getPlaceholder(final String s)
044        {
045                return getPlaceholder(s, '$');
046        }
047
048        /**
049         * If the string is in an optional parameter placeholder format #{key} this method returns the
050         * key.
051         * 
052         * @param s
053         * @return placeholder key or <code>null</code> if string is not in right format
054         */
055        protected String getOptionalPlaceholder(final String s)
056        {
057                return getPlaceholder(s, '#');
058        }
059
060        /**
061         * If the string is in a placeholder format x{key}, where 'x' can be specified, this method
062         * returns the key.
063         * 
064         * @param s
065         * @param startChar
066         *            the character used to indicate the start of the placeholder
067         * @return placeholder key or <code>null</code> if string is not in right format
068         */
069        protected String getPlaceholder(final String s, char startChar)
070        {
071                if (s == null || s.length() < 4)
072                {
073                        return null;
074                }
075                else if (s.charAt(0) != startChar || s.charAt(1) != '{' || s.charAt(s.length() - 1) != '}')
076                {
077                        return null;
078                }
079                else
080                {
081                        return s.substring(2, s.length() - 1);
082                }
083        }
084
085        /**
086         * Construct.
087         */
088        public AbstractMapper()
089        {
090                super();
091        }
092
093        /**
094         * Returns true if the given url starts with specified segments.
095         * 
096         * @param url
097         * @param segments
098         * @return <code>true</code> if the URL starts with the specified segments, <code>false</code>
099         *         otherwise
100         */
101        protected boolean urlStartsWith(final Url url, final String... segments)
102        {
103                if (url == null)
104                {
105                        return false;
106                }
107
108                List<String> urlSegments = url.getSegments();
109                
110                for (int i = 0; i < segments.length; ++i)
111                {
112                        String segment = segments[i];
113                        String urlSegment = safeSegmentGetter(urlSegments, i, null);
114                        if (urlSegment == null && getOptionalPlaceholder(segment) == null)
115                        {
116                                // if the 'segment' has static value or is mandatory placeholder
117                                return false;
118                        }
119                        else if (!segment.equals(urlSegment) &&
120                            (getPlaceholder(segment) == null &&
121                             getOptionalPlaceholder(segment) == null))
122                        {
123                                return false;
124                        }
125                }
126                        
127                return true;
128        }
129        
130        /**
131         * Utility method to safely get an element from a list of String.
132         * If the specified index is bigger than the size of the list
133         * the default value is returned.
134         * 
135         * @param segments
136         * @param index
137         * @param defaultValue
138         * @return the element at the specified position or the default value if the list size is smaller.
139         * 
140         */
141        protected String safeSegmentGetter(List<String> segments, int index, String defaultValue)
142        {
143                if (index < segments.size())
144                {
145                        return segments.get(index);
146                }
147                
148                return defaultValue;
149        }
150        
151        /**
152         * Extracts {@link PageParameters} from the URL using the given {@link IPageParametersEncoder} .
153         * 
154         * @param request
155         * @param segmentsToSkip
156         *            how many URL segments should be skipped because they "belong" to the
157         *            {@link IRequestMapper}
158         * @param encoder
159         * @return PageParameters instance
160         */
161        protected PageParameters extractPageParameters(final Request request, int segmentsToSkip,
162                final IPageParametersEncoder encoder)
163        {
164                Args.notNull(request, "request");
165                Args.notNull(encoder, "encoder");
166
167                // strip the segments and first query parameter from URL
168                Url urlCopy = new Url(request.getUrl());
169                while ((segmentsToSkip > 0) && (urlCopy.getSegments().isEmpty() == false))
170                {
171                        urlCopy.getSegments().remove(0);
172                        --segmentsToSkip;
173                }
174
175                if (!urlCopy.getQueryParameters().isEmpty() &&
176                        Strings.isEmpty(urlCopy.getQueryParameters().get(0).getValue()))
177                {
178                        removeMetaParameter(urlCopy);
179                }
180
181                return encoder.decodePageParameters(urlCopy);
182        }
183
184        /**
185         * The new {@link IRequestMapper}s use the first query parameter to hold meta information about
186         * the request like page version, component version, locale, ... The actual
187         * {@link IRequestMapper} implementation can decide whether the this parameter should be removed
188         * before creating {@link PageParameters} from the current {@link Url#getQueryParameters() query
189         * parameters}
190         * 
191         * @param urlCopy
192         *            the {@link Url} that first query parameter has no value
193         */
194        protected void removeMetaParameter(final Url urlCopy)
195        {
196        }
197
198        /**
199         * Encodes the given {@link PageParameters} to the URL using the given
200         * {@link IPageParametersEncoder}. The original URL object is unchanged.
201         * 
202         * @param url
203         * @param pageParameters
204         * @param encoder
205         * @return URL with encoded parameters
206         */
207        protected Url encodePageParameters(Url url, PageParameters pageParameters,
208                final IPageParametersEncoder encoder)
209        {
210                Args.notNull(url, "url");
211                Args.notNull(encoder, "encoder");
212
213                if (pageParameters == null)
214                {
215                        pageParameters = new PageParameters();
216                }
217
218                Url parametersUrl = encoder.encodePageParameters(pageParameters);
219                if (parametersUrl != null)
220                {
221                        // copy the url
222                        url = new Url(url);
223
224                        for (String s : parametersUrl.getSegments())
225                        {
226                                url.getSegments().add(s);
227                        }
228                        for (QueryParameter p : parametersUrl.getQueryParameters())
229                        {
230                                url.getQueryParameters().add(p);
231                        }
232                }
233
234                return url;
235        }
236
237        /**
238         * Convenience method for representing mountPath as array of segments
239         * 
240         * @param mountPath
241         * @return array of path segments
242         */
243        protected String[] getMountSegments(String mountPath)
244        {
245                if (mountPath.charAt(0) == '/')
246                {
247                        mountPath = mountPath.substring(1);
248                }
249                Url url = Url.parse(mountPath);
250
251                String[] res = new String[url.getSegments().size()];
252                for (int i = 0; i < res.length; ++i)
253                {
254                        res[i] = url.getSegments().get(i);
255                }
256                return res;
257        }
258
259        /**
260         * @return the locale to use for parsing any numbers in the request parameters
261         */
262        protected Locale resolveLocale()
263        {
264                return Locale.getDefault(Locale.Category.DISPLAY);
265        }
266}