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