1 package org.apache.turbine.services.ui;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
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
299
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;
319 }
320 else
321 {
322 log.error("No skins available - returning an empty Properties");
323 return new Properties();
324 }
325 }
326
327
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
492
493 /***
494 * Initializes the service.
495 */
496 public void init() throws InitializationException
497 {
498 Configuration cfg = Turbine.getConfiguration();
499
500
501
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 }