001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.kernel.portlet;
016    
017    import com.liferay.portal.kernel.log.Log;
018    import com.liferay.portal.kernel.log.LogFactoryUtil;
019    import com.liferay.portal.kernel.util.StringPool;
020    import com.liferay.portal.kernel.util.Validator;
021    import com.liferay.portal.model.PortletConstants;
022    import com.liferay.portal.util.PortalUtil;
023    
024    import java.util.HashMap;
025    import java.util.LinkedHashMap;
026    import java.util.LinkedHashSet;
027    import java.util.Map;
028    import java.util.Set;
029    
030    import javax.portlet.PortletMode;
031    import javax.portlet.WindowState;
032    
033    /**
034     * The default friendly URL mapper to use with friendly URL routes.
035     *
036     * <p>
037     * In most cases, to add friendly URL mapping to a portlet, simply set this
038     * class as the friendly URL mapper in <code>liferay-portlet.xml</code>, and
039     * write a <code>friendly-url-routes.xml</code> file.
040     * </p>
041     *
042     * <p>
043     * If you do need to extend this class, the key methods to override are {@link
044     * #buildPath(LiferayPortletURL)} and {@link #populateParams(String, Map, Map)}.
045     * </p>
046     *
047     * @author Connor McKay
048     * @see    Router
049     */
050    public class DefaultFriendlyURLMapper extends BaseFriendlyURLMapper {
051    
052            public DefaultFriendlyURLMapper() {
053                    defaultIgnoredParameters = new LinkedHashSet<>();
054    
055                    defaultIgnoredParameters.add("p_p_id");
056                    defaultIgnoredParameters.add("p_p_col_id");
057                    defaultIgnoredParameters.add("p_p_col_pos");
058                    defaultIgnoredParameters.add("p_p_col_count");
059    
060                    defaultReservedParameters = new LinkedHashMap<>();
061    
062                    defaultReservedParameters.put("p_p_lifecycle", "0");
063                    defaultReservedParameters.put(
064                            "p_p_state", WindowState.NORMAL.toString());
065                    defaultReservedParameters.put("p_p_mode", PortletMode.VIEW.toString());
066            }
067    
068            /**
069             * Adds a default ignored parameter.
070             *
071             * <p>
072             * A default ignored parameter will always be hidden in friendly URLs.
073             * </p>
074             *
075             * @param name the name of the parameter
076             */
077            public void addDefaultIgnoredParameter(String name) {
078                    defaultIgnoredParameters.add(name);
079            }
080    
081            /**
082             * Adds a default reserved parameter.
083             *
084             * <p>
085             * A default reserved parameter will be hidden in friendly URLs when it is
086             * set to its default value.
087             * </p>
088             *
089             * @param name the name of the parameter
090             * @param value the default value of the parameter
091             */
092            public void addDefaultReservedParameter(String name, String value) {
093                    defaultReservedParameters.put(name, value);
094            }
095    
096            @Override
097            public String buildPath(LiferayPortletURL liferayPortletURL) {
098                    Map<String, String> routeParameters = new HashMap<>();
099    
100                    buildRouteParameters(liferayPortletURL, routeParameters);
101    
102                    String friendlyURLPath = router.parametersToUrl(routeParameters);
103    
104                    if (Validator.isNull(friendlyURLPath)) {
105                            return null;
106                    }
107    
108                    addParametersIncludedInPath(liferayPortletURL, routeParameters);
109    
110                    friendlyURLPath = StringPool.SLASH.concat(getMapping()).concat(
111                            friendlyURLPath);
112    
113                    return friendlyURLPath;
114            }
115    
116            /**
117             * Returns the default ignored parameters.
118             *
119             * @return the ignored parameter names
120             * @see    #addDefaultIgnoredParameter(String)
121             */
122            public Set<String> getDefaultIgnoredParameters() {
123                    return defaultIgnoredParameters;
124            }
125    
126            /**
127             * Returns the default reserved parameters.
128             *
129             * @return the default reserved parameter names and values
130             * @see    #addDefaultReservedParameter(String, String)
131             */
132            public Map<String, String> getDefaultReservedParameters() {
133                    return defaultReservedParameters;
134            }
135    
136            @Override
137            public void populateParams(
138                    String friendlyURLPath, Map<String, String[]> parameterMap,
139                    Map<String, Object> requestContext) {
140    
141                    friendlyURLPath = friendlyURLPath.substring(getMapping().length() + 1);
142    
143                    if (friendlyURLPath.endsWith(StringPool.SLASH)) {
144                            friendlyURLPath = friendlyURLPath.substring(
145                                    0, friendlyURLPath.length() - 1);
146                    }
147    
148                    Map<String, String> routeParameters = new HashMap<>();
149    
150                    if (!router.urlToParameters(friendlyURLPath, routeParameters)) {
151                            if (_log.isWarnEnabled()) {
152                                    _log.warn(
153                                            "No route could be found to match URL " + friendlyURLPath);
154                            }
155    
156                            return;
157                    }
158    
159                    String namespace = null;
160    
161                    String portletId = getPortletId(routeParameters);
162    
163                    if (Validator.isNotNull(portletId)) {
164                            namespace = PortalUtil.getPortletNamespace(portletId);
165    
166                            addParameter(namespace, parameterMap, "p_p_id", portletId);
167                    }
168                    else if (isAllPublicRenderParameters(routeParameters)) {
169    
170                            // Portlet namespace is not needed if all the parameters are public
171                            // render parameters
172    
173                            addParameter(null, parameterMap, "p_p_id", getPortletId());
174                    }
175                    else {
176                            return;
177                    }
178    
179                    populateParams(parameterMap, namespace, routeParameters);
180            }
181    
182            /**
183             * Adds the parameters included in the path to the portlet URL.
184             *
185             * <p>
186             * Portlet URLs track which parameters are included in the friendly URL
187             * path. This method hides all the default ignored parameters, the
188             * parameters included in the path by the router, and the reserved
189             * parameters set to their defaults.
190             * </p>
191             *
192             * @param liferayPortletURL the portlet URL to which to add the parameters
193             *        included in the path
194             * @param routeParameters the parameter map populated by the router
195             * @see   com.liferay.portlet.PortletURLImpl#addParameterIncludedInPath(
196             *        String)
197             */
198            protected void addParametersIncludedInPath(
199                    LiferayPortletURL liferayPortletURL,
200                    Map<String, String> routeParameters) {
201    
202                    // Hide default ignored parameters
203    
204                    for (String name : defaultIgnoredParameters) {
205                            liferayPortletURL.addParameterIncludedInPath(name);
206                    }
207    
208                    // Hide application parameters removed by the router
209    
210                    Map<String, String[]> portletURLParameters =
211                            liferayPortletURL.getParameterMap();
212    
213                    for (String name : portletURLParameters.keySet()) {
214                            if (!routeParameters.containsKey(name)) {
215                                    liferayPortletURL.addParameterIncludedInPath(name);
216                            }
217                    }
218    
219                    // Hide reserved parameters removed by the router or set to the defaults
220    
221                    Map<String, String> reservedParameters =
222                            liferayPortletURL.getReservedParameterMap();
223    
224                    for (Map.Entry<String, String> entry : reservedParameters.entrySet()) {
225                            String key = entry.getKey();
226                            String value = entry.getValue();
227    
228                            if (!routeParameters.containsKey(key) ||
229                                    value.equals(defaultReservedParameters.get(key))) {
230    
231                                    liferayPortletURL.addParameterIncludedInPath(key);
232                            }
233                    }
234            }
235    
236            /**
237             * Builds the parameter map to be used by the router by copying parameters
238             * from the portlet URL.
239             *
240             * <p>
241             * This method also populates the special virtual parameters
242             * <code>p_p_id</code> and <code>instanceId</code> for instanceable
243             * portlets.
244             * </p>
245             *
246             * @param liferayPortletURL the portlet URL to copy parameters from
247             * @param routeParameters the parameter map to populate for use by the
248             *        router
249             */
250            protected void buildRouteParameters(
251                    LiferayPortletURL liferayPortletURL,
252                    Map<String, String> routeParameters) {
253    
254                    // Copy application parameters
255    
256                    Map<String, String[]> portletURLParameters =
257                            liferayPortletURL.getParameterMap();
258    
259                    for (Map.Entry<String, String[]> entry :
260                                    portletURLParameters.entrySet()) {
261    
262                            String[] values = entry.getValue();
263    
264                            if (values.length > 0) {
265                                    routeParameters.put(entry.getKey(), values[0]);
266                            }
267                    }
268    
269                    // Populate virtual parameters for instanceable portlets
270    
271                    if (isPortletInstanceable()) {
272                            String portletId = liferayPortletURL.getPortletId();
273    
274                            routeParameters.put("p_p_id", portletId);
275    
276                            if (Validator.isNotNull(portletId) &&
277                                    PortletConstants.hasInstanceId(portletId)) {
278    
279                                    routeParameters.put(
280                                            "instanceId", PortletConstants.getInstanceId(portletId));
281                            }
282                    }
283    
284                    // Copy reserved parameters
285    
286                    routeParameters.putAll(liferayPortletURL.getReservedParameterMap());
287            }
288    
289            /**
290             * Returns the portlet ID, including the instance ID if applicable, from the
291             * parameter map.
292             *
293             * @param  routeParameters the parameter map. For an instanceable portlet,
294             *         this must contain either <code>p_p_id</code> or
295             *         <code>instanceId</code>.
296             * @return the portlet ID, including the instance ID if applicable, or
297             *         <code>null</code> if it cannot be determined
298             */
299            protected String getPortletId(Map<String, String> routeParameters) {
300                    if (!isPortletInstanceable()) {
301                            return getPortletId();
302                    }
303    
304                    String portletId = routeParameters.remove("p_p_id");
305    
306                    if (Validator.isNotNull(portletId)) {
307                            return portletId;
308                    }
309    
310                    String instanceId = routeParameters.remove("instanceId");
311    
312                    if (Validator.isNotNull(instanceId)) {
313                            return PortletConstants.assemblePortletId(
314                                    getPortletId(), instanceId);
315                    }
316    
317                    if (!isAllPublicRenderParameters(routeParameters)) {
318                            _log.error(
319                                    "Either p_p_id or instanceId must be provided for an " +
320                                            "instanceable portlet");
321                    }
322    
323                    return null;
324            }
325    
326            /**
327             * Returns <code>true</code> if all the route parameters are public render
328             * parameters.
329             *
330             * @param  routeParameters the parameter map
331             * @return <code>true</code> if all the route parameters are public render
332             *         parameters; <code>false</code> otherwise
333             */
334            protected boolean isAllPublicRenderParameters(
335                    Map<String, String> routeParameters) {
336    
337                    Set<String> routeParameterKeys = routeParameters.keySet();
338    
339                    Map<String, String> publicRenderParameters =
340                            FriendlyURLMapperThreadLocal.getPRPIdentifiers();
341    
342                    return routeParameterKeys.containsAll(publicRenderParameters.keySet());
343            }
344    
345            /**
346             * Populates the parameter map using the parameters from the router and the
347             * default reserved parameters.
348             *
349             * @param parameterMap the parameter map to populate. This should be the map
350             *        passed to {@link #populateParams(String, Map, Map)} by {@link
351             *        com.liferay.portal.util.PortalImpl}.
352             * @param namespace the namespace to use for parameters added to
353             *        <code>parameterMap</code>
354             * @param routeParameters the parameter map populated by the router
355             */
356            protected void populateParams(
357                    Map<String, String[]> parameterMap, String namespace,
358                    Map<String, String> routeParameters) {
359    
360                    // Copy route parameters
361    
362                    for (Map.Entry<String, String> entry : routeParameters.entrySet()) {
363                            addParameter(
364                                    namespace, parameterMap, entry.getKey(), entry.getValue());
365                    }
366    
367                    // Copy default reserved parameters if they are not already set
368    
369                    for (Map.Entry<String, String> entry :
370                                    defaultReservedParameters.entrySet()) {
371    
372                            String key = entry.getKey();
373    
374                            if (!parameterMap.containsKey(key)) {
375                                    addParameter(namespace, parameterMap, key, entry.getValue());
376                            }
377                    }
378            }
379    
380            protected Set<String> defaultIgnoredParameters;
381            protected Map<String, String> defaultReservedParameters;
382    
383            private static final Log _log = LogFactoryUtil.getLog(
384                    DefaultFriendlyURLMapper.class);
385    
386    }