-
Notifications
You must be signed in to change notification settings - Fork 565
/
PlatformResourceBundleLocator.java
270 lines (240 loc) · 8.79 KB
/
PlatformResourceBundleLocator.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
/*
* Hibernate Validator, declare and validate application constraints
*
* License: Apache License, Version 2.0
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package org.hibernate.validator.resourceloading;
import static org.hibernate.validator.internal.util.CollectionHelper.newHashSet;
import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.Set;
import org.hibernate.validator.internal.util.Contracts;
import org.hibernate.validator.internal.util.privilegedactions.GetClassLoader;
import org.hibernate.validator.internal.util.privilegedactions.GetMethod;
import org.hibernate.validator.internal.util.privilegedactions.GetResources;
import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator;
import org.jboss.logging.Logger;
/**
* A resource bundle locator, that loads resource bundles by invoking {@code ResourceBundle.loadBundle(String, Local, ClassLoader)}.
* <p>
* This locator is also able to load all property files of a given name (in case there are multiple with the same
* name on the classpath) and aggregates them into a {@code ResourceBundle}.
*
* @author Hardy Ferentschik
* @author Gunnar Morling
*/
public class PlatformResourceBundleLocator implements ResourceBundleLocator {
private static final Logger log = Logger.getLogger( PlatformResourceBundleLocator.class.getName() );
private static final boolean RESOURCE_BUNDLE_CONTROL_INSTANTIABLE = determineAvailabilityOfResourceBundleControl();
private final String bundleName;
private final ClassLoader classLoader;
private final boolean aggregate;
public PlatformResourceBundleLocator(String bundleName) {
this( bundleName, null );
}
/**
* Creates a new {@link PlatformResourceBundleLocator}.
*
* @param bundleName the name of the bundle to load
* @param classLoader the classloader to be used for loading the bundle. If {@code null}, the current thread context
* classloader and finally Hibernate Validator's own classloader will be used for loading the specified
* bundle.
*
* @since 5.2
*/
public PlatformResourceBundleLocator(String bundleName, ClassLoader classLoader) {
this( bundleName, classLoader, false );
}
/**
* Creates a new {@link PlatformResourceBundleLocator}.
*
* @param bundleName the name of the bundle to load
* @param classLoader the classloader to be used for loading the bundle. If {@code null}, the current thread context
* classloader and finally Hibernate Validator's own classloader will be used for loading the specified
* bundle.
* @param aggregate Whether or not all resource bundles of a given name should be loaded and potentially merged.
*
* @since 5.2
*/
public PlatformResourceBundleLocator(String bundleName, ClassLoader classLoader, boolean aggregate) {
Contracts.assertNotNull( bundleName, "bundleName" );
this.bundleName = bundleName;
this.classLoader = classLoader;
this.aggregate = aggregate && RESOURCE_BUNDLE_CONTROL_INSTANTIABLE;
}
/**
* Search current thread classloader for the resource bundle. If not found,
* search validator (this) classloader.
*
* @param locale The locale of the bundle to load.
*
* @return the resource bundle or {@code null} if none is found.
*/
@Override
public ResourceBundle getResourceBundle(Locale locale) {
ResourceBundle rb = null;
if ( classLoader != null ) {
rb = loadBundle(
classLoader, locale, bundleName
+ " not found by user-provided classloader"
);
}
if ( rb == null ) {
ClassLoader classLoader = run( GetClassLoader.fromContext() );
if ( classLoader != null ) {
rb = loadBundle(
classLoader, locale, bundleName
+ " not found by thread context classloader"
);
}
}
if ( rb == null ) {
ClassLoader classLoader = run( GetClassLoader.fromClass( PlatformResourceBundleLocator.class ) );
rb = loadBundle(
classLoader, locale, bundleName
+ " not found by validator classloader"
);
}
if ( rb != null ) {
log.debugf( "%s found.", bundleName );
}
else {
log.debugf( "%s not found.", bundleName );
}
return rb;
}
private ResourceBundle loadBundle(ClassLoader classLoader, Locale locale, String message) {
ResourceBundle rb = null;
try {
if ( aggregate ) {
rb = ResourceBundle.getBundle(
bundleName,
locale,
classLoader,
AggregateResourceBundle.CONTROL
);
}
else {
rb = ResourceBundle.getBundle(
bundleName,
locale,
classLoader
);
}
}
catch (MissingResourceException e) {
log.trace( message );
}
return rb;
}
/**
* Runs the given privileged action, using a privileged block if required.
* <p>
* <b>NOTE:</b> This must never be changed into a publicly available method to avoid execution of arbitrary
* privileged actions within HV's protection domain.
*/
private static <T> T run(PrivilegedAction<T> action) {
return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run();
}
/**
* Check whether ResourceBundle.Control is available, which is needed for bundle aggregation. If not, we'll skip
* resource aggregation.
* <p>
* It is *not* available
* <ul>
* <li>in the Google App Engine environment</li>
* <li>when running HV as Java 9 named module (which would be the case when adding a module-info descriptor to the
* HV JAR)</li>
* </ul>
*
* @see <a href="http://code.google.com/appengine/docs/java/jrewhitelist.html">GAE JRE whitelist</a>
* @see <a href="https://hibernate.atlassian.net/browse/HV-1023">HV-1023</a>
* @see <a href="http://download.java.net/java/jdk9/docs/api/java/util/ResourceBundle.Control.html>ResourceBundle.Control</a>
*/
private static boolean determineAvailabilityOfResourceBundleControl() {
try {
ResourceBundle.Control dummyControl = AggregateResourceBundle.CONTROL;
if ( dummyControl == null ) {
return false;
}
Method getModule = run( GetMethod.action( Class.class, "getModule" ) );
// not on Java 9
if ( getModule == null ) {
return true;
}
// on Java 9, check whether HV is a named module
Object module = getModule.invoke( PlatformResourceBundleLocator.class );
Method isNamedMethod = run( GetMethod.action( module.getClass(), "isNamed" ) );
boolean isNamed = (Boolean) isNamedMethod.invoke( module );
return !isNamed;
}
catch (Throwable e) {
log.info( MESSAGES.unableToUseResourceBundleAggregation() );
return false;
}
}
/**
* Inspired by <a href="http://stackoverflow.com/questions/4614465/is-it-possible-to-include-resource-bundle-files-within-a-resource-bundle">this</a>
* Stack Overflow question.
*/
private static class AggregateResourceBundle extends ResourceBundle {
protected static final Control CONTROL = new AggregateResourceBundleControl();
private final Properties properties;
protected AggregateResourceBundle(Properties properties) {
this.properties = properties;
}
@Override
protected Object handleGetObject(String key) {
return properties.get( key );
}
@Override
public Enumeration<String> getKeys() {
Set<String> keySet = newHashSet();
keySet.addAll( properties.stringPropertyNames() );
if ( parent != null ) {
keySet.addAll( Collections.list( parent.getKeys() ) );
}
return Collections.enumeration( keySet );
}
}
private static class AggregateResourceBundleControl extends ResourceBundle.Control {
@Override
public ResourceBundle newBundle(
String baseName,
Locale locale,
String format,
ClassLoader loader,
boolean reload)
throws IllegalAccessException, InstantiationException, IOException {
// only *.properties files can be aggregated. Other formats are delegated to the default implementation
if ( !"java.properties".equals( format ) ) {
return super.newBundle( baseName, locale, format, loader, reload );
}
String resourceName = toBundleName( baseName, locale ) + ".properties";
Properties properties = load( resourceName, loader );
return properties.size() == 0 ? null : new AggregateResourceBundle( properties );
}
private Properties load(String resourceName, ClassLoader loader) throws IOException {
Properties aggregatedProperties = new Properties();
Enumeration<URL> urls = run( GetResources.action( loader, resourceName ) );
while ( urls.hasMoreElements() ) {
URL url = urls.nextElement();
Properties properties = new Properties();
properties.load( url.openStream() );
aggregatedProperties.putAll( properties );
}
return aggregatedProperties;
}
}
}