001    /*
002     * $Id: WebappXMLResources.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.util.HashMap;
030    import java.util.Locale;
031    import java.util.Map;
032    
033    import javax.servlet.ServletContext;
034    
035    import org.apache.commons.digester.Digester;
036    import org.apache.commons.logging.Log;
037    import org.apache.commons.logging.LogFactory;
038    import org.xml.sax.SAXException;
039    
040    /**
041     * <p>Concrete implementation of 
042     * {@link org.apache.commons.resources.Resources} that wraps a family
043     * (one per <code>Locale</code> of XML documents that share a base
044     * context-relative path for servlet context resources, and have
045     * name suffixes reflecting the <code>Locale</code> for which
046     * the document's messages apply.  Resources are looked up in a hierarchy
047     * of properties files in a manner identical to that performed by
048     * <code>java.util.ResourceBundle.getBundle().</code>.</p>
049     *
050     * <p>The base resource path passed to our constructor must contain the
051     * context-relative base name of the properties file family.
052     * For example, if the base path is passed as
053     * <code>http://localhost/foo/Bar</code>, the resources for the
054     * <code>en_US</code> Locale would be stored under URL
055     * <code>http://localhost/foo/Bar_en_US.xml</code>, and the default
056     * resources would be stored in
057     * <code>http://localhost/foo/Bar.xml</code>.</p>
058     */
059    public class WebappXMLResources extends CollectionResourcesBase {
060    
061        /**
062         * <p>The <code>Log</code> instance for this class.</p>
063         */
064        private transient Log log = LogFactory.getLog(WebappXMLResources.class);
065    
066        /**
067         * <p>Create a new {@link org.apache.commons.resources.Resources} instance with the specified
068         * logical name and base resource URL.</p>
069         *
070         * @param name Logical name of the new instance
071         * @param base Base URL of the family of properties files that contain
072         *  the resource keys and values
073         * @param servletContext the <code>ServletContext</code> instance
074         *  to use for resolving resource references
075         */
076        public WebappXMLResources(String name, String base,
077                                       ServletContext servletContext) {
078    
079            super(name, base);
080            this.servletContext = servletContext;
081    
082        }
083    
084    
085        // ----------------------------------------------------- Instance Variables
086    
087        /**
088         * <p>The <code>ServletContext</code> instance for resolving
089         * our resources references.</p>
090         */
091        private ServletContext servletContext = null;
092    
093    
094        // ------------------------------------------------------ Protected Methods
095    
096    
097        /**
098         * <p>Return a <code>Map</code> containing the name-value mappings for
099         * the specified base URL and requested <code>Locale</code>, if there
100         * are any.  If there are no defined mappings for the specified
101         * <code>Locale</code>, return an empty <code>Map</code> instead.</p>
102         *
103         * <p>Concrete subclasses must override this method to perform the
104         * appropriate lookup.  A typical implementation will construct an
105         * absolute URL based on the specified base URL and <code>Locale</code>,
106         * retrieve the specified resource file (if any), and parse it into
107         * a <code>Map</code> structure.</p>
108         *
109         * <p>Caching of previously retrieved <code>Map</code>s (if any) should
110         * be performed by callers of this method.  Therefore, this method should
111         * always attempt to retrieve the specified resource and load it
112         * appropriately.</p>
113         *
114         * @param baseUrl Base URL of the resource files for this {@link org.apache.commons.resources.Resources}
115         *  instance
116         * @param locale <code>Locale</code> for which name-value mappings
117         *  are requested
118         * @return A name-value Map for the specified URL and locale.
119         */
120        protected Map getLocaleMap(String baseUrl, Locale locale) {
121    
122            if (getLog().isDebugEnabled()) {
123                getLog().debug("Loading locale '" + locale + "' resources from base '" +
124                        baseUrl + "'");
125            }
126    
127            Map map = new HashMap();
128            String name = baseUrl + getLocaleSuffix(locale) + ".xml";
129            InputStream stream = null;
130    
131            try {
132    
133                // Open an input stream to the URL for this locale (if any)
134                if (getLog().isTraceEnabled()) {
135                    getLog().trace("Complete path is '" + name + "'");
136                }
137                stream = servletContext.getResourceAsStream(name);
138    
139                // Create and configure a new Digester instance
140                if (stream != null) {
141    
142                    if (getLog().isTraceEnabled()) {
143                        getLog().trace("Creating Digester instance");
144                    }
145                    Digester digester = new Digester();
146                    digester.setNamespaceAware(false);
147                    digester.setValidating(false);
148                    digester.push(map);
149                    digester.addCallMethod("resources/resource", "put", 2,
150                                           new String[] { "java.lang.Object",
151                                                          "java.lang.Object" });
152                    digester.addCallParam("resources/resource", 0, "id");
153                    digester.addCallParam("resources/resource", 1);
154    
155                    // Parse the input stream and populate the name-value mappings
156                    if (getLog().isTraceEnabled()) {
157                        getLog().trace("Parsing input resource");
158                    }
159                    digester.parse(stream);
160    
161                }
162    
163            } catch (FileNotFoundException e) {
164    
165                // Log and swallow this exception
166                if (getLog().isDebugEnabled()) {
167                    getLog().debug("No resources for locale '" + locale +
168                              "' from base '" + baseUrl + "'");
169                }
170                map.clear();
171    
172            } catch (IOException e) {
173    
174                // Log and swallow this exception
175                getLog().warn("IOException loading locale '" + locale +
176                         "' from base '" + baseUrl + "'", e);
177                map.clear();
178    
179            } catch (SAXException e) {
180    
181                // Log and swallow this exception
182                getLog().warn("SAXException loading locale '" + locale +
183                         "' from base '" + baseUrl + "'", e);
184                map.clear();
185    
186            } finally {
187    
188                // Close the input stream that was opened earlier
189                if (stream != null) {
190                    try {
191                        stream.close();
192                    } catch (Exception e) {
193                        // standard io
194                    }
195                    stream = null;
196                }
197    
198            }
199    
200            // Return the populated (or empty) map
201            return (map);
202    
203    
204        }
205    
206        /**
207         * Accessor method for Log instance.
208         *
209         * The Log instance variable is transient and
210         * accessing it through this method ensures it
211         * is re-initialized when this instance is
212         * de-serialized.
213         *
214         * @return The Log instance.
215         */
216        private Log getLog() {
217            if (log == null) {
218                log =  LogFactory.getLog(WebappXMLResources.class);
219            }
220            return log;
221        }
222    
223    }