All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jvnet.hk2.config.MessageInterpolatorImpl Maven / Gradle / Ivy

There is a newer version: 7.2024.1.Alpha1
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2007-2017 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://oss.oracle.com/licenses/CDDL+GPL-1.1
 * or LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
// Portions Copyright [2019-2021] Payara Foundation and/or affiliates

package org.jvnet.hk2.config;

import jakarta.validation.MessageInterpolator;
import jakarta.validation.Payload;
import jakarta.validation.metadata.ConstraintDescriptor;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/*
 * Custom MessageInterpolatorImpl for HK2
 *
 * This message interpolator is different from the default one in the following
 * ways:
 *
 * 1. It uses a class specified in the "payload" argument to the annotation to
 * find the class loader to find the resource bundle for messages. This allows
 * classes that are in OSGi modules to specify a resource bundle for bean
 * validation messages.
 *
 * 2. The "LocalStrings" resource bundle within the same package as the payload
 * class is used to search for messages.  If a message is not found, the message
 * is obtained from the default bean validation resource bundle.
 *
 * This class borrows heavily from the RI implementation of the ResourcBundleMessageInterpolator
 * authored by Emmanuel Bernard, Hardy Ferentschik, and Gunnar Morling.
 */
public class MessageInterpolatorImpl implements MessageInterpolator {

    /**
     * The name of the default message bundle.
     */
    public static final String DEFAULT_VALIDATION_MESSAGES = "org.hibernate.validator.ValidationMessages";
    /**
     * The name of the user-provided message bundle as defined in the specification.
     */
    public static final String USER_VALIDATION_MESSAGES = "ValidationMessages";
    /**
     * Regular expression used to do message interpolation.
     */
    private static final Pattern MESSAGE_PARAMETER_PATTERN = Pattern.compile("(\\{[^\\}]+?\\})");
    /**
     * The default locale for the current user.
     */
    private final Locale defaultLocale = Locale.getDefault();
    /**
     * Step 1-3 of message interpolation can be cached. We do this in this map.
     */
    private final Map resolvedMessages = new WeakHashMap();
    /**
     * Flag indicating whether this interpolator should chance some of the interpolation steps.
     */
    private static final boolean cacheMessages = true;

    public MessageInterpolatorImpl() { }

    @Override
    public String interpolate(String message, Context context) {
        // probably no need for caching, but it could be done by parameters since the map
        // is immutable and uniquely built per Validation definition, the comparison has to be based on == and not equals though
        return interpolate(message, context, defaultLocale);
    }

