1 package org.apache.turbine.services.ui; 2 3 /* 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 */ 21 22 import java.io.File; 23 import java.io.FilenameFilter; 24 import java.io.InputStream; 25 import java.util.Properties; 26 import java.util.concurrent.ConcurrentHashMap; 27 28 import org.apache.commons.configuration2.Configuration; 29 import org.apache.commons.lang3.StringUtils; 30 import org.apache.logging.log4j.LogManager; 31 import org.apache.logging.log4j.Logger; 32 import org.apache.turbine.Turbine; 33 import org.apache.turbine.services.InitializationException; 34 import org.apache.turbine.services.TurbineBaseService; 35 import org.apache.turbine.services.TurbineServices; 36 import org.apache.turbine.services.pull.PullService; 37 import org.apache.turbine.services.pull.tools.UITool; 38 import org.apache.turbine.services.servlet.ServletService; 39 import org.apache.turbine.util.ServerData; 40 import org.apache.turbine.util.uri.DataURI; 41 42 /** 43 * The UI service provides for shared access to User Interface (skin) files, 44 * as well as the ability for non-default skin files to inherit properties from 45 * a default skin. Use TurbineUI to access skin properties from your screen 46 * classes and action code. UITool is provided as a pull tool for accessing 47 * skin properties from your templates. 48 * 49 * @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a> 50 * @author <a href="mailto:james_coltman@majorband.co.uk">James Coltman</a> 51 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a> 52 * @author <a href="mailto:seade@backstagetech.com.au">Scott Eade</a> 53 * @author <a href="thomas.vandahl@tewisoft.de">Thomas Vandahl</a> 54 * @version $Id$ 55 * @see UIService 56 * @see UITool 57 */ 58 public class TurbineUIService 59 extends TurbineBaseService 60 implements UIService 61 { 62 /** Logging. */ 63 private static final Logger log = LogManager.getLogger(TurbineUIService.class); 64 65 /** 66 * The location of the skins within the application resources directory. 67 */ 68 private static final String SKINS_DIRECTORY = "/ui/skins"; 69 70 /** 71 * The name of the directory where images are stored for this skin. 72 */ 73 private static final String IMAGES_DIRECTORY = "/images"; 74 75 /** 76 * Property tag for the default skin that is to be used for the web 77 * application. 78 */ 79 private static final String SKIN_PROPERTY = "tool.ui.skin"; 80 81 /** 82 * Property tag for the image directory inside the skin that is to be used 83 * for the web application. 84 */ 85 private static final String IMAGEDIR_PROPERTY = "tool.ui.dir.image"; 86 87 /** 88 * Property tag for the skin directory that is to be used for the web 89 * application. 90 */ 91 private static final String SKINDIR_PROPERTY = "tool.ui.dir.skin"; 92 93 /** 94 * Property tag for the css file that is to be used for the web application. 95 */ 96 private static final String CSS_PROPERTY = "tool.ui.css"; 97 98 /** 99 * Property tag for indicating if relative links are wanted for the web 100 * application. 101 */ 102 private static final String RELATIVE_PROPERTY = "tool.ui.want.relative"; 103 104 /** 105 * Default skin name. This name refers to a directory in the 106 * WEBAPP/resources/ui/skins directory. There is a file called skin.props 107 * which contains the name/value pairs to be made available via the skin. 108 */ 109 public static final String SKIN_PROPERTY_DEFAULT = "default"; 110 111 /** 112 * The skins directory, qualified by the resources directory (which is 113 * relative to the webapp context). This is used for constructing URIs and 114 * for retrieving skin files. 115 */ 116 private String skinsDirectory; 117 118 /** 119 * The file within the skin directory that contains the name/value pairs for 120 * the skin. 121 */ 122 private static final String SKIN_PROPS_FILE = "skin.props"; 123 124 /** 125 * The file name for the skin style sheet. 126 */ 127 private static final String DEFAULT_SKIN_CSS_FILE = "skin.css"; 128 129 /** 130 * The servlet service. 131 */ 132 private ServletService servletService; 133 134 /** 135 * The directory within the skin directory that contains the skin images. 136 */ 137 private String imagesDirectory; 138 139 /** 140 * The name of the css file within the skin directory. 141 */ 142 private String cssFile; 143 144 /** 145 * The flag that determines if the links that are returned are are absolute 146 * or relative. 147 */ 148 private boolean wantRelative = false; 149 150 /** 151 * The skin Properties store. 152 */ 153 private ConcurrentHashMap<String, Properties> skins = new ConcurrentHashMap<String, Properties>(); 154 155 /** 156 * Refresh the service by clearing all skins. 157 */ 158 @Override 159 public void refresh() 160 { 161 clearSkins(); 162 } 163 164 /** 165 * Refresh a particular skin by clearing it. 166 * 167 * @param skinName the name of the skin to clear. 168 */ 169 @Override 170 public void refresh(String skinName) 171 { 172 clearSkin(skinName); 173 } 174 175 /** 176 * Retrieve the Properties for a specific skin. If they are not yet loaded 177 * they will be. If the specified skin does not exist properties for the 178 * default skin configured for the webapp will be returned and an error 179 * level message will be written to the log. If the webapp skin does not 180 * exist the default skin will be used and id that doesn't exist an empty 181 * Properties will be returned. 182 * 183 * @param skinName the name of the skin whose properties are to be 184 * retrieved. 185 * @return the Properties for the named skin or the properties for the 186 * default skin configured for the webapp if the named skin does not exist. 187 */ 188 private Properties getSkinProperties(String skinName) 189 { 190 Properties skinProperties = skins.get(skinName); 191 return null != skinProperties ? skinProperties : loadSkin(skinName); 192 } 193 194 /** 195 * Retrieve a skin property from the named skin. If the property is not 196 * defined in the named skin the value for the default skin will be 197 * provided. If the named skin does not exist then the skin configured for 198 * the webapp will be used. If the webapp skin does not exist the default 199 * skin will be used. If the default skin does not exist then 200 * <code>null</code> will be returned. 201 * 202 * @param skinName the name of the skin to retrieve the property from. 203 * @param key the key to retrieve from the skin. 204 * @return the value of the property for the named skin (defaulting to the 205 * default skin), the webapp skin, the default skin or <code>null</code>, 206 * depending on whether or not the property or skins exist. 207 */ 208 @Override 209 public String get(String skinName, String key) 210 { 211 Properties skinProperties = getSkinProperties(skinName); 212 return skinProperties.getProperty(key); 213 } 214 215 /** 216 * Retrieve a skin property from the default skin for the webapp. If the 217 * property is not defined in the webapp skin the value for the default skin 218 * will be provided. If the webapp skin does not exist the default skin 219 * will be used. If the default skin does not exist then <code>null</code> 220 * will be returned. 221 * 222 * @param key the key to retrieve. 223 * @return the value of the property for the webapp skin (defaulting to the 224 * default skin), the default skin or <code>null</code>, depending on 225 * whether or not the property or skins exist. 226 */ 227 @Override 228 public String get(String key) 229 { 230 return get(getWebappSkinName(), key); 231 } 232 233 /** 234 * Provide access to the list of available skin names. 235 * 236 * @return the available skin names. 237 */ 238 @Override 239 public String[] getSkinNames() 240 { 241 File skinsDir = new File(servletService.getRealPath(skinsDirectory)); 242 return skinsDir.list(new FilenameFilter() 243 { 244 @Override 245 public boolean accept(File dir, String name) 246 { 247 File directory = new File(dir, name); 248 return directory.isDirectory(); 249 } 250 }); 251 } 252 253 /** 254 * Clear the map of stored skins. 255 */ 256 private void clearSkins() 257 { 258 skins.clear(); 259 log.debug("All skins were cleared."); 260 } 261 262 /** 263 * Clear a particular skin from the map of stored skins. 264 * 265 * @param skinName the name of the skin to clear. 266 */ 267 private void clearSkin(String skinName) 268 { 269 if (!skinName.equals(SKIN_PROPERTY_DEFAULT)) 270 { 271 skins.remove(SKIN_PROPERTY_DEFAULT); 272 } 273 skins.remove(skinName); 274 log.debug("The skin \"{}\" was cleared (will also clear \"default\" skin).", skinName); 275 } 276 277 /** 278 * Load the specified skin. 279 * 280 * @param skinName the name of the skin to load. 281 * @return the Properties for the named skin if it exists, or the skin 282 * configured for the web application if it does not exist, or the default 283 * skin if that does not exist, or an empty Parameters object if even that 284 * cannot be found. 285 */ 286 private Properties loadSkin(String skinName) 287 { 288 Properties defaultSkinProperties = null; 289 290 if (!StringUtils.equals(skinName, SKIN_PROPERTY_DEFAULT)) 291 { 292 defaultSkinProperties = getSkinProperties(SKIN_PROPERTY_DEFAULT); 293 } 294 295 // The following line is okay even for default. 296 Properties skinProperties = new Properties(defaultSkinProperties); 297 298 StringBuilder sb = new StringBuilder(); 299 sb.append('/').append(skinsDirectory); 300 sb.append('/').append(skinName); 301 sb.append('/').append(SKIN_PROPS_FILE); 302 log.debug("Loading selected skin from: {}", sb::toString); 303 304 try (InputStream is = servletService.getResourceAsStream(sb.toString())) 305 { 306 // This will NPE if the directory associated with the skin does not 307 // exist, but it is handled correctly below. 308 skinProperties.load(is); 309 } 310 catch (Exception e) 311 { 312 log.error("Cannot load skin: {}, from: {}", skinName, sb.toString(), e); 313 if (!StringUtils.equals(skinName, getWebappSkinName()) 314 && !StringUtils.equals(skinName, SKIN_PROPERTY_DEFAULT)) 315 { 316 log.error("Attempting to return the skin configured for webapp instead of {}", skinName); 317 return getSkinProperties(getWebappSkinName()); 318 } 319 else if (!StringUtils.equals(skinName, SKIN_PROPERTY_DEFAULT)) 320 { 321 log.error("Return the default skin instead of {}", skinName); 322 return skinProperties; // Already contains the default skin. 323 } 324 else 325 { 326 log.error("No skins available - returning an empty Properties"); 327 return new Properties(); 328 } 329 } 330 331 // Replace in skins HashMap 332 skins.put(skinName, skinProperties); 333 334 return skinProperties; 335 } 336 337 /** 338 * Get the name of the default skin name for the web application from the 339 * TurbineResources.properties file. If the property is not present the 340 * name of the default skin will be returned. Note that the web application 341 * skin name may be something other than default, in which case its 342 * properties will default to the skin with the name "default". 343 * 344 * @return the name of the default skin for the web application. 345 */ 346 @Override 347 public String getWebappSkinName() 348 { 349 return Turbine.getConfiguration() 350 .getString(SKIN_PROPERTY, SKIN_PROPERTY_DEFAULT); 351 } 352 353 /** 354 * Retrieve the URL for an image that is part of a skin. The images are 355 * stored in the WEBAPP/resources/ui/skins/[SKIN]/images directory. 356 * 357 * <p>Use this if for some reason your server name, server scheme, or server 358 * port change on a per request basis. I'm not sure if this would happen in 359 * a load balanced situation. I think in most cases the image(String image) 360 * method would probably be enough, but I'm not absolutely positive. 361 * 362 * @param skinName the name of the skin to retrieve the image from. 363 * @param imageId the id of the image whose URL will be generated. 364 * @param serverData the serverData to use as the basis for the URL. 365 */ 366 @Override 367 public String image(String skinName, String imageId, ServerData serverData) 368 { 369 return getSkinResource(serverData, skinName, imagesDirectory, imageId); 370 } 371 372 /** 373 * Retrieve the URL for an image that is part of a skin. The images are 374 * stored in the WEBAPP/resources/ui/skins/[SKIN]/images directory. 375 * 376 * @param skinName the name of the skin to retrieve the image from. 377 * @param imageId the id of the image whose URL will be generated. 378 */ 379 @Override 380 public String image(String skinName, String imageId) 381 { 382 return image(skinName, imageId, Turbine.getDefaultServerData()); 383 } 384 385 /** 386 * Retrieve the URL for the style sheet that is part of a skin. The style is 387 * stored in the WEBAPP/resources/ui/skins/[SKIN] directory with the 388 * filename skin.css 389 * 390 * <p>Use this if for some reason your server name, server scheme, or server 391 * port change on a per request basis. I'm not sure if this would happen in 392 * a load balanced situation. I think in most cases the style() method would 393 * probably be enough, but I'm not absolutely positive. 394 * 395 * @param skinName the name of the skin to retrieve the style sheet from. 396 * @param serverData the serverData to use as the basis for the URL. 397 */ 398 @Override 399 public String getStylecss(String skinName, ServerData serverData) 400 { 401 return getSkinResource(serverData, skinName, null, cssFile); 402 } 403 404 /** 405 * Retrieve the URL for the style sheet that is part of a skin. The style is 406 * stored in the WEBAPP/resources/ui/skins/[SKIN] directory with the 407 * filename skin.css 408 * 409 * @param skinName the name of the skin to retrieve the style sheet from. 410 */ 411 @Override 412 public String getStylecss(String skinName) 413 { 414 return getStylecss(skinName, Turbine.getDefaultServerData()); 415 } 416 417 /** 418 * Retrieve the URL for a given script that is part of a skin. The script is 419 * stored in the WEBAPP/resources/ui/skins/[SKIN] directory. 420 * 421 * <p>Use this if for some reason your server name, server scheme, or server 422 * port change on a per request basis. I'm not sure if this would happen in 423 * a load balanced situation. I think in most cases the style() method would 424 * probably be enough, but I'm not absolutely positive. 425 * 426 * @param skinName the name of the skin to retrieve the image from. 427 * @param filename the name of the script file. 428 * @param serverData the serverData to use as the basis for the URL. 429 */ 430 @Override 431 public String getScript(String skinName, String filename, 432 ServerData serverData) 433 { 434 return getSkinResource(serverData, skinName, null, filename); 435 } 436 437 /** 438 * Retrieve the URL for a given script that is part of a skin. The script is 439 * stored in the WEBAPP/resources/ui/skins/[SKIN] directory. 440 * 441 * @param skinName the name of the skin to retrieve the image from. 442 * @param filename the name of the script file. 443 */ 444 @Override 445 public String getScript(String skinName, String filename) 446 { 447 return getScript(skinName, filename, Turbine.getDefaultServerData()); 448 } 449 450 private String stripSlashes(final String path) 451 { 452 if (StringUtils.isEmpty(path)) 453 { 454 return ""; 455 } 456 457 String ret = path; 458 int len = ret.length() - 1; 459 460 if (ret.charAt(len) == '/') 461 { 462 ret = ret.substring(0, len); 463 } 464 465 if (len > 0 && ret.charAt(0) == '/') 466 { 467 ret = ret.substring(1); 468 } 469 470 return ret; 471 } 472 473 /** 474 * Construct the URL to the skin resource. 475 * 476 * @param serverData the serverData to use as the basis for the URL. 477 * @param skinName the name of the skin. 478 * @param subDir the sub-directory in which the resource resides or 479 * <code>null</code> if it is in the root directory of the skin. 480 * @param resourceName the name of the resource to be retrieved. 481 * @return the path to the resource. 482 */ 483 private String getSkinResource(ServerData serverData, String skinName, 484 String subDir, String resourceName) 485 { 486 StringBuilder sb = new StringBuilder(skinsDirectory); 487 sb.append("/").append(skinName); 488 if (subDir != null) 489 { 490 sb.append("/").append(subDir); 491 } 492 sb.append("/").append(stripSlashes(resourceName)); 493 494 DataURIl/uri/DataURI.html#DataURI">DataURI du = new DataURI(serverData); 495 du.setScriptName(sb.toString()); 496 return wantRelative ? du.getRelativeLink() : du.getAbsoluteLink(); 497 } 498 499 // ---- Service initilization ------------------------------------------ 500 501 /** 502 * Initializes the service. 503 */ 504 @Override 505 public void init() throws InitializationException 506 { 507 Configuration cfg = Turbine.getConfiguration(); 508 509 servletService = (ServletService)TurbineServices.getInstance().getService(ServletService.SERVICE_NAME); 510 PullService./../org/apache/turbine/services/pull/PullService.html#PullService">PullService pullService = (PullService)TurbineServices.getInstance().getService(PullService.SERVICE_NAME); 511 // Get the resources directory that is specified in the TR.props or 512 // default to "resources", relative to the webapp. 513 StringBuilder sb = new StringBuilder(); 514 sb.append(stripSlashes(pullService.getResourcesDirectory())); 515 sb.append("/"); 516 sb.append(stripSlashes( 517 cfg.getString(SKINDIR_PROPERTY, SKINS_DIRECTORY))); 518 skinsDirectory = sb.toString(); 519 520 imagesDirectory = stripSlashes( 521 cfg.getString(IMAGEDIR_PROPERTY, IMAGES_DIRECTORY)); 522 cssFile = cfg.getString(CSS_PROPERTY, DEFAULT_SKIN_CSS_FILE); 523 wantRelative = cfg.getBoolean(RELATIVE_PROPERTY, false); 524 525 setInit(true); 526 } 527 528 /** 529 * Returns to uninitialized state. 530 */ 531 @Override 532 public void shutdown() 533 { 534 clearSkins(); 535 setInit(false); 536 } 537 }