Package org.hibernate.validator.messageinterpolation

Source Code of org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator

/*
* JBoss, Home of Professional Open Source
* Copyright 2010, Red Hat, Inc. and/or its affiliates, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.hibernate.validator.messageinterpolation;

import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import javax.validation.MessageInterpolator;
import javax.xml.bind.ValidationException;

import org.hibernate.validator.internal.engine.messageinterpolation.InterpolationTerm;
import org.hibernate.validator.internal.engine.messageinterpolation.InterpolationTermType;
import org.hibernate.validator.internal.engine.messageinterpolation.LocalizedMessage;
import org.hibernate.validator.internal.engine.messageinterpolation.parser.MessageDescriptorFormatException;
import org.hibernate.validator.internal.engine.messageinterpolation.parser.Token;
import org.hibernate.validator.internal.engine.messageinterpolation.parser.TokenCollector;
import org.hibernate.validator.internal.engine.messageinterpolation.parser.TokenIterator;
import org.hibernate.validator.internal.util.ConcurrentReferenceHashMap;
import org.hibernate.validator.internal.util.logging.Log;
import org.hibernate.validator.internal.util.logging.LoggerFactory;
import org.hibernate.validator.resourceloading.PlatformResourceBundleLocator;
import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator;

import static org.hibernate.validator.internal.util.ConcurrentReferenceHashMap.ReferenceType.SOFT;

/**
* Resource bundle backed message interpolator.
*
* @author Emmanuel Bernard
* @author Hardy Ferentschik
* @author Gunnar Morling
* @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI
*/
public class ResourceBundleMessageInterpolator implements MessageInterpolator {
  private static final Log log = LoggerFactory.make();

  /**
   * The default initial capacity for this cache.
   */
  private static final int DEFAULT_INITIAL_CAPACITY = 100;

  /**
   * The default load factor for this cache.
   */
  private static final float DEFAULT_LOAD_FACTOR = 0.75f;

  /**
   * The default concurrency level for this cache.
   */
  private static final int DEFAULT_CONCURRENCY_LEVEL = 16;

  /**
   * The name of the default message bundle.
   */
  private 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";

  /**
   * The default locale in the current JVM.
   */
  private final Locale defaultLocale;

  /**
   * Loads user-specified resource bundles.
   */
  private final ResourceBundleLocator userResourceBundleLocator;

  /**
   * Loads built-in resource bundles.
   */
  private final ResourceBundleLocator defaultResourceBundleLocator;

  /**
   * Step 1-3 of message interpolation can be cached. We do this in this map.
   */
  private final ConcurrentReferenceHashMap<LocalizedMessage, String> resolvedMessages;

  /**
   * Step 4 of message interpolation replaces message parameters. The token list for message parameters is cached in this map.
   */
  private final ConcurrentReferenceHashMap<String, List<Token>> tokenizedParameterMessages;

  /**
   * Step 5 of message interpolation replaces EL expressions. The token list for EL expressions is cached in this map.
   */
  private final ConcurrentReferenceHashMap<String, List<Token>> tokenizedELMessages;

  /**
   * Flag indicating whether this interpolator should cache some of the interpolation steps.
   */
  private final boolean cachingEnabled;

  public ResourceBundleMessageInterpolator() {
    this( null );
  }

  public ResourceBundleMessageInterpolator(ResourceBundleLocator userResourceBundleLocator) {
    this( userResourceBundleLocator, true );
  }

  public ResourceBundleMessageInterpolator(ResourceBundleLocator userResourceBundleLocator, boolean cacheMessages) {
    defaultLocale = Locale.getDefault();

    if ( userResourceBundleLocator == null ) {
      this.userResourceBundleLocator = new PlatformResourceBundleLocator( USER_VALIDATION_MESSAGES );
    }
    else {
      this.userResourceBundleLocator = userResourceBundleLocator;
    }

    this.defaultResourceBundleLocator = new PlatformResourceBundleLocator( DEFAULT_VALIDATION_MESSAGES );
    this.cachingEnabled = cacheMessages;

    if ( cachingEnabled ) {
      this.resolvedMessages = new ConcurrentReferenceHashMap<LocalizedMessage, String>(
          DEFAULT_INITIAL_CAPACITY,
          DEFAULT_LOAD_FACTOR,
          DEFAULT_CONCURRENCY_LEVEL,
          SOFT,
          SOFT,
          EnumSet.noneOf( ConcurrentReferenceHashMap.Option.class )
      );
      this.tokenizedParameterMessages = new ConcurrentReferenceHashMap<String, List<Token>>(
          DEFAULT_INITIAL_CAPACITY,
          DEFAULT_LOAD_FACTOR,
          DEFAULT_CONCURRENCY_LEVEL,
          SOFT,
          SOFT,
          EnumSet.noneOf( ConcurrentReferenceHashMap.Option.class )
      );
      this.tokenizedELMessages = new ConcurrentReferenceHashMap<String, List<Token>>(
          DEFAULT_INITIAL_CAPACITY,
          DEFAULT_LOAD_FACTOR,
          DEFAULT_CONCURRENCY_LEVEL,
          SOFT,
          SOFT,
          EnumSet.noneOf( ConcurrentReferenceHashMap.Option.class )
      );
    }
    else {
      resolvedMessages = null;
      tokenizedParameterMessages = null;
      tokenizedELMessages = null;
    }

  }

