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    
015    package org.apache.tapestry5.internal.services;
016    
017    import org.apache.tapestry5.Link;
018    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
019    import org.apache.tapestry5.services.BaseURLSource;
020    import org.apache.tapestry5.services.ContextPathEncoder;
021    import org.apache.tapestry5.services.Response;
022    
023    import java.util.Arrays;
024    import java.util.List;
025    import java.util.Map;
026    import java.util.TreeMap;
027    
028    public 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    }