View Javadoc

1   /*
2   Wotonomy: OpenStep design patterns for pure Java applications.
3   Copyright (C) 2000 Blacksmith, Inc.
4   
5   This library is free software; you can redistribute it and/or
6   modify it under the terms of the GNU Lesser General Public
7   License as published by the Free Software Foundation; either
8   version 2.1 of the License, or (at your option) any later version.
9   
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  Lesser General Public License for more details.
14  
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library; if not, see http://www.gnu.org
17  */
18  
19  package net.wotonomy.web;
20  
21  import java.io.BufferedReader;
22  import java.io.ByteArrayOutputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.InputStreamReader;
26  import java.io.Reader;
27  import java.util.Enumeration;
28  import java.util.HashMap;
29  import java.util.HashSet;
30  import java.util.Locale;
31  import java.util.Map;
32  
33  import net.wotonomy.foundation.NSArray;
34  import net.wotonomy.foundation.NSData;
35  import net.wotonomy.foundation.NSKeyValueCoding;
36  import net.wotonomy.foundation.NSMutableDictionary;
37  import net.wotonomy.foundation.internal.PropertyListParser;
38  
39  /***
40  * Manages all resources vended by the application.
41  *
42  * @author michael@mpowers.net
43  * @author $Author: cgruber $
44  * @version $Revision: 905 $
45  */
46  public class WOResourceManager
47  {
48      private NSMutableDictionary resourceCache;
49      private NSMutableDictionary dynamicDataCache;
50      private NSMutableDictionary stringTableCache;
51      private Map localeCache; // used for wo-style i18n
52      private Locale californiaLocale; // used for wo-style i18n
53  
54      /***
55      * Constructor is only accessible to subclasses.
56      */    
57      protected WOResourceManager()
58      {
59          resourceCache = new NSMutableDictionary();
60          dynamicDataCache = new NSMutableDictionary();
61          stringTableCache = new NSMutableDictionary();
62          localeCache = new HashMap();
63          californiaLocale = new Locale( "en", "US" );
64          localeCache.put( "en", californiaLocale );
65      }
66      
67      /***
68      * Returns the raw data corresponding to the specified resource.
69      * Any data retrieved by this method will be placed in the 
70      * resource manager's global cache.
71      */
72      public byte[] bytesForResourceNamed(String aFileName,
73                                          String aFrameworkName,
74                                          NSArray aLanguagesList)
75      {
76          String mash = aFileName + aFrameworkName;
77          if ( aLanguagesList != null )
78          {
79              mash = mash + aLanguagesList.componentsJoinedByString(":");
80          }
81          
82          byte[] result = (byte[]) resourceCache.objectForKey( mash );
83          if ( result == null )
84          {
85              InputStream input = inputStreamForResourceNamed(
86                  aFileName, aFrameworkName, aLanguagesList );
87              if ( input != null )
88              {
89                  try
90                  {
91                      int c;
92                      ByteArrayOutputStream output = new ByteArrayOutputStream();
93                      while ( ( c = input.read() ) != -1 )
94                      {
95                          output.write( c );
96                      }
97                      output.flush();
98                      input.close();
99                      output.close();
100                     result = output.toByteArray();
101                     synchronized ( resourceCache )
102                     {
103                         resourceCache.setObjectForKey( result, mash );
104                     }
105                  }
106                  catch ( Throwable t )
107                  {
108                      System.err.println( "WOResourceManager: Error reading bytes: " + aFileName );
109                      t.printStackTrace();
110                  }
111              }
112         }
113         return result;
114     }
115                                         
116     /***
117     * Returns the content type corresponding to the specified resource.
118     * This implementation recognizes gif, jpg, png, html, and xml extensions.
119     * Otherwise, "text/plain" is returned.
120     */
121     public String contentTypeForResourceNamed(String aResourcePath)
122     {
123         if ( aResourcePath.endsWith( ".gif" ) ) return "image/gif";
124         if ( aResourcePath.endsWith( ".jpg" ) ) return "image/jpeg";
125         if ( aResourcePath.endsWith( ".png" ) ) return "image/png";
126         if ( aResourcePath.endsWith( ".html" ) ) return "text/html";
127         if ( aResourcePath.endsWith( ".xml" ) ) return "text/xml";
128         return "text/plain";
129     }
130     
131     /***
132     * Returns a url to be used when errors occur while retrieving a resource.
133     */
134     public String errorMessageUrlForResourceNamed(String aResourceName,
135                                                   String aFrameworkName)
136     {
137         if ( aResourceName == null ) aResourceName = "null";
138         if ( aFrameworkName == null ) 
139         {
140             return "/ERROR/NOT_FOUND/app=" +
141                 WOApplication.application().name() +
142                 "/filename=" + aResourceName;
143         }
144         else
145         {
146             return "/ERROR/NOT_FOUND/framework=" +
147                 aFrameworkName + "/filename=" + aResourceName;
148         }
149         
150     }
151          
152     /***
153     * Clears all cached system-wide resource data.
154     */                                         
155     public void flushDataCache()
156     {
157         synchronized ( resourceCache )
158         {
159             resourceCache.removeAllObjects();
160         }
161         synchronized ( dynamicDataCache )
162         {
163             dynamicDataCache.removeAllObjects();
164         }
165         synchronized ( stringTableCache )
166         {
167             stringTableCache.removeAllObjects();
168         }
169     }
170     
171     /***
172     * Returns the file-system path for the specified resource.
173     * Deprecated and not implemented.
174     * @deprecated Use inputStreamForResourceNamed instead.
175     */
176     public String pathForResourceNamed(String aResourceName,
177                                        String aFrameworkName,
178                                        NSArray aLanguagesList)
179     {
180         throw new RuntimeException( "ResourceManager.pathForResourceNamed: deprecated" );
181     }
182                      
183     /***
184     * Removes the data from the dynamic data cache for the specified session.
185     * If aSession is null, the data is removed from the application-wide
186     * data cache.
187     */                     
188     public void removeDataForKey(String aKey,
189                                  WOSession aSession)
190     {
191         if ( aSession != null )
192         {
193             if ( aSession.dynamicDataCache != null )
194             {
195                 aSession.dynamicDataCache.removeObjectForKey( aKey );
196             }
197         }
198         else
199         {
200             synchronized ( dynamicDataCache )
201             {
202                 dynamicDataCache.removeObjectForKey( aKey );
203             }
204         }
205     }
206     
207     /***
208     * Sets the data in the dynamic data cache for the specified session.
209     * If aSession is null, the data is placed in the application-wide
210     * data cache.  If the key is a system-generated key, the data will
211     * be removed by calling removeData() the next time it is requested.
212     */                     
213     public void setData(NSData someData,
214                         String key,
215                         String type,
216                         WOSession aSession)
217     {
218         if ( aSession != null )
219         {
220             if ( aSession.dynamicDataCache != null )
221             {
222                 aSession.dynamicDataCache.setObjectForKey(
223                     new TypedData( type, someData ), key );
224             }
225         }
226         else
227         {
228             synchronized ( dynamicDataCache )
229             {
230                 dynamicDataCache.setObjectForKey(
231                     new TypedData( type, someData ), key );
232             }
233         }
234     }
235     
236     /***
237     * Returns a localized string from a property list for 
238     * a given key. If the key doesn't exist, aDefaultValue 
239     * is returned.
240     */
241     public String stringForKey(String aKey,
242                                String aFileName,
243                                String aDefaultValue,
244                                String aFrameworkName,
245                                NSArray aLanguagesList)
246     {
247         
248         String mash = aFileName + aFrameworkName;
249         if ( aLanguagesList != null )
250         {
251             mash = mash + aLanguagesList.componentsJoinedByString(":");
252         }
253         
254         Object table = stringTableCache.objectForKey( mash );
255         if ( table == null )
256         {
257             try
258             {
259                 InputStream input = (InputStream) inputStreamForResourceNamed(
260                     aFileName, aFrameworkName, aLanguagesList);
261                 if ( input != null )
262                 {
263                     Reader reader = 
264                         new BufferedReader(new InputStreamReader(input));
265                     table = PropertyListParser.propertyListFromReader( reader );
266                     synchronized ( stringTableCache )
267                     {
268                         stringTableCache.setObjectForKey( table, mash );
269                     }
270                 }
271             }
272             catch ( IOException ioe )
273             {
274                 System.err.println( "WOResourceManager: error reading: " + aFileName );
275                 ioe.printStackTrace();
276             }
277             catch ( Throwable t )
278             {
279                 // could not parse
280                 System.err.println( "WOResourceManager: could not parse: " + aFileName );
281                 System.err.println( t );
282             }
283         }
284         
285         Object result = null;
286         if ( table != null )
287         {
288             result = NSKeyValueCoding.DefaultImplementation.valueForKey( table, aKey );
289         }
290         if ( result == null )
291         {
292             result = aDefaultValue;
293         }
294         else
295         {
296             result = result.toString();
297         }
298         
299         return (String) result;
300     }
301         
302     /***
303     * Returns a url that invokes the resource manager for the
304     * specified resource.
305     */                        
306     public String urlForResourceNamed(String aResourceName,
307                                       String aFrameworkName,
308                                       NSArray aLanguagesList,
309                                       WORequest aRequest)
310     {
311         StringBuffer buffer = new StringBuffer();
312         if ( aFrameworkName == null )
313         {
314             aFrameworkName = "application";
315         }
316         buffer.append( aRequest.applicationName() );
317         buffer.append( '/' );
318         buffer.append( WOApplication.resourceRequestHandlerKey() );
319         if ( !aFrameworkName.startsWith("/") )
320         {
321             buffer.append( '/' );
322         }
323         buffer.append( aFrameworkName );
324         buffer.append( '/' );
325         buffer.append( aResourceName );
326         return buffer.toString();
327     }
328                                         
329     /***
330     * Returns an input for the raw resource.  Data returned by
331     * this method will not be put in the resource manager's global cache.
332     */
333     public InputStream inputStreamForResourceNamed(String aResourceName,
334                                                    String aFrameworkName,
335                                                    NSArray aLanguagesList)
336     {
337         if ( aResourceName == null ) return null;
338         InputStream result = null;
339         
340         StringBuffer path = new StringBuffer();
341         path.append( '/' );
342         if ( aFrameworkName != null ) 
343         {
344             path.append( aFrameworkName ).append( '/' );
345         }
346         
347         int i = aResourceName.lastIndexOf( "." );
348         if ( i != -1 ) 
349             path.append( aResourceName.substring( 0, i ) );
350         else
351             path.append( aResourceName );
352 
353         String location = path.toString();
354         if ( aLanguagesList != null )
355         {
356             String language;
357             Locale locale;
358             HashSet tried = new HashSet(5);
359             Enumeration e = aLanguagesList.objectEnumerator();
360             while ( e.hasMoreElements() && result == null )
361             {
362                 language = e.nextElement().toString();
363                 
364                 // look for java-style localization
365                 if ( i != -1 )
366                 {
367                     result = getStream( location + '_' 
368                         + language + aResourceName.substring( i ) );
369                 }
370                 else // no dot extension
371                 {
372                     result = getStream( location + '_' + language );
373                 }
374                 
375                 // look for wo-style localization
376                 if ( result == null )
377                 {
378                     locale = (Locale) localeCache.get( language );
379                     if ( locale == null )
380                     {
381                         if ( language.length() == 5 )
382                         {
383                             locale = new Locale( 
384                                 language.substring( 0, 2 ), 
385                                 language.substring( 3, 5 ) );
386                         }
387                         else
388                         {
389                             locale = new Locale( language, "" );
390                         }
391                         synchronized ( localeCache )
392                         {
393                             localeCache.put( language, locale );
394                         }
395                     }
396                     
397                     language = '/'+locale.getDisplayLanguage( californiaLocale )+".lproj";
398                     if ( !tried.contains( language ) )
399                     {
400                         if ( aFrameworkName != null )
401                         {
402                             int j = aFrameworkName.length()+1;
403                             path.insert( j, language ); 
404                             result = getStream( path.toString() + aResourceName.substring( i ) );
405                             path.delete( j, j+language.length() );
406                         }
407                         else
408                         {
409                             result = getStream( language + path.toString() + aResourceName.substring( i ) );
410                         }
411                         tried.add( language );
412                     }
413                 }
414             }
415         }            
416         
417         // look for file in package        
418         if ( result == null )
419         {
420             if ( i != -1 )
421             {
422                 result = getStream( path.append( 
423                     aResourceName.substring( i ) ).toString() );
424             }
425             else // no dot extension
426             {
427                 result = getStream( location );
428             }
429         }
430 
431         return result;
432     }
433     
434     private static final InputStream getStream( String path )
435     { //System.out.println( "getStream: " + path );        
436         InputStream input = 
437             WOApplication.application().getClass().getResourceAsStream( path );
438         if ( input == null )
439         {
440             // in case the local class loader doesn't delegate to its parent
441             input = ClassLoader.getSystemResourceAsStream( path );
442         }                    
443         return input;
444     }
445     
446     private static final class TypedData
447     {
448         String type;
449         NSData data;
450         
451         public TypedData( String aType, NSData aData )
452         {
453             type = aType;
454             data = aData;
455         }
456     }
457 }
458 
459 /*
460  * $Log$
461  * Revision 1.2  2006/02/19 01:44:02  cgruber
462  * Add xmlrpc files
463  * Remove jclark and replace with dom4j and javax.xml.sax stuff
464  * Re-work dependencies and imports so it all compiles.
465  *
466  * Revision 1.1  2006/02/16 13:22:22  cgruber
467  * Check in all sources in eclipse-friendly maven-enabled packages.
468  *
469  * Revision 1.5  2003/08/07 00:15:15  chochos
470  * general cleanup (mostly removing unused imports)
471  *
472  * Revision 1.4  2003/02/28 22:58:57  mpowers
473  * Added support for wo-style localization (*.lproj).
474  *
475  * Revision 1.3  2003/02/21 16:40:24  mpowers
476  * Now reading port and smtp host from system properties.
477  * Implemented WOApplication.main.
478  *
479  * Revision 1.2  2003/01/28 19:33:52  mpowers
480  * Implemented the rest of WOResourceManager.
481  * Implemented support for java-style i18n.
482  * Components now use the resource manager to load templates.
483  *
484  * Revision 1.1  2003/01/27 15:08:00  mpowers
485  * Implemented WOResourceManager, using java resources for now.
486  *
487  *
488  */
489