  @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
    String interpolatedMessage = message;
    try {
      interpolatedMessage = interpolateMessage( message, context, defaultLocale );
    }
    catch ( MessageDescriptorFormatException e ) {
      log.warn( e.getMessage() );
    }
    return interpolatedMessage;
  }

  @Override
  public String interpolate(String message, Context context, Locale locale) {
    String interpolatedMessage = message;
    try {
      interpolatedMessage = interpolateMessage( message, context, locale );
    }
    catch ( ValidationException e ) {
      log.warn( e.getMessage() );
    }
    return interpolatedMessage;
  }

  /**
   * Runs the message interpolation according to algorithm specified in the Bean Validation specification.
   * <br/>
   * Note:
   * <br/>
   * 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 for this interpolation
   * @param locale the {@code Locale} to use for the resource bundle.
   *
   * @return the interpolated message.
   */
  private String interpolateMessage(String message, Context context, Locale locale)
      throws MessageDescriptorFormatException {
    LocalizedMessage localisedMessage = new LocalizedMessage( message, locale );
    String resolvedMessage = null;

    if ( cachingEnabled ) {
      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 = userResourceBundleLocator
          .getResourceBundle( locale );
      ResourceBundle defaultResourceBundle = defaultResourceBundleLocator
          .getResourceBundle( locale );

      String userBundleResolvedMessage;
      resolvedMessage = message;
      boolean evaluatedDefaultBundleOnce = false;
      do {
        // search the user bundle recursive (step1)
        userBundleResolvedMessage = interpolateBundleMessage(
            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 = interpolateBundleMessage(
            userBundleResolvedMessage,
            defaultResourceBundle,
            locale,
            false
        );
        evaluatedDefaultBundleOnce = true;
      } while ( true );
    }

    // cache resolved message
    if ( cachingEnabled ) {
      String cachedResolvedMessage = resolvedMessages.putIfAbsent( localisedMessage, resolvedMessage );
      if ( cachedResolvedMessage != null ) {
        resolvedMessage = cachedResolvedMessage;
      }
    }

    // resolve parameter expressions (step 4)
    List<Token> tokens = null;
    if ( cachingEnabled ) {
      tokens = tokenizedParameterMessages.get( resolvedMessage );
    }
    if ( tokens == null ) {
      TokenCollector tokenCollector = new TokenCollector( resolvedMessage, InterpolationTermType.PARAMETER );
      tokens = tokenCollector.getTokenList();

      if ( cachingEnabled ) {
        tokenizedParameterMessages.putIfAbsent( resolvedMessage, tokens );
      }
    }
    resolvedMessage = interpolateExpression(
        new TokenIterator( tokens ),
        context,
        locale
    );

    // resolve EL expressions (step 5)
    tokens = null;
    if ( cachingEnabled ) {
      tokens = tokenizedELMessages.get( resolvedMessage );
    }
    if ( tokens == null ) {
      TokenCollector tokenCollector = new TokenCollector( resolvedMessage, InterpolationTermType.EL );
      tokens = tokenCollector.getTokenList();

      if ( cachingEnabled ) {
        tokenizedELMessages.putIfAbsent( resolvedMessage, tokens );
      }
    }
    resolvedMessage = interpolateExpression(
        new TokenIterator( tokens ),
        context,
        locale
    );

    // last but not least we have to take care of escaped literals
    resolvedMessage = replaceEscapedLiterals( resolvedMessage );

    return resolvedMessage;
  }

  private String replaceEscapedLiterals(String resolvedMessage) {
    resolvedMessage = resolvedMessage.replace( "\\{", "{" );
    resolvedMessage = resolvedMessage.replace( "\\}", "}" );
    resolvedMessage = resolvedMessage.replace( "\\\\", "\\" );
    resolvedMessage = resolvedMessage.replace( "\\$", "$" );
    return resolvedMessage;
  }

  private boolean hasReplacementTakenPlace(String origMessage, String newMessage) {
    return !origMessage.equals( newMessage );
  }

  private String interpolateBundleMessage(String message, ResourceBundle bundle, Locale locale, boolean recursive)
      throws MessageDescriptorFormatException {
    TokenCollector tokenCollector = new TokenCollector( message, InterpolationTermType.PARAMETER );
    TokenIterator tokenIterator = new TokenIterator( tokenCollector.getTokenList() );
    while ( tokenIterator.hasMoreInterpolationTerms() ) {
      String term = tokenIterator.nextInterpolationTerm();
      String resolvedParameterValue = resolveParameter(
          term, bundle, locale, recursive
      );
      tokenIterator.replaceCurrentInterpolationTerm( resolvedParameterValue );
    }
    return tokenIterator.getInterpolatedMessage();
  }

  private String interpolateExpression(TokenIterator tokenIterator, Context context, Locale locale)
      throws MessageDescriptorFormatException {
    while ( tokenIterator.hasMoreInterpolationTerms() ) {
      String term = tokenIterator.nextInterpolationTerm();

      InterpolationTerm expression = new InterpolationTerm( term, locale );
      String resolvedExpression = expression.interpolate( context );
      tokenIterator.replaceCurrentInterpolationTerm( resolvedExpression );
    }
    return tokenIterator.getInterpolatedMessage();
  }

  private String resolveParameter(String parameterName, ResourceBundle bundle, Locale locale, boolean recursive)
      throws MessageDescriptorFormatException {
    String parameterValue;
    try {
      if ( bundle != null ) {
        parameterValue = bundle.getString( removeCurlyBraces( parameterName ) );
        if ( recursive ) {
          parameterValue = interpolateBundleMessage( parameterValue, bundle, locale, recursive );
        }
      }
      else {
        parameterValue = parameterName;
      }
    }
    catch ( MissingResourceException e ) {
      // return parameter itself
      parameterValue = parameterName;
    }
    return parameterValue;
  }

  private String removeCurlyBraces(String parameter) {
    return parameter.substring( 1, parameter.length() - 1 );
  }
}
TOP

Related Classes of org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.