001 /*
002 * $Id: XMLResources.java 348371 2005-11-23 04:56:51Z niallp $
003 * $Revision: 348371 $
004 * $Date: 2005-11-23 04:56:51 +0000 (Wed, 23 Nov 2005) $
005 *
006 * ====================================================================
007 *
008 * Copyright 2003-2005 The Apache Software Foundation
009 *
010 * Licensed under the Apache License, Version 2.0 (the "License");
011 * you may not use this file except in compliance with the License.
012 * You may obtain a copy of the License at
013 *
014 * http://www.apache.org/licenses/LICENSE-2.0
015 *
016 * Unless required by applicable law or agreed to in writing, software
017 * distributed under the License is distributed on an "AS IS" BASIS,
018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
019 * See the License for the specific language governing permissions and
020 * limitations under the License.
021 *
022 */
023
024 package org.apache.commons.resources.impl;
025
026 import java.io.FileNotFoundException;
027 import java.io.IOException;
028 import java.io.InputStream;
029 import java.net.URL;
030 import java.util.HashMap;
031 import java.util.Locale;
032 import java.util.Map;
033
034 import org.apache.commons.digester.Digester;
035 import org.apache.commons.logging.Log;
036 import org.apache.commons.logging.LogFactory;
037 import org.xml.sax.SAXException;
038
039 /**
040 * <p>Concrete implementation of
041 * {@link org.apache.commons.resources.Resources} that wraps a family
042 * (one per <code>Locale</code> of XML documents that share a base URL
043 * and have name suffixes reflecting the <code>Locale</code> for which
044 * the document's messages apply. Resources are looked up in a hierarchy
045 * XML documents in a manner identical to that performed by
046 * <code>java.util.ResourceBundle.getBundle().</code>.</p>
047 *
048 * <p>The base URL passed to our constructor must contain the base name
049 * of the XML document family.
050 * For example, if the configuration URL is passed as
051 * <code>http://localhost/foo/Bar</code>, the resources for the
052 * <code>en_US</code> Locale would be stored under URL
053 * <code>http://localhost/foo/Bar_en_US.xml</code>, and the default
054 * resources would be stored in
055 * <code>http://localhost/foo/Bar.xml</code>.</p>
056 *
057 * <p>The required structure of the XML documents is very simple:</p>
058 * <ul>
059 * <li>The top level element must be <code><resources></code>.</li>
060 * <li>Each name-value pair is represented by a nested
061 * <code><resource></code> element.</li>
062 * <li>For each <code><resource></code> element, the <code>id</code>
063 * attribute contains the resource key, and the body contains a
064 * string representation of the value.</li>
065 * </ul>
066 */
067 public class XMLResources extends CollectionResourcesBase {
068
069 /**
070 * <p>The <code>Log</code> instance for this class.</p>
071 */
072 private transient Log log = LogFactory.getLog(XMLResources.class);
073
074 // ----------------------------------------------------------- Constructors
075
076
077 /**
078 * <p>Create a new {@link org.apache.commons.resources.Resources} instance with the specified
079 * logical name and base resource URL.</p>
080 *
081 * @param name Logical name of the new instance
082 * @param base Base URL of the family of properties files that contain
083 * the resource keys and values
084 */
085 public XMLResources(String name, String base) {
086 super(name, base);
087 }
088
089
090 // ------------------------------------------------------ Protected Methods
091
092
093 /**
094 * <p>Return a <code>Map</code> containing the name-value mappings for
095 * the specified base URL and requested <code>Locale</code>, if there
096 * are any. If there are no defined mappings for the specified
097 * <code>Locale</code>, return an empty <code>Map</code> instead.</p>
098 *
099 * <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 }