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.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
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
298
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;
318 }
319 else
320 {
321 log.error("No skins available - returning an empty Properties");
322 return new Properties();
323 }
324 }
325
326
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
491
492 /***
493 * Initializes the service.
494 */
495 public void init() throws InitializationException
496 {
497 Configuration cfg = Turbine.getConfiguration();
498
499
500
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 }