1 /*
2  * Hibernate Validator, declare and validate application constraints
3  *
4  * License: Apache License, Version 2.0
5  * See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
6  */

7 package org.hibernate.validator.messageinterpolation;
8
9 import java.lang.invoke.MethodHandles;
10 import java.security.AccessController;
11 import java.security.PrivilegedAction;
12 import java.util.Collections;
13 import java.util.Locale;
14 import java.util.Set;
15
16 import javax.el.ELManager;
17 import javax.el.ExpressionFactory;
18
19 import org.hibernate.validator.Incubating;
20 import org.hibernate.validator.internal.engine.messageinterpolation.DefaultLocaleResolver;
21 import org.hibernate.validator.internal.engine.messageinterpolation.InterpolationTerm;
22 import org.hibernate.validator.internal.util.logging.Log;
23 import org.hibernate.validator.internal.util.logging.LoggerFactory;
24 import org.hibernate.validator.internal.util.privilegedactions.GetClassLoader;
25 import org.hibernate.validator.internal.util.privilegedactions.SetContextClassLoader;
26 import org.hibernate.validator.spi.messageinterpolation.LocaleResolver;
27 import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator;
28
29 /**
30  * Resource bundle backed message interpolator.
31  *
32  * @author Emmanuel Bernard
33  * @author Hardy Ferentschik
34  * @author Gunnar Morling
35  * @author Kevin Pollet &lt;kevin.pollet@serli.com&gt; (C) 2011 SERLI
36  * @author Adam Stawicki
37  * @author Guillaume Smet
38  */

