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 }