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