    /**
     * Runs the message interpolation according to algorithm specified in JSR 303.
     * 
* Note: *
* Look-ups in user bundles is recursive whereas look-ups in default bundle are not! * * @param message the message to interpolate * @param context the context of the annotation for which to interpolate this message * @param locale the {@code Locale} to use for the resource bundle. * * @return the interpolated message. */ @Override public String interpolate(String message, Context context, Locale locale) { Map annotationParameters = context.getConstraintDescriptor().getAttributes(); LocalisedMessage localisedMessage = new LocalisedMessage(message, locale); String resolvedMessage = null; if (cacheMessages) { resolvedMessage = resolvedMessages.get(localisedMessage); } // if the message is not already in the cache we have to run step 1-3 of the message resolution if (resolvedMessage == null) { ResourceBundle userResourceBundle = new ContextResourceBundle(context, locale); ClassLoader cl; if (System.getSecurityManager()!=null) { cl = AccessController.doPrivileged(new PrivilegedAction() { @Override public ClassLoader run() { return MessageInterpolator.class.getClassLoader(); } }); } else { cl = MessageInterpolator.class.getClassLoader(); } ResourceBundle defaultResourceBundle = null; try { defaultResourceBundle = ResourceBundle.getBundle(DEFAULT_VALIDATION_MESSAGES, locale, cl); } catch (MissingResourceException mre) { // defaultResourceBundle is already null } String userBundleResolvedMessage; resolvedMessage = message; boolean evaluatedDefaultBundleOnce = false; do { // search the user bundle recursive (step1) userBundleResolvedMessage = replaceVariables( resolvedMessage, userResourceBundle, locale, true); // exit condition - we have at least tried to validate against the default bundle and there was no // further replacements if (evaluatedDefaultBundleOnce && !hasReplacementTakenPlace(userBundleResolvedMessage, resolvedMessage)) { break; } // search the default bundle non recursive (step2) resolvedMessage = replaceVariables(userBundleResolvedMessage, defaultResourceBundle, locale, false); evaluatedDefaultBundleOnce = true; if (cacheMessages) { resolvedMessages.put(localisedMessage, resolvedMessage); } } while (true); } // resolve annotation attributes (step 4) resolvedMessage = replaceAnnotationAttributes(resolvedMessage, annotationParameters); // last but not least we have to take care of escaped literals resolvedMessage = resolvedMessage.replace("\\{", "{"); resolvedMessage = resolvedMessage.replace("\\}", "}"); resolvedMessage = resolvedMessage.replace("\\\\", "\\"); return resolvedMessage; } private boolean hasReplacementTakenPlace(String origMessage, String newMessage) { return !origMessage.equals(newMessage); } private String replaceVariables(String message, ResourceBundle bundle, Locale locale, boolean recurse) { Matcher matcher = MESSAGE_PARAMETER_PATTERN.matcher(message); StringBuffer sb = new StringBuffer(); String resolvedParameterValue; while (matcher.find()) { String parameter = matcher.group(1); resolvedParameterValue = resolveParameter( parameter, bundle, locale, recurse); matcher.appendReplacement(sb, escapeMetaCharacters(resolvedParameterValue)); } matcher.appendTail(sb); return sb.toString(); } private String replaceAnnotationAttributes(String message, Map annotationParameters) { Matcher matcher = MESSAGE_PARAMETER_PATTERN.matcher(message); StringBuffer sb = new StringBuffer(); while (matcher.find()) { String resolvedParameterValue; String parameter = matcher.group(1); Object variable = annotationParameters.get(removeCurlyBrace(parameter)); if (variable != null) { resolvedParameterValue = escapeMetaCharacters(variable.toString()); } else { resolvedParameterValue = parameter; } matcher.appendReplacement(sb, resolvedParameterValue); } matcher.appendTail(sb); return sb.toString(); } private String resolveParameter(String parameterName, ResourceBundle bundle, Locale locale, boolean recurse) { String parameterValue; try { if (bundle != null) { parameterValue = bundle.getString(removeCurlyBrace(parameterName)); if (recurse) { parameterValue = replaceVariables(parameterValue, bundle, locale, recurse); } } else { parameterValue = parameterName; } } catch (MissingResourceException e) { // return parameter itself parameterValue = parameterName; } return parameterValue; } private String removeCurlyBrace(String parameter) { return parameter.substring(1, parameter.length() - 1); } /** * @param s The string in which to replace the meta characters '$' and '\'. * * @return A string where meta characters relevant for {@link Matcher#appendReplacement} are escaped. */ private String escapeMetaCharacters(String s) { String escapedString = s.replace("\\", "\\\\"); escapedString = escapedString.replace("$", "\\$"); return escapedString; } /* * A resource bundle that takes strings from the annotation context. This class * looks for the "LocalStrings" resource bundle in the same package as the * class that is specified in the payload of the annotation. If a String * cannot be found there, then it looks in the user resource bundle as defined * in JSR-303. */ private static class ContextResourceBundle extends ResourceBundle { ResourceBundle contextBundle; ResourceBundle userBundle; ContextResourceBundle(Context context, Locale locale) { ConstraintDescriptor descriptor = context.getConstraintDescriptor(); Set> payload = descriptor.getPayload(); // Load the LogStrings.properties for the argument Locale, from the ClassLoader // of the payload. if (!payload.isEmpty()) { final Class payloadClass = payload.iterator().next(); String baseName = payloadClass.getPackage().getName() + ".LocalStrings"; ClassLoader cl; if (System.getSecurityManager()!=null) { cl = AccessController.doPrivileged(new PrivilegedAction() { @Override public ClassLoader run() { return payloadClass.getClassLoader(); } }); } else { cl = payloadClass.getClassLoader(); } try { contextBundle = ResourceBundle.getBundle(baseName, locale, cl); } catch (MissingResourceException mre) { contextBundle = null; } } try { ClassLoader cl = System.getSecurityManager()==null?Thread.currentThread().getContextClassLoader(): AccessController.doPrivileged(new PrivilegedAction() { @Override public ClassLoader run() { return Thread.currentThread().getContextClassLoader(); } }); userBundle = ResourceBundle.getBundle(USER_VALIDATION_MESSAGES, locale, cl); } catch (MissingResourceException mre) { userBundle = null; } if (userBundle != null) { setParent(userBundle); } } @Override protected Object handleGetObject(String key) { if (contextBundle != null) { return contextBundle.getObject(key); } return null; } @Override public Enumeration getKeys() { Set keys = new TreeSet(); if (contextBundle != null) { keys.addAll(Collections.list(contextBundle.getKeys())); } if (userBundle != null) { keys.addAll(Collections.list(userBundle.getKeys())); } return Collections.enumeration(keys); } } private static class LocalisedMessage { private final String message; private final Locale locale; LocalisedMessage(String message, Locale locale) { this.message = message; this.locale = locale; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } LocalisedMessage that = (LocalisedMessage) o; if (locale != null ? !locale.equals(that.locale) : that.locale != null) { return false; } if (message != null ? !message.equals(that.message) : that.message != null) { return false; } return true; } @Override public int hashCode() { int result = message != null ? message.hashCode() : 0; result = 31 * result + (locale != null ? locale.hashCode() : 0); return result; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy