View Javadoc

    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  public class TurbineUIService
    58          extends TurbineBaseService
    59          implements UIService
    60  {
    61      /** Logging. */
    62      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     private boolean wantRelative = false;
    143
    144     /**
    145      * The skin Properties store.
    146      */
    147     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         clearSkins();
    155     }
    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         clearSkin(skinName);
    165     }
    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         Properties skinProperties = skins.get(skinName);
    183         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         Properties skinProperties = getSkinProperties(skinName);
    203         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         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         File skinsDir = new File(TurbineServlet.getRealPath(skinsDirectory));
    231         return skinsDir.list(DirectoryFileFilter.INSTANCE);
    232     }
    233
    234     /**
    235      * Clear the map of stored skins.
    236      */
    237     private void clearSkins()
    238     {
    239         synchronized (skins)
    240         {
    241             skins = new HashMap<String, Properties>();
    242         }
    243         log.debug("All skins were cleared.");
    244     }
    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         synchronized (skins)
    254         {
    255             if (!skinName.equals(SKIN_PROPERTY_DEFAULT))
    256             {
    257                 skins.remove(SKIN_PROPERTY_DEFAULT);
    258             }
    259             skins.remove(skinName);
    260         }
    261         log.debug("The skin \"" + skinName
    262                 + "\" was cleared (will also clear \"default\" skin).");
    263     }
    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         Properties defaultSkinProperties = null;
    277
    278         if (!StringUtils.equals(skinName, SKIN_PROPERTY_DEFAULT))
    279         {
    280             defaultSkinProperties = getSkinProperties(SKIN_PROPERTY_DEFAULT);
    281         }
    282
    283         // The following line is okay even for default.
    284         Properties skinProperties = new Properties(defaultSkinProperties);
    285
    286         StringBuilder sb = new StringBuilder();
    287         sb.append('/').append(skinsDirectory);
    288         sb.append('/').append(skinName);
    289         sb.append('/').append(SKIN_PROPS_FILE);
    290         if (log.isDebugEnabled())
    291         {
    292             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             InputStream is = TurbineServlet.getResourceAsStream(sb.toString());
    300
    301             skinProperties.load(is);
    302         }
    303         catch (Exception e)
    304         {
    305             log.error("Cannot load skin: " + skinName + ", from: "
    306                     + sb.toString(), e);
    307             if (!StringUtils.equals(skinName, getWebappSkinName())
    308                     && !StringUtils.equals(skinName, SKIN_PROPERTY_DEFAULT))
    309             {
    310                 log.error("Attempting to return the skin configured for "
    311                         + "webapp instead of " + skinName);
    312                 return getSkinProperties(getWebappSkinName());
    313             }
    314             else if (!StringUtils.equals(skinName, SKIN_PROPERTY_DEFAULT))
    315             {
    316                 log.error("Return the default skin instead of " + skinName);
    317                 return skinProperties; // Already contains the default skin.
    318             }
    319             else
    320             {
    321                 log.error("No skins available - returning an empty Properties");
    322                 return new Properties();
    323             }
    324         }
    325
    326         // Replace in skins HashMap
    327         synchronized (skins)
    328         {
    329             skins.put(skinName, skinProperties);
    330         }
    331
    332         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         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         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         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         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         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         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         return getScript(skinName, filename, Turbine.getDefaultServerData());
    439     }
    440
    441     private String stripSlashes(final String path)
    442     {
    443         if (StringUtils.isEmpty(path))
    444         {
    445             return "";
    446         }
    447
    448         String ret = path;
    449         int len = ret.length() - 1;
    450
    451         if (ret.charAt(len) == '/')
    452         {
    453             ret = ret.substring(0, len);
    454         }
    455
    456         if (len > 0 && ret.charAt(0) == '/')
    457         {
    458             ret = ret.substring(1);
    459         }
    460
    461         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         StringBuilder sb = new StringBuilder(skinsDirectory);
    478         sb.append("/").append(skinName);
    479         if (subDir != null)
    480         {
    481             sb.append("/").append(subDir);
    482         }
    483         sb.append("/").append(stripSlashes(resourceName));
    484
    485         DataURI du = new DataURI(serverData);
    486         du.setScriptName(sb.toString());
    487         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         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         StringBuilder sb = new StringBuilder();
    503         sb.append(stripSlashes(TurbinePull.getResourcesDirectory()));
    504         sb.append("/");
    505         sb.append(stripSlashes(
    506                 cfg.getString(SKINDIR_PROPERTY, SKINS_DIRECTORY)));
    507         skinsDirectory = sb.toString();
    508
    509         imagesDirectory = stripSlashes(
    510                 cfg.getString(IMAGEDIR_PROPERTY, IMAGES_DIRECTORY));
    511         cssFile = cfg.getString(CSS_PROPERTY, DEFAULT_SKIN_CSS_FILE);
    512         wantRelative = cfg.getBoolean(RELATIVE_PROPERTY, false);
    513
    514         setInit(true);
    515     }
    516
    517     /**
    518      * Returns to uninitialized state.
    519      */
    520     @Override
    521     public void shutdown()
    522     {
    523         clearSkins();
    524
    525         setInit(false);
    526     }
    527
    528 }