/* Wotonomy: OpenStep design patterns for pure Java applications. Copyright (C) 2000 Blacksmith, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see http://www.gnu.org */ package net.wotonomy.web; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.Map; import net.wotonomy.foundation.NSArray; import net.wotonomy.foundation.NSData; import net.wotonomy.foundation.NSKeyValueCoding; import net.wotonomy.foundation.NSMutableDictionary; import net.wotonomy.foundation.internal.PropertyListParser; /** * Manages all resources vended by the application. * * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 905 $ */ public class WOResourceManager { private NSMutableDictionary resourceCache; private NSMutableDictionary dynamicDataCache; private NSMutableDictionary stringTableCache; private Map localeCache; // used for wo-style i18n private Locale californiaLocale; // used for wo-style i18n /** * Constructor is only accessible to subclasses. */ protected WOResourceManager() { resourceCache = new NSMutableDictionary(); dynamicDataCache = new NSMutableDictionary(); stringTableCache = new NSMutableDictionary(); localeCache = new HashMap(); californiaLocale = new Locale("en", "US"); localeCache.put("en", californiaLocale); } /** * Returns the raw data corresponding to the specified resource. Any data * retrieved by this method will be placed in the resource manager's global * cache. */ public byte[] bytesForResourceNamed(String aFileName, String aFrameworkName, NSArray aLanguagesList) { String mash = aFileName + aFrameworkName; if (aLanguagesList != null) { mash = mash + aLanguagesList.componentsJoinedByString(":"); } byte[] result = (byte[]) resourceCache.objectForKey(mash); if (result == null) { InputStream input = inputStreamForResourceNamed(aFileName, aFrameworkName, aLanguagesList); if (input != null) { try { int c; ByteArrayOutputStream output = new ByteArrayOutputStream(); while ((c = input.read()) != -1) { output.write(c); } output.flush(); input.close(); output.close(); result = output.toByteArray(); synchronized (resourceCache) { resourceCache.setObjectForKey(result, mash); } } catch (Throwable t) { System.err.println("WOResourceManager: Error reading bytes: " + aFileName); t.printStackTrace(); } } } return result; } /** * Returns the content type corresponding to the specified resource. This * implementation recognizes gif, jpg, png, html, and xml extensions. Otherwise, * "text/plain" is returned. */ public String contentTypeForResourceNamed(String aResourcePath) { if (aResourcePath.endsWith(".gif")) return "image/gif"; if (aResourcePath.endsWith(".jpg")) return "image/jpeg"; if (aResourcePath.endsWith(".png")) return "image/png"; if (aResourcePath.endsWith(".html")) return "text/html"; if (aResourcePath.endsWith(".xml")) return "text/xml"; return "text/plain"; } /** * Returns a url to be used when errors occur while retrieving a resource. */ public String errorMessageUrlForResourceNamed(String aResourceName, String aFrameworkName) { if (aResourceName == null) aResourceName = "null"; if (aFrameworkName == null) { return "/ERROR/NOT_FOUND/app=" + WOApplication.application().name() + "/filename=" + aResourceName; } else { return "/ERROR/NOT_FOUND/framework=" + aFrameworkName + "/filename=" + aResourceName; } } /** * Clears all cached system-wide resource data. */ public void flushDataCache() { synchronized (resourceCache) { resourceCache.removeAllObjects(); } synchronized (dynamicDataCache) { dynamicDataCache.removeAllObjects(); } synchronized (stringTableCache) { stringTableCache.removeAllObjects(); } } /** * Returns the file-system path for the specified resource. Deprecated and not * implemented. * * @deprecated Use inputStreamForResourceNamed instead. */ public String pathForResourceNamed(String aResourceName, String aFrameworkName, NSArray aLanguagesList) { throw new RuntimeException("ResourceManager.pathForResourceNamed: deprecated"); } /** * Removes the data from the dynamic data cache for the specified session. If * aSession is null, the data is removed from the application-wide data cache. */ public void removeDataForKey(String aKey, WOSession aSession) { if (aSession != null) { if (aSession.dynamicDataCache != null) { aSession.dynamicDataCache.removeObjectForKey(aKey); } } else { synchronized (dynamicDataCache) { dynamicDataCache.removeObjectForKey(aKey); } } } /** * Sets the data in the dynamic data cache for the specified session. If * aSession is null, the data is placed in the application-wide data cache. If * the key is a system-generated key, the data will be removed by calling * removeData() the next time it is requested. */ public void setData(NSData someData, String key, String type, WOSession aSession) { if (aSession != null) { if (aSession.dynamicDataCache != null) { aSession.dynamicDataCache.setObjectForKey(new TypedData(type, someData), key); } } else { synchronized (dynamicDataCache) { dynamicDataCache.setObjectForKey(new TypedData(type, someData), key); } } } /** * Returns a localized string from a property list for a given key. If the key * doesn't exist, aDefaultValue is returned. */ public String stringForKey(String aKey, String aFileName, String aDefaultValue, String aFrameworkName, NSArray aLanguagesList) { String mash = aFileName + aFrameworkName; if (aLanguagesList != null) { mash = mash + aLanguagesList.componentsJoinedByString(":"); } Object table = stringTableCache.objectForKey(mash); if (table == null) { try { InputStream input = (InputStream) inputStreamForResourceNamed(aFileName, aFrameworkName, aLanguagesList); if (input != null) { Reader reader = new BufferedReader(new InputStreamReader(input)); table = PropertyListParser.propertyListFromReader(reader); synchronized (stringTableCache) { stringTableCache.setObjectForKey(table, mash); } } } catch (IOException ioe) { System.err.println("WOResourceManager: error reading: " + aFileName); ioe.printStackTrace(); } catch (Throwable t) { // could not parse System.err.println("WOResourceManager: could not parse: " + aFileName); System.err.println(t); } } Object result = null; if (table != null) { result = NSKeyValueCoding.DefaultImplementation.valueForKey(table, aKey); } if (result == null) { result = aDefaultValue; } else { result = result.toString(); } return (String) result; } /** * Returns a url that invokes the resource manager for the specified resource. */ public String urlForResourceNamed(String aResourceName, String aFrameworkName, NSArray aLanguagesList, WORequest aRequest) { StringBuffer buffer = new StringBuffer(); if (aFrameworkName == null) { aFrameworkName = "application"; } buffer.append(aRequest.applicationName()); buffer.append('/'); buffer.append(WOApplication.resourceRequestHandlerKey()); if (!aFrameworkName.startsWith("/")) { buffer.append('/'); } buffer.append(aFrameworkName); buffer.append('/'); buffer.append(aResourceName); return buffer.toString(); } /** * Returns an input for the raw resource. Data returned by this method will not * be put in the resource manager's global cache. */ public InputStream inputStreamForResourceNamed(String aResourceName, String aFrameworkName, NSArray aLanguagesList) { if (aResourceName == null) return null; InputStream result = null; StringBuffer path = new StringBuffer(); path.append('/'); if (aFrameworkName != null) { path.append(aFrameworkName).append('/'); } int i = aResourceName.lastIndexOf("."); if (i != -1) path.append(aResourceName.substring(0, i)); else path.append(aResourceName); String location = path.toString(); if (aLanguagesList != null) { String language; Locale locale; HashSet tried = new HashSet(5); Enumeration e = aLanguagesList.objectEnumerator(); while (e.hasMoreElements() && result == null) { language = e.nextElement().toString(); // look for java-style localization if (i != -1) { result = getStream(location + '_' + language + aResourceName.substring(i)); } else // no dot extension { result = getStream(location + '_' + language); } // look for wo-style localization if (result == null) { locale = (Locale) localeCache.get(language); if (locale == null) { if (language.length() == 5) { locale = new Locale(language.substring(0, 2), language.substring(3, 5)); } else { locale = new Locale(language, ""); } synchronized (localeCache) { localeCache.put(language, locale); } } language = '/' + locale.getDisplayLanguage(californiaLocale) + ".lproj"; if (!tried.contains(language)) { if (aFrameworkName != null) { int j = aFrameworkName.length() + 1; path.insert(j, language); result = getStream(path.toString() + aResourceName.substring(i)); path.delete(j, j + language.length()); } else { result = getStream(language + path.toString() + aResourceName.substring(i)); } tried.add(language); } } } } // look for file in package if (result == null) { if (i != -1) { result = getStream(path.append(aResourceName.substring(i)).toString()); } else // no dot extension { result = getStream(location); } } return result; } private static final InputStream getStream(String path) { // System.out.println( "getStream: " + path ); InputStream input = WOApplication.application().getClass().getResourceAsStream(path); if (input == null) { // in case the local class loader doesn't delegate to its parent input = ClassLoader.getSystemResourceAsStream(path); } return input; } private static final class TypedData { String type; NSData data; public TypedData(String aType, NSData aData) { type = aType; data = aData; } } } /* * $Log$ Revision 1.2 2006/02/19 01:44:02 cgruber Add xmlrpc files Remove jclark * and replace with dom4j and javax.xml.sax stuff Re-work dependencies and * imports so it all compiles. * * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in * eclipse-friendly maven-enabled packages. * * Revision 1.5 2003/08/07 00:15:15 chochos general cleanup (mostly removing * unused imports) * * Revision 1.4 2003/02/28 22:58:57 mpowers Added support for wo-style * localization (*.lproj). * * Revision 1.3 2003/02/21 16:40:24 mpowers Now reading port and smtp host from * system properties. Implemented WOApplication.main. * * Revision 1.2 2003/01/28 19:33:52 mpowers Implemented the rest of * WOResourceManager. Implemented support for java-style i18n. Components now * use the resource manager to load templates. * * Revision 1.1 2003/01/27 15:08:00 mpowers Implemented WOResourceManager, using * java resources for now. * * */