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        private static final int DEFAULT_HTTPS_PORT = 443;
043    
044        private final IRequestCycle _cycle;
045    
046        private final String _servletPath;
047    
048        private final URLCodec _codec;
049    
050        private String _encoding;
051    
052        private boolean _stateful;
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 cycle
065         *            The {@link IRequestCycle}  the EngineServiceLink is to be created for.
066         * @param servletPath
067         *            The path used to invoke the Tapestry servlet.
068         * @param codec
069         *            A codec for converting strings into URL-safe formats.
070         * @param encoding
071         *            The output encoding for the request.
072         * @param parameters
073         *            The query parameters to be encoded into the url. Keys are strings, values are
074         *            null, string or array of string. The map is retained, not copied.
075         * @param stateful
076         *            if true, the service which generated the EngineServiceLink is stateful and expects
077         *            that the final URL will be passed through {@link IRequestCycle#encodeURL(String)}.
078         */
079    
080        public EngineServiceLink(IRequestCycle cycle, String servletPath, String encoding,
081                URLCodec codec, WebRequest request, Map parameters, boolean stateful)
082        {
083            Defense.notNull(cycle, "cycle");
084            Defense.notNull(servletPath, "servletPath");
085            Defense.notNull(encoding, "encoding");
086            Defense.notNull(codec, "codec");
087            Defense.notNull(request, "request");
088            Defense.notNull(parameters, "parameters");
089    
090            _cycle = cycle;
091            _servletPath = servletPath;
092            _encoding = encoding;
093            _codec = codec;
094            _request = request;
095            _parameters = new QueryParameterMap(parameters);
096            _stateful = stateful;
097        }
098    
099        public String getURL()
100        {
101            return getURL(null, true);
102        }
103    
104        public String getURL(String anchor, boolean includeParameters)
105        {
106            return constructURL(new StringBuffer(), anchor, includeParameters);
107        }
108    
109        public String getAbsoluteURL()
110        {
111            return getAbsoluteURL(null, null, 0, null, true);
112        }
113    
114        public String getURL(String scheme, String server, int port, String anchor,
115                boolean includeParameters)
116        {
117            boolean useAbsolute = EngineUtils.needAbsoluteURL(scheme, server, port, _request);
118    
119            return useAbsolute ? getAbsoluteURL(scheme, server, port, anchor, includeParameters)
120                    : getURL(anchor, includeParameters);
121        }
122    
123        public String getAbsoluteURL(String scheme, String server, int port, String anchor,
124                boolean includeParameters)
125        {
126            StringBuffer buffer = new StringBuffer();
127    
128            if (scheme == null)
129                scheme = _request.getScheme();
130    
131            buffer.append(scheme);
132            buffer.append("://");
133    
134            if (server == null)
135                server = _request.getServerName();
136    
137            buffer.append(server);
138    
139            if (port == 0)
140                port = _request.getServerPort();
141    
142            if (!(scheme.equals("http") && port == DEFAULT_HTTP_PORT))
143            {
144                buffer.append(':');
145                buffer.append(port);
146            }
147    
148            // Add the servlet path and the rest of the URL & query parameters.
149            // The servlet path starts with a leading slash.
150    
151            return constructURL(buffer, anchor, includeParameters);
152        }
153    
154        private String constructURL(StringBuffer buffer, String anchor, boolean includeParameters)
155        {
156            buffer.append(_servletPath);
157    
158            if (includeParameters)
159                addParameters(buffer);
160    
161            if (anchor != null)
162            {
163                buffer.append('#');
164                buffer.append(anchor);
165            }
166    
167            String result = buffer.toString();
168    
169            result = _cycle.encodeURL(result);
170    
171            return result;
172        }
173    
174        private void addParameters(StringBuffer buffer)
175        {
176            String[] names = getParameterNames();
177    
178            String sep = "?";
179    
180            for (int i = 0; i < names.length; i++)
181            {
182                String name = names[i];
183                String[] values = getParameterValues(name);
184    
185                if (values == null)
186                    continue;
187    
188                for (int j = 0; j < values.length; j++)
189                {
190                    buffer.append(sep);
191                    buffer.append(name);
192                    buffer.append("=");
193                    buffer.append(encode(values[j]));
194    
195                    sep = "&";
196                }
197    
198            }
199        }
200    
201        private String encode(String value)
202        {
203            try
204            {
205                return _codec.encode(value, _encoding);
206            }
207            catch (UnsupportedEncodingException ex)
208            {
209                throw new ApplicationRuntimeException(Tapestry.format("illegal-encoding", _encoding),
210                        ex);
211            }
212        }
213    
214        public String[] getParameterNames()
215        {
216            return _parameters.getParameterNames();
217        }
218    
219        public String[] getParameterValues(String name)
220        {
221            return _parameters.getParameterValues(name);
222        }
223    }