001    // Copyright 2004, 2005 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.tapestry.engine;
016    
017    import org.apache.commons.codec.net.URLCodec;
018    import org.apache.hivemind.ApplicationRuntimeException;
019    import org.apache.hivemind.util.Defense;
020    import org.apache.tapestry.IRequestCycle;
021    import org.apache.tapestry.Tapestry;
022    import org.apache.tapestry.util.QueryParameterMap;
023    import org.apache.tapestry.web.WebRequest;
024    
025    import java.io.UnsupportedEncodingException;
026    
027    /**
028     * A EngineServiceLink represents a possible action within the client web browser; either clicking a
029     * link or submitting a form, which is constructed primarily from the servlet path, with some
030     * additional query parameters. A full URL for the EngineServiceLink can be generated, or the query
031     * parameters for the EngineServiceLink can be extracted (separately from the servlet path). The
032     * latter case is used when submitting constructing {@link org.apache.tapestry.form.Form forms}.
033     * 
034     * @author Howard Lewis Ship
035     * @since 3.0
036     */
037    
038    public class EngineServiceLink implements ILink
039    {
040        private static final int DEFAULT_HTTP_PORT = 80;
041        
042        private static final int DEFAULT_HTTPS_PORT = 443;
043    
044        private final String _servletPath;
045    
046        private final URLCodec _codec;
047    
048        private IRequestCycle _cycle;
049        
050        private boolean _stateful;
051        
052        private String _encoding;
053    
054        /** @since 4.0 */
055        private final QueryParameterMap _parameters;
056    
057        /** @since 4.0 */
058    
059        private final WebRequest _request;
060        
061        /**
062         * Creates a new EngineServiceLink.
063         * 
064         * @param servletPath
065         *            The path used to invoke the Tapestry servlet.
066         * @param codec
067         *            A codec for converting strings into URL-safe formats.
068         * @param encoding
069         *            The output encoding for the request.
070         * @param parameters
071         *            The query parameters to be encoded into the url. Keys are strings, values are
072         *            null, string or array of string. The map is retained, not copied.
073         * @param stateful
074         *            if true, the service which generated the EngineServiceLink is stateful and expects
075         *            that the final URL will be passed through {@link IRequestCycle#encodeURL(String)}.
076         */
077        
078        public EngineServiceLink(String servletPath, String encoding,
079                URLCodec codec, WebRequest request, QueryParameterMap parameters, boolean stateful)
080        {
081            Defense.notNull(servletPath, "servletPath");
082            Defense.notNull(encoding, "encoding");
083            Defense.notNull(codec, "codec");
084            Defense.notNull(request, "request");
085            Defense.notNull(parameters, "parameters");
086            
087            _servletPath = servletPath;
088            _encoding = encoding;
089            _codec = codec;
090            _request = request;
091            _stateful = stateful;
092            _parameters = parameters;
093        }
094        
095        /**
096         * Creates a new EngineServiceLink. Primarily used in portlet applications with the
097         * additional {@link IRequestCycle} parameter being used to encode asset urls.
098         * 
099         * @param cycle
100         *            The {@link IRequestCycle}  the EngineServiceLink is to be created for.
101         * @param servletPath
102         *            The path used to invoke the Tapestry servlet.
103         * @param codec
104         *            A codec for converting strings into URL-safe formats.
105         * @param encoding
106         *            The output encoding for the request.
107         * @param parameters
108         *            The query parameters to be encoded into the url. Keys are strings, values are
109         *            null, string or array of string. The map is retained, not copied.
110         * @param stateful
111         *            if true, the service which generated the EngineServiceLink is stateful and expects
112         *            that the final URL will be passed through {@link IRequestCycle#encodeURL(String)}.
113         */
114    
115        public EngineServiceLink(IRequestCycle cycle, String servletPath, String encoding,
116                URLCodec codec, WebRequest request, QueryParameterMap parameters, boolean stateful)
117        {
118            Defense.notNull(cycle, "cycle");
119            Defense.notNull(servletPath, "servletPath");
120            Defense.notNull(encoding, "encoding");
121            Defense.notNull(codec, "codec");
122            Defense.notNull(request, "request");
123            Defense.notNull(parameters, "parameters");
124            
125            _cycle = cycle;
126            _servletPath = servletPath;
127            _encoding = encoding;
128            _codec = codec;
129            _request = request;
130            _stateful = stateful;
131            _parameters = parameters;
132        }
133    
134        public String getURL()
135        {
136            return getURL(null, true);
137        }
138    
139        public String getURL(String anchor, boolean includeParameters)
140        {
141            return constructURL(new StringBuffer(), anchor, includeParameters);
142        }
143    
144        public String getAbsoluteURL()
145        {
146            return getAbsoluteURL(null, null, 0, null, true);
147        }
148    
149        public String getURL(String scheme, String server, int port, String anchor,
150                boolean includeParameters)
151        {
152            boolean useAbsolute = EngineUtils.needAbsoluteURL(scheme, server, port, _request);
153    
154            return useAbsolute ? getAbsoluteURL(scheme, server, port, anchor, includeParameters)
155                    : getURL(anchor, includeParameters);
156        }
157    
158        public String getAbsoluteURL(String scheme, String server, int port, String anchor,
159                boolean includeParameters)
160        {
161            StringBuffer buffer = new StringBuffer();
162            
163            int nport = port == 0 ? _request.getServerPort() : port;
164            String nscheme = scheme == null ? _request.getScheme() : scheme;
165            
166            buffer.append(nscheme);
167            buffer.append("://");
168            
169            buffer.append(server == null ? _request.getServerName() : server);
170            
171            if (!(nscheme.equals("http") && nport == DEFAULT_HTTP_PORT) && !(nscheme.equals("https") && nport == DEFAULT_HTTPS_PORT))
172            {
173                buffer.append(':');
174                buffer.append(nport);
175            }
176            
177            // Add the servlet path and the rest of the URL & query parameters.
178            // The servlet path starts with a leading slash.
179    
180            return constructURL(buffer, anchor, includeParameters);
181        }
182    
183        private String constructURL(StringBuffer buffer, String anchor, boolean includeParameters)
184        {
185            buffer.append(_servletPath);
186    
187            if (includeParameters)
188                addParameters(buffer);
189    
190            if (anchor != null)
191            {
192                buffer.append('#');
193                buffer.append(anchor);
194            }
195            
196            String result = buffer.toString();
197            
198            // TODO: This is somewhat questionable right now, was added in to support TAPESTRY-802
199            if (_cycle != null && _stateful)
200            {    
201                result = _cycle.encodeURL(result);
202            }
203            
204            return result;
205        }
206    
207        private void addParameters(StringBuffer buffer)
208        {
209            String[] names = getParameterNames();
210    
211            String sep = "?";
212    
213            for (int i = 0; i < names.length; i++)
214            {
215                String name = names[i];
216                String[] values = getParameterValues(name);
217    
218                if (values == null)
219                    continue;
220    
221                for (int j = 0; j < values.length; j++)
222                {
223                    buffer.append(sep);
224                    buffer.append(name);
225                    buffer.append("=");
226                    buffer.append(encode(values[j]));
227    
228                    sep = "&";
229                }
230    
231            }
232        }
233    
234        private String encode(String value)
235        {
236            try
237            {
238                return _codec.encode(value, _encoding);
239            }
240            catch (UnsupportedEncodingException ex)
241            {
242                throw new ApplicationRuntimeException(Tapestry.format("illegal-encoding", _encoding),
243                        ex);
244            }
245        }
246    
247        public String[] getParameterNames()
248        {
249            return _parameters.getParameterNames();
250        }
251    
252        public String[] getParameterValues(String name)
253        {
254            return _parameters.getParameterValues(name);
255        }
256    }