/* 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. * * */