/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.isis.applib.services.exceprecog;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Predicate;
import com.google.common.base.Throwables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.isis.applib.annotation.Programmatic;
import org.apache.isis.applib.services.i18n.TranslatableString;
import org.apache.isis.applib.services.i18n.TranslationService;
/**
* Abstract implementation of {@link ExceptionRecognizer} that looks
* exceptions meeting the {@link Predicate} supplied in the constructor
* and, if found anywhere in the {@link Throwables#getCausalChain(Throwable) causal chain},
* then returns a non-null message indicating that the exception has been recognized.
*
* <p>
* If a messaging-parsing {@link Function} is provided through the constructor,
* then the message can be altered. Otherwise the exception's {@link Throwable#getMessage() message} is returned as-is.
*/
public abstract class ExceptionRecognizerAbstract implements ExceptionRecognizer2 {
public static final Logger LOG = LoggerFactory.getLogger(ExceptionRecognizerAbstract.class);
/**
* Normally recognized exceptions are not logged (because they are expected and handled).
*
* <p>
* This key is primarily for diagnostic purposes, to log the exception regardless.
*/
private static final String KEY_LOG_RECOGNIZED_EXCEPTIONS = "isis.services.exceprecog.logRecognizedExceptions";
/**
* Convenience for subclass implementations that always return a fixed message.
*/
protected static Function<String, String> constant(final String message) {
return new Function<String, String>() {
@Override
public String apply(String input) {
return message;
}
};
}
/**
* Convenience for subclass implementations that always prefixes the exception message
* with the supplied text
*/
protected static Function<String, String> prefix(final String prefix) {
return new Function<String, String>() {
@Override
public String apply(String input) {
return prefix + ": " + input;
}
};
}
// //////////////////////////////////////
private final Category category;
private final Predicate<Throwable> predicate;
private final Function<String,String> messageParser;
private boolean logRecognizedExceptions;
// //////////////////////////////////////
public ExceptionRecognizerAbstract(final Category category, Predicate<Throwable> predicate, final Function<String,String> messageParser) {
this.category = category;
this.predicate = predicate;
this.messageParser = messageParser != null? messageParser: Functions.<String>identity();
}
public ExceptionRecognizerAbstract(Predicate<Throwable> predicate, final Function<String,String> messageParser) {
this(Category.OTHER, predicate, messageParser);
}
public ExceptionRecognizerAbstract(Category category, Predicate<Throwable> predicate) {
this(category, predicate, null);
}
public ExceptionRecognizerAbstract(Predicate<Throwable> predicate) {
this(Category.OTHER, predicate);
}
@PostConstruct
public void init(Map<String, String> properties) {
final String prop = properties.get(KEY_LOG_RECOGNIZED_EXCEPTIONS);
this.logRecognizedExceptions = Boolean.parseBoolean(prop);
}
@PreDestroy
public void shutdown() {
}
// //////////////////////////////////////
@Programmatic
public String recognize(Throwable ex) {
List<Throwable> causalChain = Throwables.getCausalChain(ex);
for (Throwable throwable : causalChain) {
if(predicate.apply(throwable)) {
if(logRecognizedExceptions) {
LOG.info("Recognized exception, stacktrace : ", throwable);
}
if(ex instanceof TranslatableException) {
final TranslatableException translatableException = (TranslatableException) ex;
final TranslatableString translatableMessage = translatableException.getTranslatableMessage();
final String translationContext = translatableException.getTranslationContext();
if(translatableMessage != null && translationContext != null) {
return translatableMessage.translate(translationService, translationContext);
}
}
final Throwable rootCause = Throwables.getRootCause(throwable);
final String rootCauseMessage = rootCause.getMessage();
final String parsedMessage = messageParser.apply(rootCauseMessage);
return parsedMessage;
}
}
return null;
}
@Programmatic
@Override
public Recognition recognize2(Throwable ex) {
return Recognition.of(category, recognize(ex));
}
@javax.inject.Inject
protected TranslationService translationService;
}