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.http.Link;
018import org.apache.tapestry5.http.LinkSecurity;
019import org.apache.tapestry5.http.services.BaseURLSource;
020import org.apache.tapestry5.http.services.Response;
021import org.apache.tapestry5.ioc.internal.util.InternalUtils;
022import org.apache.tapestry5.services.ContextPathEncoder;
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}