1
7 package org.hibernate.validator.resourceloading;
8
9 import static org.hibernate.validator.internal.util.CollectionHelper.newHashSet;
10 import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES;
11
12 import java.io.IOException;
13 import java.lang.invoke.MethodHandles;
14 import java.lang.reflect.Method;
15 import java.net.URL;
16 import java.security.AccessController;
17 import java.security.PrivilegedAction;
18 import java.util.Collections;
19 import java.util.Enumeration;
20 import java.util.Locale;
21 import java.util.Map;
22 import java.util.MissingResourceException;
23 import java.util.Properties;
24 import java.util.ResourceBundle;
25 import java.util.Set;
26
27 import org.hibernate.validator.Incubating;
28 import org.hibernate.validator.internal.util.CollectionHelper;
29 import org.hibernate.validator.internal.util.Contracts;
30 import org.hibernate.validator.internal.util.logging.Log;
31 import org.hibernate.validator.internal.util.logging.LoggerFactory;
32 import org.hibernate.validator.internal.util.privilegedactions.GetClassLoader;
33 import org.hibernate.validator.internal.util.privilegedactions.GetMethod;
34 import org.hibernate.validator.internal.util.privilegedactions.GetResources;
35 import org.hibernate.validator.internal.util.stereotypes.Immutable;
36 import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator;
37
38
48 public class PlatformResourceBundleLocator implements ResourceBundleLocator {
49
50 private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
51 private static final boolean RESOURCE_BUNDLE_CONTROL_INSTANTIABLE = determineAvailabilityOfResourceBundleControl();
52
53 private final String bundleName;
54 private final ClassLoader classLoader;
55 private final boolean aggregate;
56
57 @Immutable
58 private final Map<Locale, ResourceBundle> preloadedResourceBundles;
59
60
65 public PlatformResourceBundleLocator(String bundleName) {
66 this( bundleName, Collections.emptySet() );
67 }
68
69
79 public PlatformResourceBundleLocator(String bundleName, ClassLoader classLoader) {
80 this( bundleName, Collections.emptySet(), classLoader );
81 }
82
83
94 public PlatformResourceBundleLocator(String bundleName, ClassLoader classLoader, boolean aggregate) {
95 this( bundleName, Collections.emptySet(), classLoader, aggregate );
96 }
97
98
106 @Incubating
107 public PlatformResourceBundleLocator(String bundleName, Set<Locale> localesToInitialize) {
108 this( bundleName, localesToInitialize, null );
109 }
110
111
122 @Incubating
123 public PlatformResourceBundleLocator(String bundleName, Set<Locale> localesToInitialize, ClassLoader classLoader) {
124 this( bundleName, localesToInitialize, classLoader, false );
125 }
126
127
139 @Incubating
140 public PlatformResourceBundleLocator(String bundleName,
141 Set<Locale> localesToInitialize,
142 ClassLoader classLoader,
143 boolean aggregate) {
144 Contracts.assertNotNull( bundleName, "bundleName" );
145
146 this.bundleName = bundleName;
147 this.classLoader = classLoader;
148
149 this.aggregate = aggregate && RESOURCE_BUNDLE_CONTROL_INSTANTIABLE;
150
151 if ( !localesToInitialize.isEmpty() ) {
152 Map<Locale, ResourceBundle> tmpPreloadedResourceBundles = CollectionHelper.newHashMap( localesToInitialize.size() );
153 for ( Locale localeToPreload : localesToInitialize ) {
154 tmpPreloadedResourceBundles.put( localeToPreload, doGetResourceBundle( localeToPreload ) );
155 }
156 this.preloadedResourceBundles = CollectionHelper.toImmutableMap( tmpPreloadedResourceBundles );
157 }
158 else {
159 this.preloadedResourceBundles = Collections.emptyMap();
160 }
161 }
162
163
171 @Override
172 public ResourceBundle getResourceBundle(Locale locale) {
173 if ( !preloadedResourceBundles.isEmpty() ) {
174
175 if ( preloadedResourceBundles.containsKey( locale ) ) {
176 return preloadedResourceBundles.get( locale );
177 }
178 else {
179 throw LOG.uninitializedLocale( locale );
180 }
181 }
182
183 return doGetResourceBundle( locale );
184 }
185
186 private ResourceBundle doGetResourceBundle(Locale locale) {
187 ResourceBundle rb = null;
188
189 if ( classLoader != null ) {
190 rb = loadBundle(
191 classLoader, locale, bundleName
192 + " not found by user-provided classloader"
193 );
194 }
195
196 if ( rb == null ) {
197 ClassLoader classLoader = run( GetClassLoader.fromContext() );
198 if ( classLoader != null ) {
199 rb = loadBundle(
200 classLoader, locale, bundleName
201 + " not found by thread context classloader"
202 );
203 }
204 }
205
206 if ( rb == null ) {
207 ClassLoader classLoader = run( GetClassLoader.fromClass( PlatformResourceBundleLocator.class ) );
208 rb = loadBundle(
209 classLoader, locale, bundleName
210 + " not found by validator classloader"
211 );
212 }
213 if ( rb != null ) {
214 LOG.debugf( "%s found.", bundleName );
215 }
216 else {
217 LOG.debugf( "%s not found.", bundleName );
218 }
219 return rb;
220 }
221
222 private ResourceBundle loadBundle(ClassLoader classLoader, Locale locale, String message) {
223 ResourceBundle rb = null;
224 try {
225 if ( aggregate ) {
226 rb = ResourceBundle.getBundle(
227 bundleName,
228 locale,
229 classLoader,
230 AggregateResourceBundle.CONTROL
231 );
232 }
233 else {
234 rb = ResourceBundle.getBundle(
235 bundleName,
236 locale,
237 classLoader
238 );
239 }
240 }
241 catch (MissingResourceException e) {
242 LOG.trace( message );
243 }
244 return rb;
245 }
246
247
253 private static <T> T run(PrivilegedAction<T> action) {
254 return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run();
255 }
256
257
272 private static boolean determineAvailabilityOfResourceBundleControl() {
273 try {
274 ResourceBundle.Control dummyControl = AggregateResourceBundle.CONTROL;
275
276 if ( dummyControl == null ) {
277 return false;
278 }
279
280 Method getModule = run( GetMethod.action( Class.class, "getModule" ) );
281
282 if ( getModule == null ) {
283 return true;
284 }
285
286
287 Object module = getModule.invoke( PlatformResourceBundleLocator.class );
288 Method isNamedMethod = run( GetMethod.action( module.getClass(), "isNamed" ) );
289 boolean isNamed = (Boolean) isNamedMethod.invoke( module );
290
291 return !isNamed;
292 }
293 catch (Throwable e) {
294 LOG.info( MESSAGES.unableToUseResourceBundleAggregation() );
295 return false;
296 }
297 }
298
299
303 private static class AggregateResourceBundle extends ResourceBundle {
304
305 protected static final Control CONTROL = new AggregateResourceBundleControl();
306 private final Properties properties;
307
308 protected AggregateResourceBundle(Properties properties) {
309 this.properties = properties;
310 }
311
312 @Override
313 protected Object handleGetObject(String key) {
314 return properties.get( key );
315 }
316
317 @Override
318 public Enumeration<String> getKeys() {
319 Set<String> keySet = newHashSet();
320 keySet.addAll( properties.stringPropertyNames() );
321 if ( parent != null ) {
322 keySet.addAll( Collections.list( parent.getKeys() ) );
323 }
324 return Collections.enumeration( keySet );
325 }
326 }
327
328 private static class AggregateResourceBundleControl extends ResourceBundle.Control {
329 @Override
330 public ResourceBundle newBundle(
331 String baseName,
332 Locale locale,
333 String format,
334 ClassLoader loader,
335 boolean reload)
336 throws IllegalAccessException, InstantiationException, IOException {
337
338 if ( !"java.properties".equals( format ) ) {
339 return super.newBundle( baseName, locale, format, loader, reload );
340 }
341
342 String resourceName = toBundleName( baseName, locale ) + ".properties";
343 Properties properties = load( resourceName, loader );
344 return properties.size() == 0 ? null : new AggregateResourceBundle( properties );
345 }
346
347 private Properties load(String resourceName, ClassLoader loader) throws IOException {
348 Properties aggregatedProperties = new Properties();
349 Enumeration<URL> urls = run( GetResources.action( loader, resourceName ) );
350 while ( urls.hasMoreElements() ) {
351 URL url = urls.nextElement();
352 Properties properties = new Properties();
353 properties.load( url.openStream() );
354 aggregatedProperties.putAll( properties );
355 }
356 return aggregatedProperties;
357 }
358 }
359 }
360