DefaultExceptionTranslator.java

package cn.home1.oss.lib.errorhandle.internal.translator;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Lists.newLinkedList;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static java.util.Collections.unmodifiableList;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

import com.google.common.collect.ImmutableMap;

import cn.home1.oss.lib.errorhandle.api.ExceptionTranslator;

import cn.home1.oss.lib.common.msginterpolate.MessageInterpolator;
import cn.home1.oss.lib.common.msginterpolate.MessageInterpolatorAware;
import cn.home1.oss.lib.common.msginterpolate.NoOpMessageInterpolator;

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.web.context.request.RequestAttributes;

import java.io.Serializable;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;

/**
 * Created by zhanghaolun on 16/8/10.
 */
@Slf4j
public class DefaultExceptionTranslator implements ExceptionTranslator, MessageInterpolatorAware {

  static final String DEFAULT_EXCEPTION_CLASS = "default";
  private static final String FIELD_STATUS = "status";
  private static final String FIELD_TEMPLATE = "template";

  private MessageInterpolator messageInterpolator;
  @Setter
  private Locale locale;
  @Setter
  private Comparator<Location> locationComparator;
  private List<MessageSource> messageSources;

  public void setMessageSources(final List<MessageSource> messageSources) {
    checkArgument(messageSources != null && !messageSources.isEmpty(), "messageSources must not null or empty");
    this.messageSources = unmodifiableList(messageSources);
  }

  public void setMessageInterpolator(final MessageInterpolator messageInterpolator) {
    this.messageInterpolator = messageInterpolator != null ? messageInterpolator : new NoOpMessageInterpolator();
  }

  @Override
  public Optional<Location> find(final Throwable throwable) {
    final Optional<Location> result;

    if (throwable != null) {
      final List<Location> locations = newLinkedList();

      Integer sourceOrder = 0;
      for (final MessageSource source : this.messageSources) {

        Boolean found = FALSE;
        Integer level = 0;
        for (Class type = throwable.getClass(); type != Throwable.class; type = type.getSuperclass()) {
          final Optional<String> template = this.template(source, type.getName());
          final Optional<Integer> status = this.status(source, type.getName());
          if (template.isPresent() && status.isPresent()) {
            found = TRUE;
            locations.add(new Location(source, sourceOrder, type.getName(), level));
            break;
          }
          level++;
        }

        if (!found) {
          final Optional<String> defaultTemplate = this.defaultTemplate(source);
          final Optional<Integer> defaultStatus = this.defaultStatus(source);
          if (defaultTemplate.isPresent() && defaultStatus.isPresent()) {
            locations.add(new Location(source, sourceOrder, DEFAULT_EXCEPTION_CLASS, Integer.MAX_VALUE));
          }
        }

        sourceOrder++;
      }

      locations.sort(this.locationComparator);
      result = locations.isEmpty() ? Optional.empty() : Optional.of(locations.get(0));
    } else {
      result = Optional.empty();
    }


    return result;
  }

  public Optional<String> defaultTemplate(final MessageSource messageSource) {
    return this.template(messageSource, DEFAULT_EXCEPTION_CLASS);
  }

  public Optional<Integer> defaultStatus(final MessageSource messageSource) {
    return this.status(messageSource, DEFAULT_EXCEPTION_CLASS);
  }

  @Override
  public Optional<String> localizedMessage( //
    final String template, //
    final RequestAttributes request, //
    final Throwable throwable, //
    final Map<String, Serializable> contextVariables //
  ) {
    final Optional<String> result;
    if (template != null) {
      final Map<String, Object> variables = ImmutableMap.<String, Object>builder() //
        .putAll(contextVariables != null ? contextVariables : ImmutableMap.of()) //
        .put("req", request) //
        .put("ex", throwable) //
        .build();
      final String localizedMessage = this.messageInterpolator.interpolate(template, variables);
      result = Optional.ofNullable(localizedMessage);
    } else {
      result = Optional.empty();
    }
    return result;
  }

  @SuppressWarnings("rawtypes")
  @Override
  public Optional<Integer> status(final Location location) {
    return location != null ? this.status(location.getSource(), location.getKey()) : Optional.empty();
  }

  protected Optional<Integer> status(final MessageSource messageSource, final String type) {
    final String status = this.getValue(messageSource, type, FIELD_STATUS);
    return isNotBlank(status) ? Optional.of(Integer.parseInt(status)) : Optional.empty();
  }

  @SuppressWarnings("rawtypes")
  @Override
  public Optional<String> template(final Location location) {
    return location != null ? this.template(location.getSource(), location.getKey()) : Optional.empty();
  }

  protected Optional<String> template(final MessageSource messageSource, final String type) {
    final String template = this.getValue(messageSource, type, FIELD_TEMPLATE);
    return isNotBlank(template) ? Optional.of(template) : Optional.empty();
  }

  protected String getValue(final MessageSource messageSource, final String type, final String field) {
    final Locale locale = this.locale != null ? this.locale : LocaleContextHolder.getLocale();
    return getValue(messageSource, type, field, locale);
  }

  protected static String getValue( //
    final MessageSource messageSource, //
    final String type, //
    final String field, //
    final Locale locale //
  ) {
    final String key = type + "." + field;
    return messageSource.getMessage(key, null, null, locale);
  }
}