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.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 }