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