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 getAbsoluteURL(String scheme, String server, int port, String anchor,
114                boolean includeParameters)
115        {
116            StringBuffer buffer = new StringBuffer();
117    
118            if (scheme == null)
119                scheme = _request.getScheme();
120    
121            buffer.append(scheme);
122            buffer.append("://");
123    
124            if (server == null)
125                server = _request.getServerName();
126    
127            buffer.append(server);
128    
129            if (port == 0)
130                port = _request.getServerPort();
131    
132            if (!(scheme.equals("http") && port == DEFAULT_HTTP_PORT))
133            {
134                buffer.append(':');
135                buffer.append(port);
136            }
137    
138            // Add the servlet path and the rest of the URL & query parameters.
139            // The servlet path starts with a leading slash.
140    
141            return constructURL(buffer, anchor, includeParameters);
142        }
143    
144        private String constructURL(StringBuffer buffer, String anchor, boolean includeParameters)
145        {
146            buffer.append(_servletPath);
147    
148            if (includeParameters)
149                addParameters(buffer);
150    
151            if (anchor != null)
152            {
153                buffer.append('#');
154                buffer.append(anchor);
155            }
156    
157            String result = buffer.toString();
158    
159            if (_stateful)
160                result = _cycle.encodeURL(result);
161    
162            return result;
163        }
164    
165        private void addParameters(StringBuffer buffer)
166        {
167            String[] names = getParameterNames();
168    
169            String sep = "?";
170    
171            for (int i = 0; i < names.length; i++)
172            {
173                String name = names[i];
174                String[] values = getParameterValues(name);
175    
176                if (values == null)
177                    continue;
178    
179                for (int j = 0; j < values.length; j++)
180                {
181                    buffer.append(sep);
182                    buffer.append(name);
183                    buffer.append("=");
184                    buffer.append(encode(values[j]));
185    
186                    sep = "&";
187                }
188    
189            }
190        }
191    
192        private String encode(String value)
193        {
194            try
195            {
196                return _codec.encode(value, _encoding);
197            }
198            catch (UnsupportedEncodingException ex)
199            {
200                throw new ApplicationRuntimeException(Tapestry.format("illegal-encoding", _encoding),
201                        ex);
202            }
203        }
204    
205        public String[] getParameterNames()
206        {
207            return _parameters.getParameterNames();
208        }
209    
210        public String[] getParameterValues(String name)
211        {
212            return _parameters.getParameterValues(name);
213        }
214    }