View Javadoc

1   /*
2    * $Id: XMLResources.java 348371 2005-11-23 04:56:51Z niallp $
3    * $Revision: 348371 $
4    * $Date: 2005-11-23 04:56:51 +0000 (Wed, 23 Nov 2005) $
5    *
6    * ====================================================================
7    *
8    *  Copyright 2003-2005 The Apache Software Foundation
9    *
10   *  Licensed under the Apache License, Version 2.0 (the "License");
11   *  you may not use this file except in compliance with the License.
12   *  You may obtain a copy of the License at
13   *
14   *      http://www.apache.org/licenses/LICENSE-2.0
15   *
16   *  Unless required by applicable law or agreed to in writing, software
17   *  distributed under the License is distributed on an "AS IS" BASIS,
18   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19   *  See the License for the specific language governing permissions and
20   *  limitations under the License.
21   *
22   */
23  
24  package org.apache.commons.resources.impl;
25  
26  import java.io.FileNotFoundException;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.net.URL;
30  import java.util.HashMap;
31  import java.util.Locale;
32  import java.util.Map;
33  
34  import org.apache.commons.digester.Digester;
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  import org.xml.sax.SAXException;
38  
39  /**
40   * <p>Concrete implementation of 
41   * {@link org.apache.commons.resources.Resources} that wraps a family
42   * (one per <code>Locale</code> of XML documents that share a base URL
43   * and have name suffixes reflecting the <code>Locale</code> for which
44   * the document's messages apply.  Resources are looked up in a hierarchy
45   * XML documents in a manner identical to that performed by
46   * <code>java.util.ResourceBundle.getBundle().</code>.</p>
47   *
48   * <p>The base URL passed to our constructor must contain the base name
49   * of the XML document family.
50   * For example, if the configuration URL is passed as
51   * <code>http://localhost/foo/Bar</code>, the resources for the
52   * <code>en_US</code> Locale would be stored under URL
53   * <code>http://localhost/foo/Bar_en_US.xml</code>, and the default
54   * resources would be stored in
55   * <code>http://localhost/foo/Bar.xml</code>.</p>
56   *
57   * <p>The required structure of the XML documents is very simple:</p>
58   * <ul>
59   * <li>The top level element must be <code>&lt;resources&gt;</code>.</li>
60   * <li>Each name-value pair is represented by a nested
61   *     <code>&lt;resource&gt;</code> element.</li>
62   * <li>For each <code>&lt;resource&gt;</code> element, the <code>id</code>
63   *     attribute contains the resource key, and the body contains a
64   *     string representation of the value.</li>
65   * </ul>
66   */
67  public class XMLResources extends CollectionResourcesBase {
68  
69      /**
70       * <p>The <code>Log</code> instance for this class.</p>
71       */
72      private transient Log log = LogFactory.getLog(XMLResources.class);
73  
74      // ----------------------------------------------------------- Constructors
75  
76  
77      /**
78       * <p>Create a new {@link org.apache.commons.resources.Resources} instance with the specified
79       * logical name and base resource URL.</p>
80       *
81       * @param name Logical name of the new instance
82       * @param base Base URL of the family of properties files that contain
83       *  the resource keys and values
84       */
85      public XMLResources(String name, String base) {
86          super(name, base);
87      }
88  
89  
90      // ------------------------------------------------------ Protected Methods
91  
92  
93      /**
94       * <p>Return a <code>Map</code> containing the name-value mappings for
95       * the specified base URL and requested <code>Locale</code>, if there
96       * are any.  If there are no defined mappings for the specified
97       * <code>Locale</code>, return an empty <code>Map</code> instead.</p>
98       *
99       * <p>Concrete subclasses must override this method to perform the
100      * appropriate lookup.  A typical implementation will construct an
101      * absolute URL based on the specified base URL and <code>Locale</code>,
102      * retrieve the specified resource file (if any), and parse it into
103      * a <code>Map</code> structure.</p>
104      *
105      * <p>Caching of previously retrieved <code>Map</code>s (if any) should
106      * be performed by callers of this method.  Therefore, this method should
107      * always attempt to retrieve the specified resource and load it
108      * appropriately.</p>
109      *
110      * @param baseUrl Base URL of the resource files for this {@link org.apache.commons.resources.Resources}
111      *  instance
112      * @param locale <code>Locale</code> for which name-value mappings
113      *  are requested
114      * @return A name-value Map for the specified URL and locale.
115      */
116     protected Map getLocaleMap(String baseUrl, Locale locale) {
117 
118         if (getLog().isDebugEnabled()) {
119             getLog().debug("Loading locale '" + locale + "' resources from base '" +
120                     baseUrl + "'");
121         }
122 
123         Map map = new HashMap();
124         String name = baseUrl + getLocaleSuffix(locale) + ".xml";
125         InputStream stream = null;
126 
127         try {
128 
129             // Open an input stream to the URL for this locale (if any)
130             if (getLog().isTraceEnabled()) {
131                 getLog().trace("Absolute URL is '" + name + "'");
132             }
133             URL url = new URL(name);
134             stream = url.openStream();
135 
136             // Create and configure a new Digester instance
137             if (getLog().isTraceEnabled()) {
138                 getLog().trace("Creating Digester instance");
139             }
140             Digester digester = new Digester();
141             digester.setNamespaceAware(false);
142             digester.setValidating(false);
143             digester.push(map);
144             digester.addCallMethod("resources/resource", "put", 2,
145                                    new String[] { "java.lang.Object",
146                                                   "java.lang.Object" });
147             digester.addCallParam("resources/resource", 0, "id");
148             digester.addCallParam("resources/resource", 1);
149 
150             // Parse the input stream and populate the name-value mappings map
151             if (getLog().isTraceEnabled()) {
152                 getLog().trace("Parsing input resource");
153             }
154             digester.parse(stream);
155 
156         } catch (FileNotFoundException e) {
157 
158             // Log and swallow this exception
159             if (getLog().isDebugEnabled()) {
160                 getLog().debug("No resources for locale '" + locale +
161                           "' from base '" + baseUrl + "'");
162             }
163             map.clear();
164 
165         } catch (IOException e) {
166 
167             // Log and swallow this exception
168             getLog().warn("IOException loading locale '" + locale +
169                      "' from base '" + baseUrl + "'", e);
170             map.clear();
171 
172         } catch (SAXException e) {
173 
174             // Log and swallow this exception
175             getLog().warn("SAXException loading locale '" + locale +
176                      "' from base '" + baseUrl + "'", e);
177             map.clear();
178 
179         } finally {
180 
181             // Close the input stream that was opened earlier
182             if (stream != null) {
183                 try {
184                     stream.close();
185                 } catch (IOException e) {
186                     getLog().error("Error closing stream.", e);
187                 }
188                 stream = null;
189             }
190 
191         }
192 
193         // Return the populated (or empty) map
194         return (map);
195 
196     }
197 
198     /**
199      * Accessor method for Log instance.
200      *
201      * The Log instance variable is transient and
202      * accessing it through this method ensures it
203      * is re-initialized when this instance is
204      * de-serialized.
205      *
206      * @return The Log instance.
207      */
208     private Log getLog() {
209         if (log == null) {
210             log =  LogFactory.getLog(XMLResources.class);
211         }
212         return log;
213     }
214 
215 }