/*==========================================================================*\ | $Id: Theme.java,v 1.9 2012/03/28 13:48:08 stedwar2 Exp $ |*-------------------------------------------------------------------------*| | Copyright (C) 2008-2012 Virginia Tech | | This file is part of Web-CAT. | | Web-CAT is free software; you can redistribute it and/or modify | it under the terms of the GNU Affero General Public License as published | by the Free Software Foundation; either version 3 of the License, or | (at your option) any later version. | | Web-CAT is distributed in the hope that it will be useful, | but WITHOUT ANY WARRANTY; without even the implied warranty of | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | GNU General Public License for more details. | | You should have received a copy of the GNU Affero General Public License | along with Web-CAT; if not, see <http://www.gnu.org/licenses/>. \*==========================================================================*/ package org.webcat.core; import java.io.File; import org.webcat.core.messaging.UnexpectedExceptionMessage; import org.webcat.woextensions.ECAction; import static org.webcat.woextensions.ECAction.run; import org.webcat.woextensions.WCResourceManager; import com.webobjects.appserver.WOContext; import com.webobjects.appserver.WOCookie; import com.webobjects.eocontrol.EOSharedEditingContext; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSBundle; import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSKeyValueCodingAdditions; import com.webobjects.foundation.NSTimestamp; import er.extensions.foundation.ERXValueUtilities; // ------------------------------------------------------------------------- /** * Represents a theme (stored in the Core framework). * * @author Stephen Edwards * @author Last changed by $Author: stedwar2 $ * @version $Revision: 1.9 $, $Date: 2012/03/28 13:48:08 $ */ public class Theme extends _Theme { //~ Constructors .......................................................... // ---------------------------------------------------------- /** * Creates a new Theme object. */ public Theme() { super(); } // ---------------------------------------------------------- /** * Look up and return a theme object by its directory name (short * symbolic name, not its human-readable name). * * @param themeDirName the subdirectory name of the theme * @return The matching theme object */ public static Theme themeFromName(String themeDirName) { ensureThemesLoaded(); return themeForDirName( EOSharedEditingContext.defaultSharedEditingContext(), themeDirName); } // ---------------------------------------------------------- /** * Returns the last used theme that was stored in the browser's cookies. * * @param context the context from which to retrieve the cookie * @return The last-used theme stored in the cookie */ public static Theme lastUsedThemeInContext(WOContext context) { String lastUsedTheme = context.request().cookieValueForKey( COOKIE_LAST_USED_THEME); if (lastUsedTheme != null) { return themeFromName(lastUsedTheme); } else { return themeFromName("dream-way"); } } // ---------------------------------------------------------- /** * Stores this theme as the last used theme in the browser's cookies. This * is used on pages such as the login page when a user session does not yet * exist, but we would still like to present them with the theme they used * last (on the same browser/client). * * @param context the context in which to store the cookie */ public void setAsLastUsedThemeInContext(WOContext context) { String path = context.urlWithRequestHandlerKey(null, null, null); WOCookie cookie = new WOCookie(COOKIE_LAST_USED_THEME, dirName(), path, null, ONE_YEAR, false); context.response().addCookie(cookie); } // ---------------------------------------------------------- /** * Return the default theme object to use when users have not * chosen one of their own. * * @return The default theme */ public static Theme defaultTheme() { return themeFromName("dream-way"); } // ---------------------------------------------------------- /** * Get a list of shared theme objects that have already been loaded * into the shared editing context. * @return an array of all theme objects */ public static NSArray<Theme> themes() { ensureThemesLoaded(); return themes; } //~ Methods ............................................................... // ---------------------------------------------------------- /** * Get a human-readable representation of this theme, which is * the same as {@link #name()}. * @return this theme's name */ public String userPresentableDescription() { return name(); } // ---------------------------------------------------------- public Theme parent() { if (baseIsNotSet) { String baseName = properties() != null ? (String)properties().valueForKey("extends") : null; if (baseName != null) { base = themeFromName(baseName); baseIsNotSet = false; } } return base; } // ---------------------------------------------------------- /** * Provided for OGNL compatibility of the pseudo-key .inherit. * WO will correctly use valueForKey()/valueForKeyPath(), but * OGNL won't, so we need this stub for evaluating OGNL expressions * using the .inherit key. */ public Object inherit() { return valueForKey(INHERIT_KEY); } // ---------------------------------------------------------- public Object valueForKey(String key) { if (INHERIT_KEY.equals(key)) { if (inheriter == null) { inheriter = new PropertyInheriter(); } return inheriter; } else if (key.startsWith(INHERIT_PREFIX)) { key = key.substring(INHERIT_PREFIX_LEN); if (inheriter == null) { inheriter = new PropertyInheriter(); } return inheriter.valueForKey(key); } else { return super.valueForKey(key); } } // ---------------------------------------------------------- public void takeValueForKey(Object value, String key) { if (key.equals(INHERIT_KEY)) { throw new IllegalArgumentException("cannot set the .inherit key"); } else if (key.startsWith(INHERIT_PREFIX)) { key = key.substring(INHERIT_PREFIX_LEN); } super.takeValueForKey(value, key); } // ---------------------------------------------------------- public Object valueForKeyPath(String keyPath) { if (keyPath.startsWith(INHERIT_PREFIX)) { keyPath = keyPath.substring(INHERIT_PREFIX_LEN); if (inheriter == null) { inheriter = new PropertyInheriter(); } return inheriter.valueForKeyPath(keyPath); } else { return super.valueForKeyPath(keyPath); } } // ---------------------------------------------------------- public void takeValueForKeyPath(Object value, String keyPath) { if (keyPath.startsWith(INHERIT_PREFIX)) { keyPath = keyPath.substring(INHERIT_PREFIX_LEN); } super.takeValueForKey(value, keyPath); } // ---------------------------------------------------------- public boolean isDark() { Object result = valueForKeyPath(INHERIT_PREFIX + "properties.isDark"); return (result == null) ? false : Boolean.valueOf(result.toString()); } // ---------------------------------------------------------- public String dojoTheme() { Object result = valueForKeyPath(INHERIT_PREFIX + "properties.dojoTheme"); return (result == null) ? "nihilo" : result.toString(); } // ---------------------------------------------------------- public String linkTags() { if (linkTags == null) { linkTags = (parent() == null) ? "" : parent().linkTags(); if (properties() != null) { try { Object cssFileList = properties().valueForKey("cssOrder"); if (cssFileList != null && cssFileList instanceof NSArray) { @SuppressWarnings("unchecked") NSArray<NSDictionary<String, String>> cssFiles = (NSArray<NSDictionary<String, String>>)cssFileList; String baseLocation = "Core.framework/WebServerResources/theme/" + dirName() + "/"; for (NSDictionary<String, String> css : cssFiles) { linkTags += "<link rel=\"stylesheet\" " + "type=\"text/css\" href=\"" + WCResourceManager.resourceURLFor( baseLocation + css.get("file"), null) + "\""; String media = css.get("media"); if (media != null) { linkTags += " media=\"" + media + "\""; } linkTags += " />"; } } } catch (Exception e) { new UnexpectedExceptionMessage(e, null, null, "Unexpected exception trying to decode theme " + "properties for theme: " + dirName() + "(" + id() + ").") .send(); } } } return linkTags; } // ---------------------------------------------------------- public void refresh() { File plist = new File(themeBaseDir(), dirName()); if (plist.exists()) { refreshFrom(plist); } else { log.error("Unable to refresh theme " + this + ": file " + "not found: " + plist); } } // ---------------------------------------------------------- public static void refreshThemes() { log.debug("refreshThemes()"); if (!themeBaseDir().exists()) return; run(new ECAction() { public void action() { ec.setSharedEditingContext(null); for (File subdir : themeBaseDir().listFiles()) { if (subdir.isDirectory()) { File plist = new File(subdir, "theme.plist"); if (plist.exists()) { Theme themeToUpdate = themeForDirName(ec, subdir.getName()); if (themeToUpdate != null) { // Theme already exists, so check to see if // it needs to be updated NSTimestamp modTime = new NSTimestamp( plist.lastModified()); if (themeToUpdate.lastUpdate() != null && themeToUpdate.lastUpdate().after(modTime)) { // No update needed log.debug("theme " + themeToUpdate.dirName() + " is up to date"); themeToUpdate = null; } } else { // Create it log.info("Registering new theme: " + subdir.getName()); themeToUpdate = create(ec, subdir.getName(), false, false); } if (themeToUpdate != null) { themeToUpdate.refreshFrom(plist); ec.saveChanges(); } } } } }}); log.debug( "refreshing shared theme objects" ); themes = allObjectsOrderedByName( EOSharedEditingContext.defaultSharedEditingContext()); if (log.isDebugEnabled()) { log.debug("Registered themes = " + themes); } } // ---------------------------------------------------------- public void setUpdateMutableFields(boolean value) { // Silently swallow this operation, since Themes are held in // the shared editing context and should not be modified, except // under very controlled conditions. } //~ Private Methods ....................................................... // ---------------------------------------------------------- private void refreshFrom(File plist) { NSTimestamp now = new NSTimestamp(); try { log.info("reloading theme settings from: " + plist.getCanonicalPath()); MutableDictionary dict = MutableDictionary.fromPropertyList(plist); setProperties(dict); String themeName = (String)dict.objectForKey("name"); setName(themeName); setLastUpdate(now); setIsForThemeDevelopers(ERXValueUtilities.booleanValue( dict.valueForKey("isForThemeDevelopers"))); } catch (Exception e) { log.error("Unable to refresh theme from " + plist, e); new UnexpectedExceptionMessage(e, null, null, "Error refreshing theme.").send(); } } // ---------------------------------------------------------- private static void ensureThemesLoaded() { if (themes == null) { refreshThemes(); } } // ---------------------------------------------------------- @SuppressWarnings("deprecation") private static File themeBaseDir() { if (themeBaseDir == null) { // We *cannot* use the subsystem itself to find this // information, since this method is called before the // subsystem manager has initialized the subsystems! themeBaseDir = new File( NSBundle.bundleForName("Core").bundlePath(), "WebServerResources/theme"); } return themeBaseDir; } // ---------------------------------------------------------- private class PropertyInheriter implements NSKeyValueCodingAdditions { // ---------------------------------------------------------- public void takeValueForKeyPath(Object value, String keyPath) { Theme.this.takeValueForKeyPath(value, keyPath); } // ---------------------------------------------------------- public Object valueForKeyPath(String keyPath) { Object result = Theme.this.valueForKeyPath(keyPath); if (result == null && parent() != null) { result = parent().valueForKeyPath(INHERIT_PREFIX + keyPath); } return result; } // ---------------------------------------------------------- public void takeValueForKey(Object value, String key) { Theme.this.takeStoredValueForKey(value, key); } // ---------------------------------------------------------- public Object valueForKey(String key) { Object result = Theme.this.valueForKey(key); if (result == null && parent() != null) { result = parent().valueForKey(INHERIT_PREFIX + key); } return result; } } //~ Instance/static variables ............................................. private String linkTags; private Theme base; private boolean baseIsNotSet = true; private PropertyInheriter inheriter; private static NSArray<Theme> themes; private static File themeBaseDir; private static final String INHERIT_KEY = "inherit"; private static final String INHERIT_PREFIX = INHERIT_KEY + "."; private static final int INHERIT_PREFIX_LEN = INHERIT_PREFIX.length(); // One year, in seconds private static final int ONE_YEAR = 60 * 60 * 24 * 365; private static final String COOKIE_LAST_USED_THEME = "org.webcat.core.Theme.lastUsed"; }