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 }