39 public class ResourceBundleMessageInterpolator extends AbstractMessageInterpolator {
40
41     private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
42
43     private final ExpressionFactory expressionFactory;
44
45     public ResourceBundleMessageInterpolator() {
46         this( Collections.emptySet(), Locale.getDefault(), new DefaultLocaleResolver(), false );
47     }
48
49     public ResourceBundleMessageInterpolator(ResourceBundleLocator userResourceBundleLocator) {
50         this( userResourceBundleLocator, Collections.emptySet(), Locale.getDefault(), new DefaultLocaleResolver(), false );
51     }
52
53     public ResourceBundleMessageInterpolator(ResourceBundleLocator userResourceBundleLocator,
54             ResourceBundleLocator contributorResourceBundleLocator) {
55         this( userResourceBundleLocator, contributorResourceBundleLocator, Collections.emptySet(), Locale.getDefault(), new DefaultLocaleResolver(), false );
56     }
57
58     public ResourceBundleMessageInterpolator(ResourceBundleLocator userResourceBundleLocator,
59             ResourceBundleLocator contributorResourceBundleLocator,
60             boolean cachingEnabled) {
61         this( userResourceBundleLocator, contributorResourceBundleLocator, Collections.emptySet(), Locale.getDefault(), new DefaultLocaleResolver(),
62                 false, cachingEnabled );
63     }
64
65     public ResourceBundleMessageInterpolator(ResourceBundleLocator userResourceBundleLocator, boolean cachingEnabled) {
66         this( userResourceBundleLocator, null, Collections.emptySet(), Locale.getDefault(), new DefaultLocaleResolver(), false, cachingEnabled );
67     }
68
69     public ResourceBundleMessageInterpolator(ResourceBundleLocator userResourceBundleLocator,
70             boolean cachingEnabled,
71             ExpressionFactory expressionFactory) {
72         this( userResourceBundleLocator, null, Collections.emptySet(), Locale.getDefault(), new DefaultLocaleResolver(), false, cachingEnabled );
73     }
74
75     /**
76      * @since 6.1.1
77      */

78     @Incubating
79     public ResourceBundleMessageInterpolator(Set<Locale> locales, Locale defaultLocale, LocaleResolver localeResolver, boolean preloadResourceBundles) {
80         super( locales, defaultLocale, localeResolver, preloadResourceBundles );
81         this.expressionFactory = buildExpressionFactory();
82     }
83
84     /**
85      * @since 6.1.1
86      */

87     @Incubating
88     public ResourceBundleMessageInterpolator(ResourceBundleLocator userResourceBundleLocator,
89             Set<Locale> locales,
90             Locale defaultLocale,
91             LocaleResolver localeResolver,
92             boolean preloadResourceBundles) {
93         super( userResourceBundleLocator, locales, defaultLocale, localeResolver, preloadResourceBundles );
94         this.expressionFactory = buildExpressionFactory();
95     }
96
97     /**
98      * @since 6.1.1
99      */

100     @Incubating
101     public ResourceBundleMessageInterpolator(ResourceBundleLocator userResourceBundleLocator,
102             ResourceBundleLocator contributorResourceBundleLocator,
103             Set<Locale> locales,
104             Locale defaultLocale,
105             LocaleResolver localeResolver,
106             boolean preloadResourceBundles) {
107         super( userResourceBundleLocator, contributorResourceBundleLocator, locales, defaultLocale, localeResolver, preloadResourceBundles );
108         this.expressionFactory = buildExpressionFactory();
109     }
110
111     /**
112      * @since 6.1.1
113      */

114     @Incubating
115     public ResourceBundleMessageInterpolator(ResourceBundleLocator userResourceBundleLocator,
116             ResourceBundleLocator contributorResourceBundleLocator,
117             Set<Locale> locales,
118             Locale defaultLocale,
119             LocaleResolver localeResolver,
120             boolean preloadResourceBundles,
121             boolean cachingEnabled) {
122         super( userResourceBundleLocator, contributorResourceBundleLocator, locales, defaultLocale, localeResolver, preloadResourceBundles,
123                 cachingEnabled );
124         this.expressionFactory = buildExpressionFactory();
125     }
126
127     /**
128      * @since 6.1.1
129      */

130     @Incubating
131     public ResourceBundleMessageInterpolator(ResourceBundleLocator userResourceBundleLocator,
132             Set<Locale> locales,
133             Locale defaultLocale,
134             LocaleResolver localeResolver,
135             boolean preloadResourceBundles,
136             boolean cachingEnabled) {
137         super( userResourceBundleLocator, null, locales, defaultLocale, localeResolver, preloadResourceBundles, cachingEnabled );
138         this.expressionFactory = buildExpressionFactory();
139     }
140
141     /**
142      * @since 6.1.1
143      */

144     @Incubating
145     public ResourceBundleMessageInterpolator(ResourceBundleLocator userResourceBundleLocator,
146             Set<Locale> locales,
147             Locale defaultLocale,
148             LocaleResolver localeResolver,
149             boolean preloadResourceBundles,
150             boolean cachingEnabled,
151             ExpressionFactory expressionFactory) {
152         super( userResourceBundleLocator, null, locales, defaultLocale, localeResolver, preloadResourceBundles, cachingEnabled );
153         this.expressionFactory = expressionFactory;
154     }
155
156     @Override
157     public String interpolate(Context context, Locale locale, String term) {
158         InterpolationTerm expression = new InterpolationTerm( term, locale, expressionFactory );
159         return expression.interpolate( context );
160     }
161
162     /**
163      * The javax.el FactoryFinder uses the TCCL to load the {@link ExpressionFactory} implementation so we need to be
164      * extra careful when initializing it.
165      *
166      * @return the {@link ExpressionFactory}
167      */

168     private static ExpressionFactory buildExpressionFactory() {
169         // First, we try to load the instance from the original TCCL.
170         if ( canLoadExpressionFactory() ) {
171             ExpressionFactory expressionFactory = ELManager.getExpressionFactory();
172             LOG.debug( "Loaded expression factory via original TCCL" );
173             return expressionFactory;
174         }
175
176         final ClassLoader originalContextClassLoader = run( GetClassLoader.fromContext() );
177
178         try {
179             // Then we try the Hibernate Validator class loader. In a fully-functional modular environment such as
180             // WildFly or Jigsaw, it is the way to go.
181             run( SetContextClassLoader.action( ResourceBundleMessageInterpolator.class.getClassLoader() ) );
182
183             if ( canLoadExpressionFactory() ) {
184                 ExpressionFactory expressionFactory = ELManager.getExpressionFactory();
185                 LOG.debug( "Loaded expression factory via HV classloader" );
186                 return expressionFactory;
187             }
188
189             // Finally we try the CL of the EL module itself; the EL RI uses the TCCL to load the implementation from
190             // its own module, so this should work
191             run( SetContextClassLoader.action( ELManager.class.getClassLoader() ) );
192             if ( canLoadExpressionFactory() ) {
193                 ExpressionFactory expressionFactory = ELManager.getExpressionFactory();
194                 LOG.debug( "Loaded expression factory via EL classloader" );
195                 return expressionFactory;
196             }
197         }
198         catch (Throwable e) {
199             throw LOG.getUnableToInitializeELExpressionFactoryException( e );
200         }
201         finally {
202             run( SetContextClassLoader.action( originalContextClassLoader ) );
203         }
204
205         // HV-793 - We fail eagerly in case we have no EL dependencies on the classpath
206         throw LOG.getUnableToInitializeELExpressionFactoryException( null );
207     }
208
209     /**
210      * Instead of testing the different class loaders via {@link ELManager}, we directly access the
211      * {@link ExpressionFactory}. This avoids issues with loading the {@code ELUtil} class (used by {@code ELManager})
212      * after a failed attempt.
213      */

214     private static boolean canLoadExpressionFactory() {
215         try {
216             ExpressionFactory.newInstance();
217             return true;
218         }
219         catch (Throwable e) {
220             LOG.debugv( e, "Failed to load expression factory via classloader {0}",
221                     run( GetClassLoader.fromContext() ) );
222             return false;
223         }
224     }
225
226     /**
227      * Runs the given privileged action, using a privileged block if required.
228      * <p>
229      * <b>NOTE:</b> This must never be changed into a publicly available method to avoid execution of arbitrary
230      * privileged actions within HV's protection domain.
231      */

232     private static <T> T run(PrivilegedAction<T> action) {
233         return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run();
234     }
235 }
236