    001// Copyright 2009-2013 The Apache Software Foundation
    002//
    003// Licensed under the Apache License, Version 2.0 (the "License");
    004// you may not use this file except in compliance with the License.
    005// You may obtain a copy of the License at
    006//
    007// http://www.apache.org/licenses/LICENSE-2.0
    008//
    009// Unless required by applicable law or agreed to in writing, software
    010// distributed under the License is distributed on an "AS IS" BASIS,
    011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012// See the License for the specific language governing permissions and
    013// limitations under the License.
    014
    015package org.apache.tapestry5.internal.services;
    016
    017import org.apache.tapestry5.Link;
    018import org.apache.tapestry5.LinkSecurity;
    019import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    020import org.apache.tapestry5.services.BaseURLSource;
    021import org.apache.tapestry5.services.ContextPathEncoder;
    022import org.apache.tapestry5.services.Response;
    023
    024import java.util.Arrays;
    025import java.util.List;
    026import java.util.Map;
    027import java.util.TreeMap;
    028
    029public class LinkImpl implements Link
    030{
    031    private Map<String, List<String>> parameters;
    032
    033    private final String basePath;
    034
    035    private final boolean forForm;
    036
    037    private LinkSecurity defaultSecurity;
    038
    039    private final Response response;
    040
    041    private final ContextPathEncoder contextPathEncoder;
    042
    043    private final BaseURLSource baseURLSource;
    044
    045    private String anchor;
    046
    047    public LinkImpl(String basePath, boolean forForm, LinkSecurity defaultSecurity, Response response,
    048                    ContextPathEncoder contextPathEncoder, BaseURLSource baseURLSource)
    049    {
    050        assert basePath != null;
    051
    052        this.basePath = basePath;
    053        this.forForm = forForm;
    054        this.defaultSecurity = defaultSecurity;
    055        this.response = response;
    056        this.contextPathEncoder = contextPathEncoder;
    057        this.baseURLSource = baseURLSource;
    058    }
    059
    060    public Link copyWithBasePath(String basePath)
    061    {
    062        LinkImpl copy = new LinkImpl(basePath, forForm, defaultSecurity, response, contextPathEncoder, baseURLSource);
    063
    064        copy.anchor = anchor;
    065
    066        for (String name : getParameterNames())
    067        {
    068            copy.addParameter(name, getParameterValues(name));
    069        }
    070
    071        return copy;
    072    }
    073
    074    private void addParameter(String parameterName, String[] value)
    075    {
    076        assert InternalUtils.isNonBlank(parameterName);
    077        if (parameters == null)
    078            parameters = new TreeMap<String, List<String>>();
    079
    080        parameters.put(parameterName, Arrays.asList(value));
    081    }
    082
    083    public Link addParameter(String parameterName, String value)
    084    {
    085        assert InternalUtils.isNonBlank(parameterName);
    086
    087        if (parameters == null)
    088        {
    089            parameters = new TreeMap<String, List<String>>();
    090        }
    091
    092        InternalUtils.addToMapList(parameters, parameterName, value == null ? "" : value);
    093
    094        return this;
    095    }
    096
    097    public String getBasePath()
    098    {
    099        return basePath;
    100    }
    101
    102    public Link removeParameter(String parameterName)
    103    {
    104        assert InternalUtils.isNonBlank(parameterName);
    105
    106        if (parameters != null)
    107        {
    108            parameters.remove(parameterName);
    109        }
    110
    111        return this;
    112    }
    113
    114    public String getAnchor()
    115    {
    116        return anchor;
    117    }
    118
    119    public List<String> getParameterNames()
    120    {
    121        return InternalUtils.sortedKeys(parameters);
    122    }
    123
    124    public String getParameterValue(String name)
    125    {
    126        List<String> values = InternalUtils.get(parameters, name);
    127        return values != null && !values.isEmpty() ? values.get(0) : null;
    128    }
    129
    130    public Link setAnchor(String anchor)
    131    {
    132        this.anchor = anchor;
    133
    134        return this;
    135    }
    136
    137    public String toAbsoluteURI()
    138    {
    139        return buildAnchoredURI(defaultSecurity.promote());
    140    }
    141
    142    public String toAbsoluteURI(boolean secure)
    143    {
    144        return buildAnchoredURI(secure ? LinkSecurity.FORCE_SECURE : LinkSecurity.FORCE_INSECURE);
    145    }
    146
    147    public void setSecurity(LinkSecurity newSecurity)
    148    {
    149        assert newSecurity != null;
    150
    151        defaultSecurity = newSecurity;
    152    }
    153
    154    public LinkSecurity getSecurity()
    155    {
    156        return defaultSecurity;
    157    }
    158
    159    public String toRedirectURI()
    160    {
    161        return appendAnchor(response.encodeRedirectURL(buildURI(defaultSecurity)));
    162    }
    163
    164    public String toURI()
    165    {
    166        return buildAnchoredURI(defaultSecurity);
    167    }
    168
    169    private String appendAnchor(String path)
    170    {
    171        return InternalUtils.isBlank(anchor) ? path : path + "#" + anchor;
    172    }
    173
    174    private String buildAnchoredURI(LinkSecurity security)
    175    {
    176        return appendAnchor(response.encodeURL(buildURI(security)));
    177    }
    178
    179    /**
    180     * Returns the value from {@link #toURI()}
    181     */
    182    @Override
    183    public String toString()
    184    {
    185        return toURI();
    186    }
    187
    188    /**
    189     * Extends the absolute path with any query parameters. Query parameters are never added to a forForm link.
    190     *
    191     * @return absoluteURI appended with query parameters
    192     */
    193    private String buildURI(LinkSecurity security)
    194    {
    195
    196        if (!security.isAbsolute() && (forForm || parameters == null))
    197            return basePath;
    198
    199        StringBuilder builder = new StringBuilder(basePath.length() * 2);
    200
    201        switch (security)
    202        {
    203            case FORCE_SECURE:
    204                builder.append(baseURLSource.getBaseURL(true));
    205                break;
    206            case FORCE_INSECURE:
    207                builder.append(baseURLSource.getBaseURL(false));
    208                break;
    209            default:
    210        }
    211
    212        // The base URL (from BaseURLSource) does not end with a slash.
    213        // The basePath does (the context path begins with a slash or is blank, then there's
    214        // always a slash before the local name or page name.
    215
    216        builder.append(basePath);
    217
    218        if (!forForm)
    219        {
    220            String sep = basePath.contains("?") ? "&" : "?";
    221
    222            for (String name : getParameterNames())
    223            {
    224                List<String> values = parameters.get(name);
    225
    226                for (String value : values)
    227                {
    228                    builder.append(sep);
    229
    230                    // We assume that the name is URL safe and that the value will already have been URL
    231                    // encoded if it is not known to be URL safe.
    232
    233                    builder.append(name);
    234                    builder.append('=');
    235                    builder.append(value);
    236
    237                    sep = "&";
    238                }
    239            }
    240        }
    241
    242        return builder.toString();
    243    }
    244
    245    public Link addParameterValue(String parameterName, Object value)
    246    {
    247        addParameter(parameterName, contextPathEncoder.encodeValue(value));
    248
    249        return this;
    250    }
    251
    252    public String[] getParameterValues(String parameterName)
    253    {
    254        List<String> values = InternalUtils.get(parameters, parameterName);
    255        return values.toArray(new String[values.size()]);
    256    }
    257
    258}