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 skins = new HashMap();
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 = (Properties) 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();
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         StringBuffer sb = new StringBuffer();
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         StringBuffer sb = new StringBuffer(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     public void init() throws InitializationException
496     {
497         Configuration cfg = Turbine.getConfiguration();
498 
499         // Get the resources directory that is specified in the TR.props or 
500         // default to "resources", relative to the webapp.
501         StringBuffer sb = new StringBuffer();
502         sb.append(stripSlashes(TurbinePull.getResourcesDirectory()));
503         sb.append("/");
504         sb.append(stripSlashes(
505                 cfg.getString(SKINDIR_PROPERTY, SKINS_DIRECTORY)));
506         skinsDirectory = sb.toString();
507 
508         imagesDirectory = stripSlashes(
509                 cfg.getString(IMAGEDIR_PROPERTY, IMAGES_DIRECTORY));
510         cssFile = cfg.getString(CSS_PROPERTY, DEFAULT_SKIN_CSS_FILE);
511         wantRelative = cfg.getBoolean(RELATIVE_PROPERTY, false);
512 
513         setInit(true);
514     }
515 
516     /***
517      * Returns to uninitialized state.
518      */
519     public void shutdown()
520     {
521         clearSkins();
522 
523         setInit(false);
524     }
525 
526 }