1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.resources.impl;
19
20 import java.io.FileNotFoundException;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.util.Locale;
24 import java.util.Map;
25 import java.util.Properties;
26
27 import javax.servlet.ServletContext;
28
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31
32 /**
33 * <p>Concrete implementation of {@link org.apache.commons.resources.Resources} that wraps a family
34 * (one per <code>Locale</code> of properties files that share a base
35 * context-relative path for servlet context resources, and have
36 * name suffixes reflecting the <code>Locale</code> for which
37 * the document's messages apply. Resources are looked up in a hierarchy
38 * of properties files in a manner identical to that performed by
39 * <code>java.util.ResourceBundle.getBundle().</code>.</p>
40 *
41 * <p>The base resource path passed to our constructor must contain the
42 * context-relative base name of the properties file family.
43 * For example, if the base path is passed as
44 * <code>http://localhost/foo/Bar</code>, the resources for the
45 * <code>en_US</code> Locale would be stored under URL
46 * <code>http://localhost/foo/Bar_en_US.properties</code>, and the default
47 * resources would be stored in
48 * <code>http://localhost/foo/Bar.properties</code>.</p>
49 */
50 public class WebappPropertyResources extends CollectionResourcesBase {
51
52 /**
53 * <p>The <code>Log</code> instance for this class.</p>
54 */
55 private transient Log log =
56 LogFactory.getLog(WebappPropertyResources.class);
57
58 // ----------------------------------------------------------- Constructors
59
60
61 /**
62 * <p>Create a new {@link org.apache.commons.resources.Resources} instance with the specified
63 * logical name and base resource URL.</p>
64 *
65 * @param name Logical name of the new instance
66 * @param base Base URL of the family of properties files that contain
67 * the resource keys and values
68 * @param servletContext the <code>ServletContext</code> instance
69 * to use for resolving resource references
70 */
71 public WebappPropertyResources(String name, String base,
72 ServletContext servletContext) {
73
74 super(name, base);
75 this.servletContext = servletContext;
76
77 }
78
79
80 // ----------------------------------------------------- Instance Variables
81
82
83 /**
84 * <p>The <code>ServletContext</code> instance for resolving
85 * our resources references.</p>
86 */
87 private ServletContext servletContext = null;
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 Properties props = new Properties();
124 String name = baseUrl.replace('.', '/');
125 name += getLocaleSuffix(locale) + ".properties";
126
127 InputStream stream = null;
128
129 try {
130
131 // Open an input stream to the URL for this locale (if any)
132 if (getLog().isTraceEnabled()) {
133 getLog().trace("Complete path is '" + name + "'");
134 }
135 try{
136 stream = servletContext.getResourceAsStream(name);
137 }
138 catch(Exception e){
139 // not found
140 // try ContextClassLoader
141 }
142 if (stream == null){
143
144 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
145 if (classLoader == null) {
146 classLoader = this.getClass().getClassLoader();
147 }
148 stream = classLoader.getResourceAsStream(name);
149 }
150
151 // Parse the input stream and populate the name-value mappings map
152 if (stream != null) {
153 if (getLog().isTraceEnabled()) {
154 getLog().trace("Parsing input resource");
155 }
156 props.load(stream);
157 }
158
159 } catch (FileNotFoundException e) {
160
161 // Log and swallow this exception
162 if (getLog().isDebugEnabled()) {
163 getLog().debug("No resources for locale '" + locale +
164 "' from base '" + baseUrl + "'");
165 }
166 props.clear();
167
168 } catch (IOException e) {
169
170 getLog().warn("IOException loading locale '" + locale +
171 "' from base '" + baseUrl + "'", e);
172 props.clear();
173
174 } finally {
175
176 // Close the input stream that was opened earlier
177 if (stream != null) {
178 try {
179 stream.close();
180 } catch (IOException e) {
181 getLog().error("Error closing stream.", e);
182 }
183 stream = null;
184 }
185
186 }
187
188 // Return the populated (or empty) properties
189 return (props);
190
191 }
192
193 /**
194 * Accessor method for Log instance.
195 *
196 * The Log instance variable is transient and
197 * accessing it through this method ensures it
198 * is re-initialized when this instance is
199 * de-serialized.
200 *
201 * @return The Log instance.
202 */
203 private Log getLog() {
204 if (log == null) {
205 log = LogFactory.getLog(WebappPropertyResources.class);
206 }
207 return log;
208 }
209
210 }