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 IRequestCycle _cycle;
044    
045        private final String _servletPath;
046    
047        private final URLCodec _codec;
048    
049        private String _encoding;
050    
051        private boolean _stateful;
052    
053        /** @since 4.0 */
054        private final QueryParameterMap _parameters;
055    
056        /** @since 4.0 */
057    
058        private final WebRequest _request;
059    
060        /**
061         * Creates a new EngineServiceLink.
062         * 
063         * @param cycle
064         *            The {@link IRequestCycle}  the EngineServiceLink is to be created for.
065         * @param servletPath
066         *            The path used to invoke the Tapestry servlet.
067         * @param codec
068         *            A codec for converting strings into URL-safe formats.
069         * @param encoding
070         *            The output encoding for the request.
071         * @param parameters
072         *            The query parameters to be encoded into the url. Keys are strings, values are
073         *            null, string or array of string. The map is retained, not copied.
074         * @param stateful
075         *            if true, the service which generated the EngineServiceLink is stateful and expects
076         *            that the final URL will be passed through {@link IRequestCycle#encodeURL(String)}.
077         */
078    
079        public EngineServiceLink(IRequestCycle cycle, String servletPath, String encoding,
080                URLCodec codec, WebRequest request, Map parameters, boolean stateful)
081        {
082            Defense.notNull(cycle, "cycle");
083            Defense.notNull(servletPath, "servletPath");
084            Defense.notNull(encoding, "encoding");
085            Defense.notNull(codec, "codec");
086            Defense.notNull(request, "request");
087            Defense.notNull(parameters, "parameters");
088    
089            _cycle = cycle;
090            _servletPath = servletPath;
091            _encoding = encoding;
092            _codec = codec;
093            _request = request;
094            _parameters = new QueryParameterMap(parameters);
095            _stateful = stateful;
096        }
097    
098        public String getURL()
099        {
100            return getURL(null, true);
101        }
102    
103        public String getURL(String anchor, boolean includeParameters)
104        {
105            return constructURL(new StringBuffer(), anchor, includeParameters);
106        }
107    
108        public String getAbsoluteURL()
109        {
110            return getAbsoluteURL(null, null, 0, null, true);
111        }
112    
113        public String getURL(String scheme, String server, int port, String anchor,
114                boolean includeParameters)
115        {
116            boolean useAbsolute = EngineUtils.needAbsoluteURL(scheme, server, port, _request);
117    
118            return useAbsolute ? getAbsoluteURL(scheme, server, port, anchor, includeParameters)
119                    : getURL(anchor, includeParameters);
120        }
121    
122        public String getAbsoluteURL(String scheme, String server, int port, String anchor,
123                boolean includeParameters)
124        {
125            StringBuffer buffer = new StringBuffer();
126    
127            if (scheme == null)
128                scheme = _request.getScheme();
129    
130            buffer.append(scheme);
131            buffer.append("://");
132    
133            if (server == null)
134                server = _request.getServerName();
135    
136            buffer.append(server);
137    
138            if (port == 0)
139                port = _request.getServerPort();
140    
141            if (!(scheme.equals("http") && port == DEFAULT_HTTP_PORT))
142            {
143                buffer.append(':');
144                buffer.append(port);
145            }
146    
147            // Add the servlet path and the rest of the URL & query parameters.
148            // The servlet path starts with a leading slash.
149    
150            return constructURL(buffer, anchor, includeParameters);
151        }
152    
153        private String constructURL(StringBuffer buffer, String anchor, boolean includeParameters)
154        {
155            buffer.append(_servletPath);
156    
157            if (includeParameters)
158                addParameters(buffer);
159    
160            if (anchor != null)
161            {
162                buffer.append('#');
163                buffer.append(anchor);
164            }
165    
166            String result = buffer.toString();
167    
168            if (_stateful)
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    }