View Javadoc

1   /*
2    * Copyright (C) 1998-2000 Semiotek Inc.  All Rights Reserved.
3    *
4    * Redistribution and use in source and binary forms, with or without
5    * modification, are permitted under the terms of either of the following
6    * Open Source licenses:
7    *
8    * The GNU General Public License, version 2, or any later version, as
9    * published by the Free Software Foundation
10   * (http://www.fsf.org/copyleft/gpl.html);
11   *
12   *  or
13   *
14   * The Semiotek Public License (http://webmacro.org/LICENSE.)
15   *
16   * This software is provided "as is", with NO WARRANTY, not even the
17   * implied warranties of fitness to purpose, or merchantability. You
18   * assume all risks and liabilities associated with its use.
19   *
20   * See www.webmacro.org for more information on the WebMacro project.
21   */
22  
23  package org.webmacro.util;
24  
25  import org.webmacro.Broker;
26  import org.webmacro.InitException;
27  import org.webmacro.Log;
28  import org.webmacro.ResourceException;
29  import org.webmacro.resource.CacheElement;
30  import org.webmacro.resource.CacheManager;
31  import org.webmacro.resource.ResourceLoader;
32  import org.webmacro.resource.TrivialCacheManager;
33  
34  import java.io.UnsupportedEncodingException;
35  
36  /***
37   * An encoder is used to encode strings into a particular encoding in
38   * preparation for sending the data as part of a response. An encoder is
39   * constructed with a particular output encoding and is then expected to
40   * properly encode strings with that output encoding for its lifetime.
41   *
42   * <p><code>Encoder</code> instances are obtained via the
43   * <code>EncoderProvider</code> implementation that is configured at
44   * initialization time. The encoder provider is responsible communicating
45   * the encoding scheme to be used by the encoder at construct time.
46   *
47   * <p> The mechanism is factored into the encoder interface to allow for
48   * caching encoders with differing caching schemes based on the server
49   * environment's requirements or no caching scheme at all.
50   *
51   * @see EncoderProvider
52   * @since 0.96
53   * @author Michael Bayne
54   */
55  
56  public class Encoder implements ResourceLoader
57  {
58  
59      private String _encoding;
60      private CacheManager _cache;
61      private Log _log;
62  
63      /***
64       * Creates an encoder instance with the supplied encoding.
65       *
66       * @exception UnsupportedEncodingException Thrown when the underlying
67       * Java encoding mechanism does not provide support for the requesting
68       * encoding.
69       */
70      public Encoder (String encoding)
71              throws UnsupportedEncodingException
72      {
73          // enforce some specific rules related to choice of encodings
74          if (encoding == null ||
75                  encoding.equalsIgnoreCase("UNICODE") ||
76                  encoding.equalsIgnoreCase("UNICODEBIG") ||
77                  encoding.equalsIgnoreCase("UNICODELITTLE") ||
78                  encoding.equalsIgnoreCase("UTF16"))
79          {
80              String err = "The encoding you specified is invalid: " +
81                      encoding + ". Note that the UNICODE and UTF16 encodings " +
82                      "are not supported by WebMacro because they prefix the " +
83                      "stream with a marker indicating whether the stream is " +
84                      "big endian or little endian. Instead choose the byte " +
85                      "ordering yourself by using the UTF-16BE or UTF-16LE " +
86                      "encodings.";
87              throw new UnsupportedEncodingException(err);
88          }
89  
90          // check to be sure that this encoding is supported. this will
91          // throw an UnsupportedEncodingException if the JVM doesn't
92          // support the requested encoding
93          byte[] test = "some test string".getBytes(encoding);
94  
95          // keep track of this for later
96          _encoding = encoding;
97      }
98  
99      public void init (Broker b, Settings config)
100             throws InitException
101     {
102         String cacheManager;
103         _log = b.getLog("resource", "Object loading and caching");
104 
105         cacheManager = b.getSetting("Encoder." + _encoding + ".CacheManager");
106         if (cacheManager == null)
107             cacheManager = b.getSetting("Encoder.*.CacheManager");
108         if (cacheManager == null || cacheManager.equals(""))
109         {
110             _log.info("No cache manager specified for encoding " + _encoding
111                     + ", using TrivialCacheManager");
112             _cache = new TrivialCacheManager();
113         }
114         else
115         {
116             try
117             {
118                 Class c = b.classForName(cacheManager);
119                 _cache = (CacheManager) c.newInstance();
120             }
121             catch (Exception e)
122             {
123                 _log.warning("Unable to load cache manager " + cacheManager
124                         + " for encoding type " + _encoding
125                         + ", using TrivialCacheManager.  Reason:\n" + e);
126                 _cache = new TrivialCacheManager();
127             }
128         }
129         _cache.init(b, config, _encoding);
130     }
131 
132     /***
133      * Load an object from permanent storage (or construct it) on
134      * demand.
135      */
136     public Object load (Object query, CacheElement ce)
137             throws ResourceException
138     {
139         try
140         {
141             if (query instanceof Block)
142             {
143                 String[] source = ((Block) query).text;
144                 byte[][] encoded = new byte[source.length][];
145                 for (int i = 0; i < source.length; i++)
146                 {
147                     encoded[i] = source[i].getBytes(_encoding);
148                 }
149                 return encoded;
150 
151             }
152             else if (query instanceof String)
153             {
154                 return ((String) query).getBytes(_encoding);
155             }
156             else
157             {
158                 return query.toString().getBytes(_encoding);
159             }
160 
161         }
162         catch (UnsupportedEncodingException uee)
163         {
164             // this should never happen as we check in the constructor to
165             // ensure that the encoding is supported
166             throw new ResourceException("Unable to encode: " + uee);
167         }
168     }
169 
170     public Object load (String query, CacheElement ce)
171             throws ResourceException
172     {
173         try
174         {
175             return query.getBytes(_encoding);
176         }
177         catch (UnsupportedEncodingException uee)
178         {
179             // this should never happen as we check in the constructor to
180             // ensure that the encoding is supported
181             throw new ResourceException("Unable to encode: " + uee);
182         }
183     }
184 
185     /***
186      * Encodes the supplied string using the encoding bound to this encoder
187      * at construct time.
188      *
189      * @return The encoded version of the supplied string.
190      *
191      * @exception UnsupportedEncodingException Thrown when the underlying
192      * Java encoding mechanism does not provide support for the encoding
193      * used by this encoder instance.
194      */
195     public final byte[] encode (String source)
196             throws UnsupportedEncodingException
197     {
198         try
199         {
200             return (byte[]) _cache.get(source, this);
201         }
202         catch (ResourceException e)
203         {
204             throw new UnsupportedEncodingException("Encoder: Could not encode; "
205                     + e);
206         }
207     }
208 
209     /***
210      * Encodes the supplied block of strings using the encoding bound to
211      * this encoder at construct time.
212      *
213      * @return The encoded version of the supplied block of strings.
214      *
215      * @exception UnsupportedEncodingException Thrown when the underlying
216      * Java encoding mechanism does not provide support for the encoding
217      * used by this encoder instance.
218      */
219     public final byte[][] encode (Block source)
220             throws UnsupportedEncodingException
221     {
222         try
223         {
224             return (byte[][]) _cache.get(source, this);
225         }
226         catch (ResourceException e)
227         {
228             throw new UnsupportedEncodingException("Encoder: Could not encode; "
229                     + e);
230         }
231     }
232 
233     /***
234      * The block class provides a means by which encoder users can encode
235      * entire blocks of text at once (and have those encoded blocks
236      * cached).
237      */
238     public static class Block
239     {
240 
241         public String[] text;
242 
243         public Block (String[] text)
244         {
245             this.text = text;
246 
247             // we compute the combined hash of our string array so that we
248             // can behave as an efficient, stable key while allowing our
249             // strings to maintain happy independent existences
250             long strhash = 0;
251             for (int i = 0; i < text.length; i++)
252             {
253                 strhash = (strhash + (long) text[i].hashCode()) %
254                         Integer.MAX_VALUE;
255             }
256             _hashCode = (int) strhash;
257         }
258 
259         public int hashCode ()
260         {
261             return _hashCode;
262         }
263 
264         public boolean equals (Object other)
265         {
266             // we try to be as efficient as possible about this, but to be
267             // correct, we have to compare every string
268             if (!(other instanceof Block))
269             {
270                 return false;
271             }
272 
273             Block ob = (Block) other;
274 
275             // check the obvious things
276             if (this == ob || text == ob.text)
277             {
278                 return true;
279             }
280             if (text == null || ob.text == null)
281             {
282                 return false;
283             }
284             if (ob.text.length != text.length)
285             {
286                 return false;
287             }
288 
289             // compare each string individually
290             for (int i = 0; i < text.length; i++)
291             {
292                 if (!text[i].equals(ob.text[i]))
293                 {
294                     return false;
295                 }
296             }
297 
298             return true;
299         }
300 
301         protected int _hashCode;
302     }
303 }