001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package com.sun.rave.web.ui.theme;
042:
043: import java.net.MalformedURLException;
044: import java.net.URL;
045: import java.text.MessageFormat;
046: import java.util.ArrayList;
047: import java.util.Locale;
048: import java.util.MissingResourceException;
049: import java.util.ResourceBundle;
050: import java.util.StringTokenizer;
051:
052: import javax.faces.context.FacesContext;
053: import javax.servlet.ServletContext;
054: import javax.servlet.ServletRequest;
055: import javax.servlet.http.HttpServletRequest; //import javax.portlet.PortletRequest;
056:
057: import com.sun.rave.web.ui.component.Icon;
058: import com.sun.rave.web.ui.util.ClassLoaderFinder;
059: import com.sun.rave.web.ui.util.ClientSniffer;
060: import com.sun.rave.web.ui.util.ClientType;
061: import com.sun.rave.web.ui.util.MessageUtil;
062: import java.beans.Beans;
063:
064: /**
065: * <p>The Theme class is responsible for managing application
066: * resources such as style sheets, JavaScript files and message
067: * files.</p>
068: * <p>Theme resources are delived in the form of Jar files which are
069: * placed in the classpath of the application or of the Servlet
070: * container. Themes must include a file called
071: * <code>META-INF/swc_theme.properties</code> which describe the
072: * resources available to the theme.</p>
073: * <p>To configure the default Theme to be used by an application,
074: * ensure that the theme jar is the application's classpath and
075: * configure the application's deployment descriptor to set the
076: * context parameter <code>com.sun.rave.web.ui.DEFAULT_THEME</code> to the
077: * Theme's name. </code>
078: * <p>If you need to add additional locale support, you have two
079: * options:</p>
080: * <ul>
081: * <li>If your application only uses a single Theme, you can find out
082: * what the message file for the Theme is called and provide a message
083: * file for your locale using the same namebase in the application's
084: * classpath. The usual fallback mechanism will apply. </li>
085: * <li> If your application uses multiple Themes, you can specify an
086: * alternative message file to be used by all Themes using the context
087: * parameter <code>com.sun.rave.web.ui.MESSAGES</code>. The themes will
088: * attempt to retrieve messages from the bundle(s) of this basename
089: * first. If the message key does not resolve, the Theme's default
090: * bundles are used instead. </li>
091: */
092:
093: public class Theme {
094:
095: private ResourceBundle bundle = null;
096: private ResourceBundle fallbackBundle = null;
097: private ResourceBundle classMapper = null;
098: private ResourceBundle imageResources = null;
099: private ResourceBundle jsFiles = null;
100: private ResourceBundle stylesheets = null;
101: private String[] globalJSFiles = null;
102: private String[] globalStylesheets = null;
103:
104: /**
105: * Attribute name used to store the user's theme name in the Session
106: */
107: public static final String THEME_ATTR = "com.sun.rave.web.ui.Theme";
108: /** The context parameter name used to specify a console path, if one is used. */
109: public static final String RESOURCE_PATH_ATTR = "com.sun.web.console.resource_path";
110:
111: private static final String HEIGHT_SUFFIX = "_HEIGHT";
112: private static final String WIDTH_SUFFIX = "_WIDTH";
113: private static final String GLOBAL_JSFILES = ThemeJavascript.GLOBAL;
114: private static final String GLOBAL_STYLESHEETS = ThemeStyles.GLOBAL;
115: private static final String MASTER_STYLESHEET = ThemeStyles.MASTER;
116: private String prefix = null;
117: private String path = null;
118: private Locale locale = null;
119: private boolean realServer = true;
120:
121: private static final boolean DEBUG = false;
122:
123: public Theme(Locale locale) {
124: realServer = !java.beans.Beans.isDesignTime();
125: this .locale = locale;
126: }
127:
128: /**
129: * Use this method to retrieve a String array of URIs
130: * to the JavaScript files that should be included
131: * with all pages of this application
132: * @return String array of URIs to the JavaScript files
133: */
134: public String[] getGlobalJSFiles() {
135:
136: if (DEBUG)
137: log("getGlobalJSFiles()");
138:
139: if (globalJSFiles == null) {
140:
141: try {
142:
143: String files = jsFiles.getString(GLOBAL_JSFILES);
144: StringTokenizer tokenizer = new StringTokenizer(files,
145: " ");
146: String pathKey = null;
147: String path = null;
148:
149: ArrayList fileNames = new ArrayList();
150:
151: while (tokenizer.hasMoreTokens()) {
152: pathKey = tokenizer.nextToken();
153: path = jsFiles.getString(pathKey);
154: fileNames.add(translateURI(path));
155: }
156: int numFiles = fileNames.size();
157: globalJSFiles = new String[numFiles];
158: for (int i = 0; i < numFiles; ++i) {
159: globalJSFiles[i] = fileNames.get(i).toString();
160: }
161: } catch (MissingResourceException npe) {
162: // Do nothing - there are no global javascript files
163: globalJSFiles = new String[0];
164: }
165: }
166: return globalJSFiles;
167: }
168:
169: /**
170: * Use this method to retrieve a String array of URIs
171: * to the CSS stylesheets files that should be included
172: * with all pages of this application
173: * @return String array of URIs to the stylesheets
174: */
175: public String[] getGlobalStylesheets() {
176: if (globalStylesheets == null) {
177:
178: try {
179: String files = stylesheets
180: .getString(GLOBAL_STYLESHEETS);
181: StringTokenizer tokenizer = new StringTokenizer(files,
182: " ");
183: String pathKey = null;
184: String path = null;
185: ArrayList fileNames = new ArrayList();
186:
187: while (tokenizer.hasMoreTokens()) {
188:
189: pathKey = tokenizer.nextToken();
190: path = stylesheets.getString(pathKey);
191: fileNames.add(translateURI(path));
192: }
193: int numFiles = fileNames.size();
194: globalStylesheets = new String[numFiles];
195: for (int i = 0; i < numFiles; ++i) {
196: globalStylesheets[i] = fileNames.get(i).toString();
197: }
198:
199: } catch (MissingResourceException npe) {
200: // There was no "global" key
201: // Do nothing
202: globalStylesheets = new String[0];
203: }
204: }
205: return globalStylesheets;
206: }
207:
208: /**
209: * Returns a String that represents a valid path to the JavaScript
210: * file corresponding to the key
211: * @return Returns a String that represents a valid path to the JavaScript
212: * file corresponding to the key
213: * @param key Key to retrieve the javascript file
214: */
215: public String getPathToJSFile(String key) {
216: if (DEBUG)
217: log("getPathToJSFile()");
218: String path = jsFiles.getString(key);
219: if (DEBUG)
220: log("path is " + translateURI(path));
221: return translateURI(path);
222: }
223:
224: /**
225: * Returns a String that represents a valid path to the CSS stylesheet
226: * corresponding to the key
227: * @param context FacesContext of the request
228: * @return A String that represents a valid path to the CSS stylesheet
229: * corresponding to the key
230: */
231: public String getPathToStylesheet(FacesContext context) {
232:
233: if (DEBUG)
234: log("getPathToStyleSheet()");
235:
236: ClientType clientType = ClientSniffer.getClientType(context);
237: if (DEBUG)
238: log("Client type is " + clientType.toString());
239: try {
240: String path = stylesheets.getString(clientType.toString());
241: if (DEBUG) {
242: log(path);
243: log(translateURI(path));
244: }
245: if (path == null || path.length() == 0) {
246: return null;
247: } else {
248: return translateURI(path);
249: }
250: } catch (MissingResourceException mre) {
251: StringBuffer msgBuffer = new StringBuffer(
252: "Could not find propery ");
253: msgBuffer.append(clientType.toString());
254: msgBuffer.append(" in ResourceBundle ");
255: msgBuffer.append(stylesheets.toString());
256: throw new RuntimeException(msgBuffer.toString());
257: }
258: }
259:
260: /**
261: * Returns a String that represents a valid path to the CSS stylesheet
262: * corresponding to the key
263: * @return A String that represents a valid path to the CSS stylesheet
264: * corresponding to the key
265: */
266: public String getPathToMasterStylesheet() {
267:
268: try {
269: String path = stylesheets.getString(MASTER_STYLESHEET);
270: if (path == null || path.length() == 0) {
271: return null;
272: } else {
273: return translateURI(path);
274: }
275: } catch (MissingResourceException mre) {
276: StringBuffer msgBuffer = new StringBuffer(
277: "Could not find master ");
278: msgBuffer.append("stylesheet in ResourceBundle ");
279: msgBuffer.append(stylesheets.toString());
280: throw new RuntimeException(msgBuffer.toString());
281: }
282: }
283:
284: /**
285: * Returns a String that represents a valid path to the CSS stylesheet
286: * corresponding to the key
287: * @return A String that represents a valid path to the CSS stylesheet
288: * corresponding to the key
289: */
290: public String getPathToStylesheet(String clientName) {
291:
292: if (DEBUG)
293: log("getPathToStyleSheet()");
294:
295: try {
296: String path = stylesheets.getString(clientName);
297: if (path == null || path.length() == 0) {
298: return null;
299: } else {
300: return translateURI(path);
301: }
302: } catch (MissingResourceException mre) {
303: StringBuffer msgBuffer = new StringBuffer(
304: "Could not find propery ");
305: msgBuffer.append(clientName);
306: msgBuffer.append(" in ResourceBundle ");
307: msgBuffer.append(stylesheets.toString());
308: throw new RuntimeException(msgBuffer.toString());
309: }
310: }
311:
312: /**
313: * Returns the name of a CSS style. If the Theme includes a class
314: * mapper, the method checks it for the presence of a mapping for
315: * the CSS class name passed in with the argument. If there
316: * is no mapping, the name is used as is.
317: *
318: * up in the class mapper if there is one, a valid path to the CSS stylesheet
319: * corresponding to the key
320: * @param name The style class name to be used
321: * @return the name of a CSS style.
322: */
323: public String getStyleClass(String name) {
324: if (classMapper == null) {
325: return name;
326: }
327: String styleClass = classMapper.getString(name);
328: return (styleClass == null) ? name : styleClass;
329: }
330:
331: /**
332: * Retrieves a message from the appropriate ResourceBundle.
333: * If the web application specifies a bundle that overrides
334: * the standard bundle, that one is tried first. If no override
335: * bundle is specified, or if the bundle does not contain the
336: * key, the key is resolved from the Theme's default bundle.
337: * @param key The key used to retrieve the message
338: * @return A localized message string
339: */
340: public String getMessage(String key) {
341: String message = null;
342: try {
343: message = bundle.getString(key);
344: } catch (MissingResourceException mre) {
345: try {
346: message = fallbackBundle.getString(key);
347: } catch (NullPointerException npe) {
348: throw mre;
349: }
350: }
351: return message;
352: }
353:
354: /**
355: * Retrieves a message from the appropriate ResourceBundle.
356: * If the web application specifies a bundle that overrides
357: * the standard bundle, that one is tried first. If no override
358: * bundle is specified, or if the bundle does not contain the
359: * key, the key is resolved from the Theme's default bundle.
360: * @param key The key used to retrieve the message
361: * @param params An object array specifying the parameters of
362: * the message
363: * @return A localized message string
364: */
365: public String getMessage(String key, Object[] params) {
366: String message = getMessage(key);
367: MessageFormat mf = new MessageFormat(message, locale);
368: return mf.format(params);
369: }
370:
371: // Sets the prefix to be unconditionally prepended for any URI given out
372: // by theme.
373: /**
374: * Sets the prefix to be prepended to the path names of the resources
375: * @param p prefix for all URIs in the theme
376: */
377: protected void setPrefix(String p) {
378: prefix = p;
379: }
380:
381: /**
382: * Configures a resource bundle which overrides the standard keys for
383: * retrieving style class names.
384: * @param classMapper A ResourceBundle that overrides the standard style
385: * class keys
386: */
387: protected void configureClassMapper(ResourceBundle classMapper) {
388: this .classMapper = classMapper;
389: }
390:
391: /**
392: * <p>Configures the message bundles. All Themes must contain a default
393: * ResourceBundle for messages, which is configured in the Theme
394: * configuration file. This bundle is passed in as the first parameter
395: * (base).</p>
396: * <p>Optionally, the web application developer can override
397: * the messages from all themes by specifying a resource bundle
398: * in a context init parameter (if they haven't done so, the second
399: * parameter will be null). If the second parameter is non-null,
400: * Theme.getMessage tries to get the message from the override bundle first.
401: * If that fails (or if there is no override bundle), getMessage() tries
402: * the base bundle. </p>
403: * @param base The message bundle specified by the Theme
404: * configuration file.
405: * @param override A message bundle configured by the user
406: * in a context parameter, to override messages from the base bundle.
407: */
408: protected void configureMessages(ResourceBundle base,
409: ResourceBundle override) {
410: if (DEBUG)
411: log("configureMessages()");
412: if (override == null) {
413: if (DEBUG)
414: log("override is null, bundle is "
415: + override.toString());
416: bundle = base;
417: } else {
418: bundle = override;
419: fallbackBundle = base;
420: }
421: }
422:
423: /**
424: * <p>Configures the image resource bundle.</p>
425: *
426: * @param imageResources A ResourceBundle whose keys specify
427: * the available images.
428: */
429: protected void configureImages(ResourceBundle imageResources) {
430: this .imageResources = imageResources;
431: }
432:
433: /**
434: * <p>Configures the JS resource files.</p>
435: *
436: * @param jsFiles A ResourceBundle whose keys specify the available
437: * JavaScript files
438: */
439: protected void configureJSFiles(ResourceBundle jsFiles) {
440: this .jsFiles = jsFiles;
441: }
442:
443: /**
444: * <p>Configures the stylesheets.</p>
445: *
446: * @param stylesheets A resource bundle specifying the stylesheet for
447: * each @link ClientType
448: */
449: protected void configureStylesheets(ResourceBundle stylesheets) {
450: this .stylesheets = stylesheets;
451: }
452:
453: /**
454: * <p>This method needs to be refactored. The information about what
455: * type of path to generate is available when the Theme is configured,
456: * and it does not vary from request to request. So it should be
457: * fixed on startup. </p>
458: * @param context FacesContext of the calling application
459: * @param uri URI to be translated
460: * @return translated URI String
461: */
462: private String translateURI(String uri) {
463: if (uri == null || uri.length() == 0) {
464: return null;
465: }
466:
467: if (DEBUG)
468: log("translateURI(). URI is " + uri);
469: if (path == null) {
470: initializePath();
471: }
472: if (realServer) {
473: if (DEBUG)
474: log("\tPath is " + path.concat(uri));
475: return path.concat(uri);
476: }
477: if (DEBUG)
478: log("Generating a URL for design view");
479: ClassLoader loader = ClassLoaderFinder
480: .getCurrentLoader(Theme.class);
481:
482: if (Beans.isDesignTime()) {
483: // NB6 gives warnings if the path has a leading "/". So, strip it off if it has one
484: uri = uri.startsWith("/") ? uri.substring(1) : uri;
485: }
486:
487: URL url = loader.getResource(uri);
488: if (DEBUG)
489: log("URL is " + url);
490: return url.toString();
491: }
492:
493: private void initializePath() {
494:
495: if (DEBUG)
496: log("initializePath()");
497: FacesContext context = FacesContext.getCurrentInstance();
498: Object consolePath = context.getExternalContext()
499: .getApplicationMap().get(Theme.RESOURCE_PATH_ATTR);
500: if (consolePath == null) {
501: if (DEBUG)
502: log("\tNo console path, use path prefix");
503: path = context.getApplication().getViewHandler()
504: .getResourceURL(context, prefix);
505: if (DEBUG)
506: log("Path is " + path);
507: return;
508:
509: }
510: if (DEBUG)
511: log("\tFound console path..." + consolePath.toString());
512: Object request = context.getExternalContext().getRequest();
513:
514: String protocol = null;
515: String server = null;
516: int port;
517:
518: if (request instanceof ServletRequest) {
519: ServletRequest sr = (ServletRequest) request;
520: protocol = sr.getScheme();
521: server = sr.getServerName();
522: port = sr.getServerPort();
523: }
524:
525: // else if(request instanceof PortletRequest) {
526: // PortletRequest pr = (PortletRequest)request;
527: // protocol = pr.getScheme();
528: // server = pr.getServerName();
529: // port = pr.getServerPort();
530: // }
531: else {
532: String message = "REquest opbject is "
533: + request.getClass().getName();
534: throw new RuntimeException(message);
535: }
536: URL url = null;
537: try {
538: if (DEBUG) {
539: log("protocol: " + protocol);
540: log("server: " + server);
541: log("port " + String.valueOf(port));
542: log(" consolepath "
543: + consolePath.toString().concat(prefix));
544: }
545: url = new URL(protocol, server, port, consolePath
546: .toString().concat(prefix));
547: } catch (MalformedURLException mue) {
548: throw new ThemeConfigurationException(
549: "Couldn't figure out resource path");
550: }
551:
552: path = url.toString();
553: if (DEBUG)
554: log("\tPath is " + path);
555: }
556:
557: public void initializePath(ServletContext context,
558: HttpServletRequest request) {
559:
560: if (DEBUG)
561: log("initializePath(ServletContext)");
562:
563: if (path != null) {
564: return;
565: }
566:
567: String pathString = null;
568: Object consolePath = context
569: .getAttribute(Theme.RESOURCE_PATH_ATTR);
570: if (consolePath == null) {
571: if (DEBUG)
572: log("\tNo console path attribute! Set to "
573: + String.valueOf(consolePath));
574: pathString = request.getContextPath();
575: } else {
576: if (DEBUG)
577: log("\tFound console path..." + consolePath.toString());
578: pathString = consolePath.toString();
579: if (pathString.length() > 0 && !pathString.startsWith("/"))
580: pathString = "/".concat(path);
581: }
582: path = pathString.concat(prefix);
583: }
584:
585: private void log(String s) {
586: System.out.println(getClass().getName() + "::" + s); //NOI18N
587: }
588:
589: public Icon getIcon(String identifier) {
590:
591: Icon icon = new Icon();
592: icon.setIcon(identifier);
593: if (identifier != null) {
594:
595: //make sure to setIcon on parent and not the icon itself (which
596: //now does the theme stuff in the component
597:
598: String path = null;
599: try {
600: path = imageResources.getString(identifier);
601: } catch (MissingResourceException mre) {
602: Object[] params = { identifier };
603: String message = MessageUtil.getMessage(
604: "com.sun.rave.web.ui.resources.LogMessages",
605: "Theme.noIcon", params);
606: throw new RuntimeException(message, mre);
607: }
608:
609: path = translateURI(path);
610: icon.setUrl(path);
611: try {
612: String height = imageResources.getString(identifier
613: .concat(HEIGHT_SUFFIX));
614: int ht = Integer.parseInt(height);
615: icon.setHeight(ht);
616: } catch (Exception ex) {
617: // Don't do anything...
618: }
619:
620: try {
621: String width = imageResources.getString(identifier
622: .concat(WIDTH_SUFFIX));
623: int wt = Integer.parseInt(width);
624: icon.setWidth(wt);
625: } catch (Exception ex) {
626: // Don't do anything...
627: }
628: }
629: return icon;
630: }
631: }
|