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.FilenameFilter;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.util.HashMap;
27  import java.util.Properties;
28  
29  import org.apache.commons.configuration.Configuration;
30  import org.apache.commons.io.filefilter.DirectoryFileFilter;
31  import org.apache.commons.lang.StringUtils;
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.turbine.Turbine;
35  import org.apache.turbine.services.InitializationException;
36  import org.apache.turbine.services.TurbineBaseService;
37  import org.apache.turbine.services.pull.TurbinePull;
38  import org.apache.turbine.services.servlet.TurbineServlet;
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 Log log = LogFactory.getLog(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 directory within the skin directory that contains the skin images.
131      */
132     private String imagesDirectory;
133 
134     /***
135      * The name of the css file within the skin directory.
136      */
137     private String cssFile;
138 
139     /***
140      * The flag that determines if the links that are returned are are absolute
141      * or relative.
142      */
143     private boolean wantRelative = false;
144 
145     /***
146      * The skin Properties store.
147      */
148     private HashMap skins = new HashMap();
149 
150     /***
151      * Refresh the service by clearing all skins.
152      */
153     public void refresh()
154     {
155         clearSkins();
156     }
157 
158     /***
159      * Refresh a particular skin by clearing it.
160      * 
161      * @param skinName the name of the skin to clear.
162      */
163     public void refresh(String skinName)
164     {
165         clearSkin(skinName);
166     }
167     
168     /***
169      * Retrieve the Properties for a specific skin.  If they are not yet loaded
170      * they will be.  If the specified skin does not exist properties for the
171      * default skin configured for the webapp will be returned and an error
172      * level message will be written to the log.  If the webapp skin does not
173      * exist the default skin will be used and id that doesn't exist an empty
174      * Properties will be returned.
175      * 
176      * @param skinName the name of the skin whose properties are to be 
177      * retrieved.
178      * @return the Properties for the named skin or the properties for the 
179      * default skin configured for the webapp if the named skin does not exist.
180      */
181     private Properties getSkinProperties(String skinName)
182     {
183         Properties skinProperties = (Properties) skins.get(skinName);
184         return null != skinProperties ? skinProperties : loadSkin(skinName); 
185     }
186 
187     /***
188      * Retrieve a skin property from the named skin.  If the property is not 
189      * defined in the named skin the value for the default skin will be 
190      * provided.  If the named skin does not exist then the skin configured for 
191      * the webapp will be used.  If the webapp skin does not exist the default
192      * skin will be used.  If the default skin does not exist then 
193      * <code>null</code> will be returned.
194      * 
195      * @param skinName the name of the skin to retrieve the property from.
196      * @param key the key to retrieve from the skin.
197      * @return the value of the property for the named skin (defaulting to the 
198      * default skin), the webapp skin, the default skin or <code>null</code>,
199      * depending on whether or not the property or skins exist.
200      */
201     public String get(String skinName, String key)
202     {
203         Properties skinProperties = getSkinProperties(skinName);
204         return skinProperties.getProperty(key);
205     }
206 
207     /***
208      * Retrieve a skin property from the default skin for the webapp.  If the 
209      * property is not defined in the webapp skin the value for the default skin 
210      * will be provided.  If the webapp skin does not exist the default skin 
211      * will be used.  If the default skin does not exist then <code>null</code> 
212      * will be returned.
213      * 
214      * @param key the key to retrieve.
215      * @return the value of the property for the webapp skin (defaulting to the 
216      * default skin), the default skin or <code>null</code>, depending on 
217      * whether or not the property or skins exist.
218      */
219     public String get(String key)
220     {
221         return get(getWebappSkinName(), key);
222     }
223 
224     /***
225      * Provide access to the list of available skin names.
226      * 
227      * @return the available skin names.
228      */
229     public String[] getSkinNames()
230     {
231         File skinsDir = new File(TurbineServlet.getRealPath(skinsDirectory));
232         return skinsDir.list(DirectoryFileFilter.INSTANCE);
233     }
234 
235     /***
236      * Clear the map of stored skins. 
237      */
238     private void clearSkins()
239     {
240         synchronized (skins)
241         {
242             skins = new HashMap();
243         }
244         log.debug("All skins were cleared.");
245     }
246     
247     /***
248      * Clear a particular skin from the map of stored skins.
249      * 
250      * @param skinName the name of the skin to clear.
251      */
252     private void clearSkin(String skinName)
253     {
254         synchronized (skins)
255         {
256             if (!skinName.equals(SKIN_PROPERTY_DEFAULT))
257             {
258                 skins.remove(SKIN_PROPERTY_DEFAULT);
259             }
260             skins.remove(skinName);
261         }
262         log.debug("The skin \"" + skinName 
263                 + "\" was cleared (will also clear \"default\" skin).");
264     }
265 
266     /***
267      * Load the specified skin.
268      * 
269      * @param skinName the name of the skin to load.
270      * @return the Properties for the named skin if it exists, or the skin
271      * configured for the web application if it does not exist, or the default
272      * skin if that does not exist, or an empty Parameters object if even that 
273      * cannot be found.
274      */
275     private synchronized Properties loadSkin(String skinName)
276     {
277         Properties defaultSkinProperties = null;
278         
279         if (!StringUtils.equals(skinName, SKIN_PROPERTY_DEFAULT))
280         {
281             defaultSkinProperties = getSkinProperties(SKIN_PROPERTY_DEFAULT);
282         }
283 
284         // The following line is okay even for default.
285         Properties skinProperties = new Properties(defaultSkinProperties);
286         
287         StringBuffer sb = new StringBuffer();
288         sb.append('/').append(skinsDirectory);
289         sb.append('/').append(skinName);
290         sb.append('/').append(SKIN_PROPS_FILE);
291         if (log.isDebugEnabled())
292         {
293             log.debug("Loading selected skin from: " + sb.toString());
294         }
295 
296         try
297         {
298             // This will NPE if the directory associated with the skin does not
299             // exist, but it is habdled correctly below.
300             InputStream is = TurbineServlet.getResourceAsStream(sb.toString());
301 
302             skinProperties.load(is);
303         }
304         catch (Exception e)
305         {
306             log.error("Cannot load skin: " + skinName + ", from: "
307                     + sb.toString(), e);
308             if (!StringUtils.equals(skinName, getWebappSkinName()) 
309                     && !StringUtils.equals(skinName, SKIN_PROPERTY_DEFAULT))
310             {
311                 log.error("Attempting to return the skin configured for " 
312                         + "webapp instead of " + skinName);
313                 return getSkinProperties(getWebappSkinName());
314             }
315             else if (!StringUtils.equals(skinName, SKIN_PROPERTY_DEFAULT))
316             {
317                 log.error("Return the default skin instead of " + skinName);
318                 return skinProperties; // Already contains the default skin.
319             }
320             else
321             {
322                 log.error("No skins available - returning an empty Properties");
323                 return new Properties();
324             }
325         }
326         
327         // Replace in skins HashMap
328         synchronized (skins)
329         {
330             skins.put(skinName, skinProperties);
331         }
332         
333         return skinProperties;
334     }
335 
336     /***
337      * Get the name of the default skin name for the web application from the 
338      * TurbineResources.properties file. If the property is not present the 
339      * name of the default skin will be returned.  Note that the web application
340      * skin name may be something other than default, in which case its 
341      * properties will default to the skin with the name "default".
342      * 
343      * @return the name of the default skin for the web application.
344      */
345     public String getWebappSkinName()
346     {
347         return Turbine.getConfiguration()
348                 .getString(SKIN_PROPERTY, SKIN_PROPERTY_DEFAULT);
349     }
350 
351     /***
352      * Retrieve the URL for an image that is part of a skin. The images are 
353      * stored in the WEBAPP/resources/ui/skins/[SKIN]/images directory.
354      *
355      * <p>Use this if for some reason your server name, server scheme, or server 
356      * port change on a per request basis. I'm not sure if this would happen in 
357      * a load balanced situation. I think in most cases the image(String image)
358      * method would probably be enough, but I'm not absolutely positive.
359      * 
360      * @param skinName the name of the skin to retrieve the image from.
361      * @param imageId the id of the image whose URL will be generated.
362      * @param serverData the serverData to use as the basis for the URL.
363      */
364     public String image(String skinName, String imageId, ServerData serverData)
365     {
366         return getSkinResource(serverData, skinName, imagesDirectory, imageId);
367     }
368 
369     /***
370      * Retrieve the URL for an image that is part of a skin. The images are 
371      * stored in the WEBAPP/resources/ui/skins/[SKIN]/images directory.
372      * 
373      * @param skinName the name of the skin to retrieve the image from.
374      * @param imageId the id of the image whose URL will be generated.
375      */
376     public String image(String skinName, String imageId)
377     {
378         return image(skinName, imageId, Turbine.getDefaultServerData());
379     }
380 
381     /***
382      * Retrieve the URL for the style sheet that is part of a skin. The style is 
383      * stored in the WEBAPP/resources/ui/skins/[SKIN] directory with the 
384      * filename skin.css
385      *
386      * <p>Use this if for some reason your server name, server scheme, or server 
387      * port change on a per request basis. I'm not sure if this would happen in 
388      * a load balanced situation. I think in most cases the style() method would 
389      * probably be enough, but I'm not absolutely positive.
390      * 
391      * @param skinName the name of the skin to retrieve the style sheet from.
392      * @param serverData the serverData to use as the basis for the URL.
393      */
394     public String getStylecss(String skinName, ServerData serverData)
395     {
396         return getSkinResource(serverData, skinName, null, cssFile);
397     }
398 
399     /***
400      * Retrieve the URL for the style sheet that is part of a skin. The style is 
401      * stored in the WEBAPP/resources/ui/skins/[SKIN] directory with the 
402      * filename skin.css
403      * 
404      * @param skinName the name of the skin to retrieve the style sheet from.
405      */
406     public String getStylecss(String skinName)
407     {
408         return getStylecss(skinName, Turbine.getDefaultServerData());
409     }
410 
411     /***
412      * Retrieve the URL for a given script that is part of a skin. The script is
413      * stored in the WEBAPP/resources/ui/skins/[SKIN] directory.
414      *
415      * <p>Use this if for some reason your server name, server scheme, or server 
416      * port change on a per request basis. I'm not sure if this would happen in 
417      * a load balanced situation. I think in most cases the style() method would 
418      * probably be enough, but I'm not absolutely positive.
419      *
420      * @param skinName the name of the skin to retrieve the image from.
421      * @param filename the name of the script file.
422      * @param serverData the serverData to use as the basis for the URL.
423      */
424     public String getScript(String skinName, String filename,
425             ServerData serverData)
426     {
427         return getSkinResource(serverData, skinName, null, filename);
428     }
429 
430     /***
431      * Retrieve the URL for a given script that is part of a skin. The script is
432      * stored in the WEBAPP/resources/ui/skins/[SKIN] directory.
433      *
434      * @param skinName the name of the skin to retrieve the image from.
435      * @param filename the name of the script file.
436      */
437     public String getScript(String skinName, String filename)
438     {
439         return getScript(skinName, filename, Turbine.getDefaultServerData());
440     }
441 
442     private String stripSlashes(final String path)
443     {
444         if (StringUtils.isEmpty(path))
445         {
446             return "";
447         }
448 
449         String ret = path;
450         int len = ret.length() - 1;
451 
452         if (ret.charAt(len) == '/')
453         {
454             ret = ret.substring(0, len);
455         }
456 
457         if (len > 0 && ret.charAt(0) == '/')
458         {
459             ret = ret.substring(1);
460         }
461 
462         return ret;
463     }
464 
465     /***
466      * Construct the URL to the skin resource.
467      *
468      * @param serverData the serverData to use as the basis for the URL.
469      * @param skinName the name of the skin.
470      * @param subDir the sub-directory in which the resource resides or
471      * <code>null</code> if it is in the root directory of the skin.
472      * @param resourceName the name of the resource to be retrieved.
473      * @return the path to the resource.
474      */
475     private String getSkinResource(ServerData serverData, String skinName,
476             String subDir, String resourceName)
477     {
478         StringBuffer sb = new StringBuffer(skinsDirectory);
479         sb.append("/").append(skinName);
480         if (subDir != null)
481         {
482             sb.append("/").append(subDir);
483         }
484         sb.append("/").append(stripSlashes(resourceName));
485 
486         DataURI du = new DataURI(serverData);
487         du.setScriptName(sb.toString());
488         return wantRelative ? du.getRelativeLink() : du.getAbsoluteLink();
489     }
490 
491     // ---- Service initilization ------------------------------------------
492 
493     /***
494      * Initializes the service.
495      */
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         StringBuffer sb = new StringBuffer();
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     public void shutdown()
521     {
522         clearSkins();
523 
524         setInit(false);
525     }
526 
527 }