001: package org.apache.turbine.services.ui;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.io.File;
023: import java.io.InputStream;
024: import java.util.HashMap;
025: import java.util.Properties;
026:
027: import org.apache.commons.configuration.Configuration;
028: import org.apache.commons.io.filefilter.DirectoryFileFilter;
029: import org.apache.commons.lang.StringUtils;
030: import org.apache.commons.logging.Log;
031: import org.apache.commons.logging.LogFactory;
032: import org.apache.turbine.Turbine;
033: import org.apache.turbine.services.InitializationException;
034: import org.apache.turbine.services.TurbineBaseService;
035: import org.apache.turbine.services.pull.TurbinePull;
036: import org.apache.turbine.services.pull.tools.UITool;
037: import org.apache.turbine.services.servlet.TurbineServlet;
038: import org.apache.turbine.util.ServerData;
039: import org.apache.turbine.util.uri.DataURI;
040:
041: /**
042: * The UI service provides for shared access to User Interface (skin) files,
043: * as well as the ability for non-default skin files to inherit properties from
044: * a default skin. Use TurbineUI to access skin properties from your screen
045: * classes and action code. UITool is provided as a pull tool for accessing
046: * skin properties from your templates.
047: *
048: * @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a>
049: * @author <a href="mailto:james_coltman@majorband.co.uk">James Coltman</a>
050: * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
051: * @author <a href="mailto:seade@backstagetech.com.au">Scott Eade</a>
052: * @author <a href="thomas.vandahl@tewisoft.de">Thomas Vandahl</a>
053: * @version $Id$
054: * @see UIService
055: * @see UITool
056: */
057: public class TurbineUIService extends TurbineBaseService implements
058: UIService {
059: /** Logging. */
060: private static Log log = LogFactory.getLog(TurbineUIService.class);
061:
062: /**
063: * The location of the skins within the application resources directory.
064: */
065: private static final String SKINS_DIRECTORY = "/ui/skins";
066:
067: /**
068: * The name of the directory where images are stored for this skin.
069: */
070: private static final String IMAGES_DIRECTORY = "/images";
071:
072: /**
073: * Property tag for the default skin that is to be used for the web
074: * application.
075: */
076: private static final String SKIN_PROPERTY = "tool.ui.skin";
077:
078: /**
079: * Property tag for the image directory inside the skin that is to be used
080: * for the web application.
081: */
082: private static final String IMAGEDIR_PROPERTY = "tool.ui.dir.image";
083:
084: /**
085: * Property tag for the skin directory that is to be used for the web
086: * application.
087: */
088: private static final String SKINDIR_PROPERTY = "tool.ui.dir.skin";
089:
090: /**
091: * Property tag for the css file that is to be used for the web application.
092: */
093: private static final String CSS_PROPERTY = "tool.ui.css";
094:
095: /**
096: * Property tag for indicating if relative links are wanted for the web
097: * application.
098: */
099: private static final String RELATIVE_PROPERTY = "tool.ui.want.relative";
100:
101: /**
102: * Default skin name. This name refers to a directory in the
103: * WEBAPP/resources/ui/skins directory. There is a file called skin.props
104: * which contains the name/value pairs to be made available via the skin.
105: */
106: public static final String SKIN_PROPERTY_DEFAULT = "default";
107:
108: /**
109: * The skins directory, qualified by the resources directory (which is
110: * relative to the webapp context). This is used for constructing URIs and
111: * for retrieving skin files.
112: */
113: private String skinsDirectory;
114:
115: /**
116: * The file within the skin directory that contains the name/value pairs for
117: * the skin.
118: */
119: private static final String SKIN_PROPS_FILE = "skin.props";
120:
121: /**
122: * The file name for the skin style sheet.
123: */
124: private static final String DEFAULT_SKIN_CSS_FILE = "skin.css";
125:
126: /**
127: * The directory within the skin directory that contains the skin images.
128: */
129: private String imagesDirectory;
130:
131: /**
132: * The name of the css file within the skin directory.
133: */
134: private String cssFile;
135:
136: /**
137: * The flag that determines if the links that are returned are are absolute
138: * or relative.
139: */
140: private boolean wantRelative = false;
141:
142: /**
143: * The skin Properties store.
144: */
145: private HashMap skins = new HashMap();
146:
147: /**
148: * Refresh the service by clearing all skins.
149: */
150: public void refresh() {
151: clearSkins();
152: }
153:
154: /**
155: * Refresh a particular skin by clearing it.
156: *
157: * @param skinName the name of the skin to clear.
158: */
159: public void refresh(String skinName) {
160: clearSkin(skinName);
161: }
162:
163: /**
164: * Retrieve the Properties for a specific skin. If they are not yet loaded
165: * they will be. If the specified skin does not exist properties for the
166: * default skin configured for the webapp will be returned and an error
167: * level message will be written to the log. If the webapp skin does not
168: * exist the default skin will be used and id that doesn't exist an empty
169: * Properties will be returned.
170: *
171: * @param skinName the name of the skin whose properties are to be
172: * retrieved.
173: * @return the Properties for the named skin or the properties for the
174: * default skin configured for the webapp if the named skin does not exist.
175: */
176: private Properties getSkinProperties(String skinName) {
177: Properties skinProperties = (Properties) skins.get(skinName);
178: return null != skinProperties ? skinProperties
179: : loadSkin(skinName);
180: }
181:
182: /**
183: * Retrieve a skin property from the named skin. If the property is not
184: * defined in the named skin the value for the default skin will be
185: * provided. If the named skin does not exist then the skin configured for
186: * the webapp will be used. If the webapp skin does not exist the default
187: * skin will be used. If the default skin does not exist then
188: * <code>null</code> will be returned.
189: *
190: * @param skinName the name of the skin to retrieve the property from.
191: * @param key the key to retrieve from the skin.
192: * @return the value of the property for the named skin (defaulting to the
193: * default skin), the webapp skin, the default skin or <code>null</code>,
194: * depending on whether or not the property or skins exist.
195: */
196: public String get(String skinName, String key) {
197: Properties skinProperties = getSkinProperties(skinName);
198: return skinProperties.getProperty(key);
199: }
200:
201: /**
202: * Retrieve a skin property from the default skin for the webapp. If the
203: * property is not defined in the webapp skin the value for the default skin
204: * will be provided. If the webapp skin does not exist the default skin
205: * will be used. If the default skin does not exist then <code>null</code>
206: * will be returned.
207: *
208: * @param key the key to retrieve.
209: * @return the value of the property for the webapp skin (defaulting to the
210: * default skin), the default skin or <code>null</code>, depending on
211: * whether or not the property or skins exist.
212: */
213: public String get(String key) {
214: return get(getWebappSkinName(), key);
215: }
216:
217: /**
218: * Provide access to the list of available skin names.
219: *
220: * @return the available skin names.
221: */
222: public String[] getSkinNames() {
223: File skinsDir = new File(TurbineServlet
224: .getRealPath(skinsDirectory));
225: return skinsDir.list(DirectoryFileFilter.INSTANCE);
226: }
227:
228: /**
229: * Clear the map of stored skins.
230: */
231: private void clearSkins() {
232: synchronized (skins) {
233: skins = new HashMap();
234: }
235: log.debug("All skins were cleared.");
236: }
237:
238: /**
239: * Clear a particular skin from the map of stored skins.
240: *
241: * @param skinName the name of the skin to clear.
242: */
243: private void clearSkin(String skinName) {
244: synchronized (skins) {
245: if (!skinName.equals(SKIN_PROPERTY_DEFAULT)) {
246: skins.remove(SKIN_PROPERTY_DEFAULT);
247: }
248: skins.remove(skinName);
249: }
250: log.debug("The skin \"" + skinName
251: + "\" was cleared (will also clear \"default\" skin).");
252: }
253:
254: /**
255: * Load the specified skin.
256: *
257: * @param skinName the name of the skin to load.
258: * @return the Properties for the named skin if it exists, or the skin
259: * configured for the web application if it does not exist, or the default
260: * skin if that does not exist, or an empty Parameters object if even that
261: * cannot be found.
262: */
263: private synchronized Properties loadSkin(String skinName) {
264: Properties defaultSkinProperties = null;
265:
266: if (!StringUtils.equals(skinName, SKIN_PROPERTY_DEFAULT)) {
267: defaultSkinProperties = getSkinProperties(SKIN_PROPERTY_DEFAULT);
268: }
269:
270: // The following line is okay even for default.
271: Properties skinProperties = new Properties(
272: defaultSkinProperties);
273:
274: StringBuffer sb = new StringBuffer();
275: sb.append('/').append(skinsDirectory);
276: sb.append('/').append(skinName);
277: sb.append('/').append(SKIN_PROPS_FILE);
278: if (log.isDebugEnabled()) {
279: log.debug("Loading selected skin from: " + sb.toString());
280: }
281:
282: try {
283: // This will NPE if the directory associated with the skin does not
284: // exist, but it is habdled correctly below.
285: InputStream is = TurbineServlet.getResourceAsStream(sb
286: .toString());
287:
288: skinProperties.load(is);
289: } catch (Exception e) {
290: log.error("Cannot load skin: " + skinName + ", from: "
291: + sb.toString(), e);
292: if (!StringUtils.equals(skinName, getWebappSkinName())
293: && !StringUtils.equals(skinName,
294: SKIN_PROPERTY_DEFAULT)) {
295: log
296: .error("Attempting to return the skin configured for "
297: + "webapp instead of " + skinName);
298: return getSkinProperties(getWebappSkinName());
299: } else if (!StringUtils.equals(skinName,
300: SKIN_PROPERTY_DEFAULT)) {
301: log.error("Return the default skin instead of "
302: + skinName);
303: return skinProperties; // Already contains the default skin.
304: } else {
305: log
306: .error("No skins available - returning an empty Properties");
307: return new Properties();
308: }
309: }
310:
311: // Replace in skins HashMap
312: synchronized (skins) {
313: skins.put(skinName, skinProperties);
314: }
315:
316: return skinProperties;
317: }
318:
319: /**
320: * Get the name of the default skin name for the web application from the
321: * TurbineResources.properties file. If the property is not present the
322: * name of the default skin will be returned. Note that the web application
323: * skin name may be something other than default, in which case its
324: * properties will default to the skin with the name "default".
325: *
326: * @return the name of the default skin for the web application.
327: */
328: public String getWebappSkinName() {
329: return Turbine.getConfiguration().getString(SKIN_PROPERTY,
330: SKIN_PROPERTY_DEFAULT);
331: }
332:
333: /**
334: * Retrieve the URL for an image that is part of a skin. The images are
335: * stored in the WEBAPP/resources/ui/skins/[SKIN]/images directory.
336: *
337: * <p>Use this if for some reason your server name, server scheme, or server
338: * port change on a per request basis. I'm not sure if this would happen in
339: * a load balanced situation. I think in most cases the image(String image)
340: * method would probably be enough, but I'm not absolutely positive.
341: *
342: * @param skinName the name of the skin to retrieve the image from.
343: * @param imageId the id of the image whose URL will be generated.
344: * @param serverData the serverData to use as the basis for the URL.
345: */
346: public String image(String skinName, String imageId,
347: ServerData serverData) {
348: return getSkinResource(serverData, skinName, imagesDirectory,
349: imageId);
350: }
351:
352: /**
353: * Retrieve the URL for an image that is part of a skin. The images are
354: * stored in the WEBAPP/resources/ui/skins/[SKIN]/images directory.
355: *
356: * @param skinName the name of the skin to retrieve the image from.
357: * @param imageId the id of the image whose URL will be generated.
358: */
359: public String image(String skinName, String imageId) {
360: return image(skinName, imageId, Turbine.getDefaultServerData());
361: }
362:
363: /**
364: * Retrieve the URL for the style sheet that is part of a skin. The style is
365: * stored in the WEBAPP/resources/ui/skins/[SKIN] directory with the
366: * filename skin.css
367: *
368: * <p>Use this if for some reason your server name, server scheme, or server
369: * port change on a per request basis. I'm not sure if this would happen in
370: * a load balanced situation. I think in most cases the style() method would
371: * probably be enough, but I'm not absolutely positive.
372: *
373: * @param skinName the name of the skin to retrieve the style sheet from.
374: * @param serverData the serverData to use as the basis for the URL.
375: */
376: public String getStylecss(String skinName, ServerData serverData) {
377: return getSkinResource(serverData, skinName, null, cssFile);
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: * @param skinName the name of the skin to retrieve the style sheet from.
386: */
387: public String getStylecss(String skinName) {
388: return getStylecss(skinName, Turbine.getDefaultServerData());
389: }
390:
391: /**
392: * Retrieve the URL for a given script that is part of a skin. The script is
393: * stored in the WEBAPP/resources/ui/skins/[SKIN] directory.
394: *
395: * <p>Use this if for some reason your server name, server scheme, or server
396: * port change on a per request basis. I'm not sure if this would happen in
397: * a load balanced situation. I think in most cases the style() method would
398: * probably be enough, but I'm not absolutely positive.
399: *
400: * @param skinName the name of the skin to retrieve the image from.
401: * @param filename the name of the script file.
402: * @param serverData the serverData to use as the basis for the URL.
403: */
404: public String getScript(String skinName, String filename,
405: ServerData serverData) {
406: return getSkinResource(serverData, skinName, null, filename);
407: }
408:
409: /**
410: * Retrieve the URL for a given script that is part of a skin. The script is
411: * stored in the WEBAPP/resources/ui/skins/[SKIN] directory.
412: *
413: * @param skinName the name of the skin to retrieve the image from.
414: * @param filename the name of the script file.
415: */
416: public String getScript(String skinName, String filename) {
417: return getScript(skinName, filename, Turbine
418: .getDefaultServerData());
419: }
420:
421: private String stripSlashes(final String path) {
422: if (StringUtils.isEmpty(path)) {
423: return "";
424: }
425:
426: String ret = path;
427: int len = ret.length() - 1;
428:
429: if (ret.charAt(len) == '/') {
430: ret = ret.substring(0, len);
431: }
432:
433: if (len > 0 && ret.charAt(0) == '/') {
434: ret = ret.substring(1);
435: }
436:
437: return ret;
438: }
439:
440: /**
441: * Construct the URL to the skin resource.
442: *
443: * @param serverData the serverData to use as the basis for the URL.
444: * @param skinName the name of the skin.
445: * @param subDir the sub-directory in which the resource resides or
446: * <code>null</code> if it is in the root directory of the skin.
447: * @param resourceName the name of the resource to be retrieved.
448: * @return the path to the resource.
449: */
450: private String getSkinResource(ServerData serverData,
451: String skinName, String subDir, String resourceName) {
452: StringBuffer sb = new StringBuffer(skinsDirectory);
453: sb.append("/").append(skinName);
454: if (subDir != null) {
455: sb.append("/").append(subDir);
456: }
457: sb.append("/").append(stripSlashes(resourceName));
458:
459: DataURI du = new DataURI(serverData);
460: du.setScriptName(sb.toString());
461: return wantRelative ? du.getRelativeLink() : du
462: .getAbsoluteLink();
463: }
464:
465: // ---- Service initilization ------------------------------------------
466:
467: /**
468: * Initializes the service.
469: */
470: public void init() throws InitializationException {
471: Configuration cfg = Turbine.getConfiguration();
472:
473: // Get the resources directory that is specified in the TR.props or
474: // default to "resources", relative to the webapp.
475: StringBuffer sb = new StringBuffer();
476: sb.append(stripSlashes(TurbinePull.getResourcesDirectory()));
477: sb.append("/");
478: sb.append(stripSlashes(cfg.getString(SKINDIR_PROPERTY,
479: SKINS_DIRECTORY)));
480: skinsDirectory = sb.toString();
481:
482: imagesDirectory = stripSlashes(cfg.getString(IMAGEDIR_PROPERTY,
483: IMAGES_DIRECTORY));
484: cssFile = cfg.getString(CSS_PROPERTY, DEFAULT_SKIN_CSS_FILE);
485: wantRelative = cfg.getBoolean(RELATIVE_PROPERTY, false);
486:
487: setInit(true);
488: }
489:
490: /**
491: * Returns to uninitialized state.
492: */
493: public void shutdown() {
494: clearSkins();
495:
496: setInit(false);
497: }
498:
499: }
|