001 // Copyright 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.util; 016 017 import org.apache.commons.lang.StringUtils; 018 import org.apache.hivemind.Locatable; 019 import org.apache.hivemind.Location; 020 import org.apache.hivemind.Resource; 021 import org.apache.hivemind.util.Defense; 022 import org.apache.tapestry.*; 023 import org.apache.tapestry.asset.AssetFactory; 024 import org.apache.tapestry.services.ResponseBuilder; 025 026 import java.util.ArrayList; 027 import java.util.HashMap; 028 import java.util.List; 029 import java.util.Map; 030 031 /** 032 * Implementation of {@link org.apache.tapestry.PageRenderSupport}. The 033 * {@link org.apache.tapestry.html.Body} component uses an instance of this class. 034 * 035 * @author Howard M. Lewis Ship 036 * @since 4.0 037 */ 038 public class PageRenderSupportImpl implements Locatable, PageRenderSupport 039 { 040 private final AssetFactory _assetFactory; 041 042 private final Location _location; 043 044 private final ResponseBuilder _builder; 045 046 // Lines that belong inside the onLoad event handler for the <body> tag. 047 048 private StringBuffer _initializationScript; 049 050 // Used by addScriptAfterInitialization 051 052 private StringBuffer _postInitializationScript; 053 054 // Any other scripting desired 055 056 private StringBuffer _bodyScript; 057 058 // Contains text lines related to image initializations 059 060 private StringBuffer _imageInitializations; 061 062 /** 063 * Map of URLs to Strings (preloaded image references). 064 */ 065 066 private Map _imageMap; 067 068 /** 069 * List of included scripts. Values are Strings. 070 * 071 * @since 1.0.5 072 */ 073 074 private List _externalScripts; 075 076 private final IdAllocator _idAllocator; 077 078 private final String _preloadName; 079 080 private final Map _requires = new HashMap(); 081 082 public PageRenderSupportImpl(AssetFactory assetFactory, String namespace, 083 Location location, ResponseBuilder builder) 084 { 085 Defense.notNull(assetFactory, "assetService"); 086 087 _assetFactory = assetFactory; 088 _location = location; 089 _idAllocator = new IdAllocator(namespace); 090 _builder = builder; 091 092 _preloadName = (namespace.equals("") ? "tapestry." : namespace) + "preload"; 093 } 094 095 /** 096 * Returns the location, which may be used in error messages. In practical terms, this is the 097 * location of the {@link org.apache.tapestry.html.Body} component. 098 */ 099 100 public Location getLocation() 101 { 102 return _location; 103 } 104 105 public String getPreloadedImageReference(String URL) 106 { 107 return getPreloadedImageReference(null, URL); 108 } 109 110 public String getPreloadedImageReference(IComponent target, IAsset source) 111 { 112 return getPreloadedImageReference(target, source.buildURL()); 113 } 114 115 public String getPreloadedImageReference(IComponent target, String URL) 116 { 117 if (target != null 118 && !_builder.isImageInitializationAllowed(target)) 119 return URL; 120 121 if (_imageMap == null) 122 _imageMap = new HashMap(); 123 124 String reference = (String) _imageMap.get(URL); 125 126 if (reference == null) 127 { 128 int count = _imageMap.size(); 129 String varName = _preloadName + "[" + count + "]"; 130 reference = varName + ".src"; 131 132 if (_imageInitializations == null) 133 _imageInitializations = new StringBuffer(); 134 135 _imageInitializations.append(" "); 136 _imageInitializations.append(varName); 137 _imageInitializations.append(" = new Image();\n"); 138 _imageInitializations.append(" "); 139 _imageInitializations.append(reference); 140 _imageInitializations.append(" = \""); 141 _imageInitializations.append(URL); 142 _imageInitializations.append("\";\n"); 143 144 _imageMap.put(URL, reference); 145 } 146 147 return reference; 148 } 149 150 public void addBodyScript(String script) 151 { 152 addBodyScript(null, script); 153 } 154 155 public void addBodyScript(IComponent target, String script) 156 { 157 if (!_builder.isBodyScriptAllowed(target)) 158 return; 159 160 String val = stripDuplicateIncludes(script); 161 162 if (_bodyScript == null) 163 _bodyScript = new StringBuffer(val.length()); 164 165 _bodyScript.append("\n").append(val); 166 } 167 168 /** 169 * {@inheritDoc} 170 */ 171 public boolean isBodyScriptAllowed(IComponent target) 172 { 173 return _builder.isBodyScriptAllowed(target); 174 } 175 176 /** 177 * {@inheritDoc} 178 */ 179 public boolean isExternalScriptAllowed(IComponent target) 180 { 181 return _builder.isExternalScriptAllowed(target); 182 } 183 184 /** 185 * {@inheritDoc} 186 */ 187 public boolean isInitializationScriptAllowed(IComponent target) 188 { 189 return _builder.isInitializationScriptAllowed(target); 190 } 191 192 public void addInitializationScript(String script) 193 { 194 addInitializationScript(null, script); 195 } 196 197 public void addInitializationScript(IComponent target, String script) 198 { 199 if (!_builder.isInitializationScriptAllowed(target)) 200 return; 201 202 String val = stripDuplicateIncludes(script); 203 204 if (_initializationScript == null) 205 _initializationScript = new StringBuffer(val.length() + 1); 206 207 _initializationScript.append("\n").append(val); 208 } 209 210 public void addScriptAfterInitialization(IComponent target, String script) 211 { 212 if (!_builder.isInitializationScriptAllowed(target)) 213 return; 214 215 String strippedScript = stripDuplicateIncludes(script); 216 217 if (_postInitializationScript == null) 218 _postInitializationScript = new StringBuffer(strippedScript.length() + 1); 219 220 _postInitializationScript.append("\n").append(strippedScript); 221 } 222 223 /** 224 * Provides a mechanism to strip out duplicate dojo.require calls made in script 225 * templates in order to reduce amount of redundant javascript written to client. 226 * 227 * @param input The incoming script string to check for requires. 228 * @return The input string stripped of all known dojo.require calls, if any. 229 */ 230 String stripDuplicateIncludes(String input) 231 { 232 String[] lines = StringUtils.splitPreserveAllTokens(input, ';'); 233 234 if (lines == null || lines.length < 1) 235 return input; 236 237 String ret = input; 238 239 for (int i=0; i < lines.length; i++) 240 { 241 if (lines[i].indexOf("dojo.require") < 0) 242 continue; 243 244 String line = StringUtils.stripToEmpty(lines[i]); 245 246 if (_requires.containsKey(line)) 247 { 248 ret = StringUtils.replaceOnce(ret, line+";", ""); 249 } else 250 { 251 _requires.put(line, "t"); 252 } 253 } 254 255 return StringUtils.stripToEmpty(ret.trim()); 256 } 257 258 public void addExternalScript(Resource scriptLocation) 259 { 260 addExternalScript(null, scriptLocation); 261 } 262 263 public void addExternalScript(IComponent target, Resource scriptLocation) 264 { 265 if (!_builder.isExternalScriptAllowed(target)) 266 return; 267 268 if (_externalScripts == null) 269 _externalScripts = new ArrayList(); 270 271 if (_externalScripts.contains(scriptLocation)) 272 return; 273 274 // Record the Resource so we don't include it twice. 275 276 _externalScripts.add(scriptLocation); 277 } 278 279 public String getUniqueString(String baseValue) 280 { 281 return _idAllocator.allocateId(baseValue); 282 } 283 284 private void writeExternalScripts(IMarkupWriter writer, IRequestCycle cycle) 285 { 286 int count = Tapestry.size(_externalScripts); 287 for (int i = 0; i < count; i++) 288 { 289 Resource scriptLocation = (Resource) _externalScripts.get(i); 290 291 IAsset asset = _assetFactory.createAsset(scriptLocation, null); 292 293 String url = asset.buildURL(); 294 295 // Note: important to use begin(), not beginEmpty(), because browser don't 296 // interpret <script .../> properly. 297 298 _builder.writeExternalScript(writer, url, cycle); 299 } 300 } 301 302 /** 303 * Writes a single large JavaScript block containing: 304 * <ul> 305 * <li>Any image initializations (via {@link #getPreloadedImageReference(IComponent, String)}). 306 * <li>Any included scripts (via {@link #addExternalScript(Resource)}). 307 * <li>Any contributions (via {@link #addBodyScript(String)}). 308 * </ul> 309 * 310 * @see #writeInitializationScript(IMarkupWriter) 311 * @param writer 312 * The markup writer to use. 313 * @param cycle 314 * The current request. 315 */ 316 317 public void writeBodyScript(IMarkupWriter writer, IRequestCycle cycle) 318 { 319 if (!Tapestry.isEmpty(_externalScripts)) 320 writeExternalScripts(writer, cycle); 321 322 if (!(any(_bodyScript) || any(_imageInitializations))) 323 return; 324 325 _builder.beginBodyScript(writer, cycle); 326 327 if (any(_imageInitializations)) 328 { 329 _builder.writeImageInitializations(writer, 330 StringUtils.stripToEmpty(_imageInitializations.toString()), 331 _preloadName, 332 cycle); 333 } 334 335 if (any(_bodyScript)) 336 { 337 _builder.writeBodyScript(writer, StringUtils.stripToEmpty(_bodyScript.toString()), cycle); 338 } 339 340 _builder.endBodyScript(writer, cycle); 341 } 342 343 /** 344 * Writes any image initializations; this should be invoked at the end of the render, after all 345 * the related HTML will have already been streamed to the client and parsed by the web browser. 346 * Earlier versions of Tapestry uses a <code>window.onload</code> event handler. 347 * 348 * @param writer 349 * The markup writer to use. 350 */ 351 352 public void writeInitializationScript(IMarkupWriter writer) 353 { 354 if (!any(_initializationScript) && !any(_postInitializationScript)) 355 return; 356 357 String script = getContent(_initializationScript) + getContent(_postInitializationScript); 358 359 _builder.writeInitializationScript(writer, StringUtils.stripToEmpty(script)); 360 } 361 362 public static String getContent(StringBuffer buffer) 363 { 364 if (buffer == null || buffer.length() < 1) 365 return ""; 366 367 return buffer.toString(); 368 } 369 370 private boolean any(StringBuffer buffer) 371 { 372 return buffer != null && buffer.length() > 0; 373 } 374 }