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