1 package org.apache.turbine.services.pull.util;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 import java.io.InputStream;
20 import java.util.Properties;
21
22 import org.apache.commons.configuration.Configuration;
23 import org.apache.commons.lang.StringUtils;
24 import org.apache.commons.logging.Log;
25 import org.apache.commons.logging.LogFactory;
26 import org.apache.turbine.Turbine;
27 import org.apache.turbine.om.security.User;
28 import org.apache.turbine.services.pull.ApplicationTool;
29 import org.apache.turbine.services.pull.TurbinePull;
30 import org.apache.turbine.services.servlet.TurbineServlet;
31 import org.apache.turbine.util.RunData;
32 import org.apache.turbine.util.ServerData;
33 import org.apache.turbine.util.uri.DataURI;
34
35 /***
36 * UIManager.java
37 * <br>
38 * Manages all UI elements for a Turbine Application. Any
39 * UI element can be accessed in any template using the
40 * $ui handle (assuming you use the default PullService
41 * configuration). So, for example, you could access
42 * the background colour for your pages by using
43 * $ui.bgcolor
44 * <p>
45 * <h3>Questions:</h3>
46 * What is the best way to allow an application
47 * to be skinned. And how to allow the flexible
48 * altering of a particular UI element in certain
49 * parts of the template hierarchy. For example
50 * on one section of your site you might like
51 * a certain bgcolor, on another part of your
52 * site you might want another. How can be let
53 * the designer specify these properties and
54 * still use the single $app.ui.bgcolor in
55 * all the templates.
56 * <p>
57 * It would also be very cool to use some form
58 * of inheritence for UI elements. Say a $ui.bgcolor
59 * is used in a template where the bgcolor is not
60 * set for that part of hierarch, it would be cool
61 * if it could find the setting for the bgcolor
62 * in the parent directory. So you could override
63 * a UI element where you wanted and the system
64 * would fall back to the parent when necessary.
65 * <p>
66 * How to specify skins, how to deal with images,
67 * how could this be handled with a web app.
68 * <p>
69 *
70 * This is an application pull tool for the template system. You should <b>not</b>
71 * use it in a normal application!
72 *
73 *
74 * @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a>
75 * @author <a href="mailto:james_coltman@majorband.co.uk">James Coltman</a>
76 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
77 * @author <a href="thomas.vandahl@tewisoft.de">Thomas Vandahl</a>
78 * @version $Id: UIManager.java 290076 2005-09-19 07:13:51Z henning $
79 */
80 public class UIManager implements ApplicationTool
81 {
82 /*** Logging */
83 private static Log log = LogFactory.getLog(UIManager.class);
84
85 /***
86 * The location of the skins within the application
87 * resources directory.
88 */
89 private static final String SKINS_DIRECTORY = "/ui/skins";
90
91 /***
92 * The name of the directory where images are
93 * stored for this skin.
94 */
95 private static final String IMAGES_DIRECTORY = "/images";
96
97 /***
98 * Property tag for the default skin that is to be
99 * used for the web application.
100 */
101 private static final String SKIN_PROPERTY = "tool.ui.skin";
102
103 /***
104 * Property tag for the image directory inside the skin that is to be
105 * used for the web application.
106 */
107 private static final String IMAGEDIR_PROPERTY = "tool.ui.dir.image";
108
109 /***
110 * Property tag for the skin directory that is to be
111 * used for the web application.
112 */
113 private static final String SKINDIR_PROPERTY = "tool.ui.dir.skin";
114
115 /***
116 * Property tag for the css file that is to be
117 * used for the web application.
118 */
119 private static final String CSS_PROPERTY = "tool.ui.css";
120
121 /***
122 * Property tag for the css file that is to be
123 * used for the web application.
124 */
125 private static final String RELATIVE_PROPERTY = "tool.ui.want.relative";
126
127 /***
128 * Default skin name. This name actually represents
129 * a directory in the WEBAPP/resources/ui/skins
130 * directory. There is a file called skin.props
131 * which actually contains the name/value pairs.
132 */
133 private static final String SKIN_PROPERTY_DEFAULT = "default";
134
135 /***
136 * Attribute name of skinName value in User's temp hashmap.
137 */
138 private static final String SKIN_ATTRIBUTE =
139 UIManager.class.getName()+ ".skin";
140
141 /***
142 * The actual skin being used for the webapp.
143 */
144 private String skinName;
145
146 /***
147 * The skins directory.
148 */
149 private String skinsDirectory;
150
151 /***
152 * The file within the skin directory that actually
153 * contains the name/value pairs for the skin.
154 */
155 private static final String SKIN_PROPS_FILE = "skin.props";
156
157 /***
158 * The file name for the skin style sheet.
159 */
160 private static final String DEFAULT_SKIN_CSS_FILE = "skin.css";
161
162 /***
163 * This the resources directory relative to the
164 * webapp context. Used for constructing correct
165 * URIs for retrieving images in image().
166 */
167 private String resourcesDirectory;
168 private String imagesDirectory;
169 private String cssFile;
170
171 private boolean wantRelative = false;
172
173 /***
174 * Properties to hold the name/value pairs
175 * for the skin.
176 */
177 private Properties skinProperties;
178
179 /***
180 * Initialize the UIManager object.
181 *
182 * @param data This is null, RunData or User depending upon specified tool scope.
183 */
184 public void init(Object data)
185 {
186 /***
187 * Store the resources directory for use in image().
188 */
189 Configuration cfg = Turbine.getConfiguration();
190
191 resourcesDirectory = stripSlashes(TurbinePull.getResourcesDirectory());
192
193 if (data == null)
194 {
195 log.debug("UI Manager scope is global");
196 setSkin();
197 }
198 else if (data instanceof RunData)
199 {
200 log.debug("UI Manager scope is request");
201 setSkin((RunData) data);
202 }
203 else if (data instanceof User)
204 {
205 log.debug("UI Manager scope is session");
206 setSkin((User) data);
207 }
208
209 skinsDirectory = stripSlashes(cfg.getString(SKINDIR_PROPERTY, SKINS_DIRECTORY));
210
211 imagesDirectory = stripSlashes(cfg.getString(IMAGEDIR_PROPERTY, IMAGES_DIRECTORY));
212
213 cssFile = cfg.getString(CSS_PROPERTY, DEFAULT_SKIN_CSS_FILE);
214
215 wantRelative = cfg.getBoolean(RELATIVE_PROPERTY, false);
216
217 loadSkin();
218 }
219
220 private String stripSlashes(final String path)
221 {
222 if (StringUtils.isEmpty(path))
223 {
224 return "";
225 }
226
227 String ret = path;
228 int len = ret.length() - 1;
229
230 if (ret.charAt(len) == '/')
231 {
232 ret = ret.substring(0, len);
233 }
234
235 if (len > 0 && ret.charAt(0) == '/')
236 {
237 ret = ret.substring(1);
238 }
239
240 return ret;
241 }
242
243 /***
244 * This lets the tool know that it should be
245 * refreshed. The tool can perform whatever actions
246 * are necessary to refresh itself. This is necessary
247 * for sane development where you probably want the
248 * tools to refresh themselves on every request.
249 */
250 public void refresh()
251 {
252 log.debug("Refreshing UI manager");
253
254 loadSkin();
255 }
256
257 /***
258 * Retrieve a property from the properties held
259 * within the properties file for this skin.
260 */
261 public String get(String key)
262 {
263 return skinProperties.getProperty(key);
264 }
265
266 /***
267 * Retrieve the skin name.
268 */
269 public String getSkin()
270 {
271 return skinName;
272 }
273
274 /***
275 * Retrieve the URL for an image that is part
276 * of a skin. The images are stored in the
277 * WEBAPP/resources/ui/skins/<SKIN>/images
278 * directory.
279 *
280 * Use this if for some reason your server name,
281 * server scheme, or server port change on a
282 * per request basis. I'm not sure if this
283 * would happend in a load balanced situation.
284 * I think in most cases the image(String image)
285 * method would probably be enough, but I'm not
286 * absolutely positive.
287 */
288 public String image(String imageId, RunData data)
289 {
290 DataURI du = new DataURI(data);
291
292 StringBuffer sb = new StringBuffer();
293
294 sb.append(resourcesDirectory).
295 append('/').
296 append(skinsDirectory).
297 append('/').
298 append(getSkin()).
299 append('/').
300 append(imagesDirectory).
301 append('/').
302 append(stripSlashes(imageId));
303
304 du.setScriptName(sb.toString());
305
306 return wantRelative ? du.getRelativeLink() : du.getAbsoluteLink();
307 }
308
309 /***
310 * Retrieve the URL for an image that is part
311 * of a skin. The images are stored in the
312 * WEBAPP/resources/ui/skins/<SKIN>/images
313 * directory.
314 */
315 public String image(String imageId)
316 {
317 ServerData sd = Turbine.getDefaultServerData();
318 DataURI du = new DataURI(sd);
319
320 StringBuffer sb = new StringBuffer();
321
322 sb.append(resourcesDirectory).
323 append('/').
324 append(skinsDirectory).
325 append('/').
326 append(getSkin()).
327 append('/').
328 append(imagesDirectory).
329 append('/').
330 append(stripSlashes(imageId));
331
332 du.setScriptName(sb.toString());
333 return wantRelative ? du.getRelativeLink() : du.getAbsoluteLink();
334 }
335
336 /***
337 * Retrieve the URL for the style sheet that is part
338 * of a skin. The style is stored in the
339 * WEBAPP/resources/ui/skins/<SKIN> directory with the
340 * filename skin.css
341 *
342 * Use this if for some reason your server name,
343 * server scheme, or server port change on a
344 * per request basis. I'm not sure if this
345 * would happend in a load balanced situation.
346 * I think in most cases the style()
347 * method would probably be enough, but I'm not
348 * absolutely positive.
349 */
350 public String getStylecss(RunData data)
351 {
352 return getScript(cssFile, data);
353 }
354
355 /***
356 * Retrieve the URL for the style sheet that is part
357 * of a skin. The style is stored in the
358 * WEBAPP/resources/ui/skins/<SKIN> directory with the
359 * filename skin.css
360 */
361 public String getStylecss()
362 {
363 return getScript(cssFile);
364 }
365
366 /***
367 * Retrieve the URL for a given script that is part
368 * of a skin. The script is stored in the
369 * WEBAPP/resources/ui/skins/<SKIN> directory
370 */
371 public String getScript(String filename, RunData data)
372 {
373 DataURI du = new DataURI(data);
374
375 StringBuffer sb = new StringBuffer();
376
377 sb.append(resourcesDirectory).
378 append('/').
379 append(skinsDirectory).
380 append('/').
381 append(getSkin()).
382 append('/').
383 append(stripSlashes(filename));
384
385 du.setScriptName(sb.toString());
386 return wantRelative ? du.getRelativeLink() : du.getAbsoluteLink();
387 }
388
389 /***
390 * Retrieve the URL for a given script that is part
391 * of a skin. The script is stored in the
392 * WEBAPP/resources/ui/skins/<SKIN> directory
393 */
394 public String getScript(String filename)
395 {
396 ServerData sd = Turbine.getDefaultServerData();
397 DataURI du = new DataURI(sd);
398
399 StringBuffer sb = new StringBuffer();
400
401 sb.append(resourcesDirectory).
402 append('/').
403 append(skinsDirectory).
404 append('/').
405 append(getSkin()).
406 append('/').
407 append(stripSlashes(filename));
408
409 du.setScriptName(sb.toString());
410 return wantRelative ? du.getRelativeLink() : du.getAbsoluteLink();
411 }
412
413 /***
414 * Load the specified skin. In development mode
415 * this may occur frequently as the skin properties
416 * are being changed.
417 */
418 private void loadSkin()
419 {
420 skinProperties = new Properties();
421
422 try
423 {
424 StringBuffer sb = new StringBuffer();
425
426 sb.append(resourcesDirectory).
427 append('/').
428 append(skinsDirectory).
429 append('/').
430 append(getSkin()).
431 append('/').
432 append(SKIN_PROPS_FILE);
433
434 InputStream is = TurbineServlet.getResourceAsStream(sb.toString());
435
436 skinProperties.load(is);
437 }
438 catch (Exception e)
439 {
440 log.error("Cannot load skin: " + skinName);
441 }
442 }
443
444 /***
445 * Set the skin name to the skin from the TR.props
446 * file. If the property is not present use the
447 * default skin.
448 */
449 public void setSkin()
450 {
451 this.skinName = Turbine.getConfiguration()
452 .getString(SKIN_PROPERTY,
453 SKIN_PROPERTY_DEFAULT);
454 }
455
456 /***
457 * Set the skin name to the specified skin.
458 *
459 * @param skinName the skin name to use.
460 */
461 public void setSkin(String skinName)
462 {
463 this.skinName = skinName;
464 }
465
466 /***
467 * Set the skin name when the tool is configured to be
468 * loaded on a per-request basis. By default it calls getSkin
469 * to return the skin specified in TR.properties. Developers can
470 * write a subclass of UIManager that overrides this method to
471 * determine the skin to use based on information held in the request.
472 *
473 * @param data a RunData instance
474 */
475 protected void setSkin(RunData data)
476 {
477 setSkin();
478 }
479
480 /***
481 * Set the skin name when the tool is configured to be
482 * loaded on a per-session basis. It the user's temp hashmap contains
483 * a value in the attribute specified by the String constant SKIN_ATTRIBUTE
484 * then that is returned. Otherwise it calls getSkin to return the skin
485 * specified in TR.properties.
486 *
487 * @param user a User instance
488 */
489 protected void setSkin(User user)
490 {
491 if (user.getTemp(SKIN_ATTRIBUTE) == null)
492 {
493 setSkin();
494 }
495 else
496 {
497 setSkin((String) user.getTemp(SKIN_ATTRIBUTE));
498 }
499 }
500
501 /***
502 * Set the skin name user's temp hashmap for the current session.
503 *
504 * @param user a User instance
505 * @param skin the skin name for the session
506 */
507 public static void setSkin(User user, String skin)
508 {
509 user.setTemp(SKIN_ATTRIBUTE, skin);
510 }
511 }