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><resources></code>.</li> 60 * <li>Each name-value pair is represented by a nested 61 * <code><resource></code> element.</li> 62 * <li>For each <code><resource></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 }