de.bund.bva.pliscommon.serviceapi.core.serviceimpl.ServiceExceptionFassade.java Source code

Java tutorial

Introduction

Here is the source code for de.bund.bva.pliscommon.serviceapi.core.serviceimpl.ServiceExceptionFassade.java

Source

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 * The Federal Office of Administration (Bundesverwaltungsamt, BVA)
 * 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 de.bund.bva.pliscommon.serviceapi.core.serviceimpl;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Required;

import de.bund.bva.isyfact.logging.IsyLogger;
import de.bund.bva.isyfact.logging.IsyLoggerFactory;
import de.bund.bva.isyfact.logging.LogKategorie;
import de.bund.bva.pliscommon.exception.PlisException;
import de.bund.bva.pliscommon.exception.PlisTechnicalRuntimeException;
import de.bund.bva.pliscommon.exception.service.PlisTechnicalToException;
import de.bund.bva.pliscommon.exception.service.PlisToException;
import de.bund.bva.pliscommon.serviceapi.common.exception.PlisExceptionMapper;
import de.bund.bva.pliscommon.serviceapi.common.konstanten.EreignisSchluessel;

/**
 * Eine generische Exception-Fassade fr Service- und Native-GUI-Komponenten. Diese Implementierung fngt alle
 * Exceptions und behandelt sie folgendermaen:
 *
 * <ul>
 * <li>Eine {@link PlisException} wird auf einem konfigurierbaren Loglevel geloggt (siehe
 * {@link #setLogLevelPlisExceptions(Level)}). Sie wird auf eine {@link PlisToException} abgebildet, die pro
 * konkreter {@link PlisException}-Subklasse konfigurierbar ist (siehe
 * {@link #setExceptionMappingSource(ExceptionMappingSource)}).</li>
 * <li>Eine {@link PlisTechnicalRuntimeException} wird auf Level ERROR geloggt und auf eine global
 * konfigurierbare {@link PlisTechnicalToException} abgebildet (siehe
 * {@link #setExceptionMappingSource(ExceptionMappingSource)}).</li>
 * <li>Eine sonstige Exception zunchst in eine global konfigurierbare {@link PlisTechnicalRuntimeException}
 * gewrappt ({@link #setAppTechnicalRuntimeException(Class)}) und dann auf Level ERROR geloggt. Durch das
 * Wrapping werden eine Ausnahme-ID und Unique-ID erzeugt, die dann sowohl im Server- als auch im Aufrufer-Log
 * erscheinen. Die gewrappte Exception wird dann auf die global konfigurierbare
 * {@link PlisTechnicalToException} abgebildet.
 * </ul>
 *
 */
public class ServiceExceptionFassade implements MethodInterceptor, Validatable {

    /** Isy-Logger. */
    private static final IsyLogger LOG = IsyLoggerFactory.getLogger(ServiceExceptionFassade.class);

    /** Methoden-Mapping. */
    private MethodMappingSource methodMappingSource;

    /** Quelle fr das Exception-Mapping. */
    private ExceptionMappingSource exceptionMappingSource;

    /** Der Ausnahme-ID-Ermittler. */
    private AusnahmeIdErmittler ausnahmeIdErmittler;

    /** Konstruktor der generischen, technischen PlisRuntimeException der Anwendung. */
    private Constructor<? extends PlisTechnicalRuntimeException> appTechnicalRuntimeExceptionCon;

    /**
     * Das Log-Level, auf dem (checked) {@link PlisException PlisExceptions} geloggt werden. Checked
     * Exceptions sind vorhergesehene fachliche oder technische Fehler, die keinen unerwarteten
     * Betriebszustand darstellen. Sie sollen deshalb unter Umstnden nicht ins ERROR-Log geschrieben werden,
     * um den Betrieb nicht grundlos zu alarmieren. Hier kann ein feinerer Log-Level konfiguriert werden oder
     * <code>null</code>, um checked Exceptions gar nicht zu loggen.
     */
    private String logLevelPlisExceptions = "ERROR";

    @Required
    public void setMethodMappingSource(MethodMappingSource methodMappingSource) {
        this.methodMappingSource = methodMappingSource;
    }

    @Required
    public void setExceptionMappingSource(ExceptionMappingSource exceptionMappingSource) {
        this.exceptionMappingSource = exceptionMappingSource;
    }

    @Required
    public void setAusnahmeIdErmittler(AusnahmeIdErmittler ausnahmeIdErmittler) {
        this.ausnahmeIdErmittler = ausnahmeIdErmittler;
    }

    @Required
    public void setAppTechnicalRuntimeException(
            Class<? extends PlisTechnicalRuntimeException> appTechnicalRuntimeException) {
        try {
            this.appTechnicalRuntimeExceptionCon = appTechnicalRuntimeException.getConstructor(String.class,
                    Throwable.class, String[].class);
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("Die Klasse " + appTechnicalRuntimeException.getName()
                    + " hat nicht den bentigten Konstruktor "
                    + "(String ausnahmeId, Throwable cause, String... parameter)");
        }
    }

    public void setLogLevelPlisExceptions(String logLevelPlisExceptions) {
        this.logLevelPlisExceptions = logLevelPlisExceptions;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {

        Method remoteBeanMethod = invocation.getMethod();

        try {
            return invocation.proceed();
        } catch (PlisException e) {
            if (this.logLevelPlisExceptions != null) {
                // Workaround, da Isy-Logging keine bergabe des Loglevels untersttzt.
                if ("INFO".equals(this.logLevelPlisExceptions)) {
                    LOG.info(LogKategorie.JOURNAL, "Fehler in der Serviceoperation {}", e,
                            getMethodSignatureString(invocation));
                } else if ("DEBUG".equals(this.logLevelPlisExceptions)) {
                    LOG.debug("Fehler in der Serviceoperation {}: {}", getMethodSignatureString(invocation),
                            e.getMessage());
                } else if ("WARN".equals(this.logLevelPlisExceptions)) {
                    LOG.warn("Fehler in der Serviceoperation {}", e, getMethodSignatureString(invocation));
                } else if ("ERROR".equals(this.logLevelPlisExceptions)) {
                    LOG.error("Fehler in der Serviceoperation {}", e, getMethodSignatureString(invocation));
                } else if ("FATAL".equals(this.logLevelPlisExceptions)) {
                    LOG.fatal("Fehler in der Serviceoperation {}", e, getMethodSignatureString(invocation));
                } else if ("TRACE".equals(this.logLevelPlisExceptions)) {
                    LOG.trace("Fehler in der Serviceoperation {}: {}", getMethodSignatureString(invocation),
                            e.getMessage());
                }
            }

            Class<? extends PlisToException> targetExceptionClass = this.exceptionMappingSource
                    .getToExceptionClass(remoteBeanMethod, e.getClass());
            if (targetExceptionClass == null) {
                targetExceptionClass = this.exceptionMappingSource.getGenericTechnicalToException(remoteBeanMethod);
                LOG.warn(EreignisSchluessel.KEIN_EXCEPTION_MAPPING_DEFINIERT,
                        "Fr die Serviceoperation {} ist kein Exception-Mapping fr Exceptionklasse {} definiert. Benutze stattdessen technische TO-Exception {}",
                        getMethodSignatureString(invocation), e.getClass(), targetExceptionClass.getName());
            }
            PlisToException toException = PlisExceptionMapper.mapException(e, targetExceptionClass);
            throw toException;
        } catch (PlisTechnicalRuntimeException e) {
            LOG.error("Fehler in der Serviceoperation {}", e, getMethodSignatureString(invocation));
            PlisTechnicalToException toException = PlisExceptionMapper.mapException(e,
                    this.exceptionMappingSource.getGenericTechnicalToException(remoteBeanMethod));
            throw toException;
        } catch (Throwable t) {
            // In seltenen Fllen trat bei Lasttests ein NoClassDefFound-Fehler beim Erzeugen der
            // RuntimeException auf. Da dabei der ursprngliche Fehler verloren ging, wird hier nochmal
            // gecatcht.
            PlisTechnicalRuntimeException plisRuntimeException = null;
            try {

                // Die gefangene Exception wird in eine generische PLIS-RuntimeException gewrapped, damit die
                // Ausnahme-ID und Unique-ID, die zum Client bermittelt werden, auch im Server-Log stehen.
                plisRuntimeException = this.appTechnicalRuntimeExceptionCon.newInstance(
                        this.ausnahmeIdErmittler.ermittleAusnahmeId(t), t,
                        new String[] { ObjectUtils.defaultIfNull(t.getMessage(), "<Kein Fehlertext>") });
                LOG.error("Fehler in der Serviceoperation ", plisRuntimeException,
                        getMethodSignatureString(invocation));

            } catch (Throwable t2) {
                // Loggen des Sonderfalls Fehler in Fehlerbehandlung
                LOG.error(EreignisSchluessel.FEHLER_FEHLERBEHANDLUNG, "Fehler bei der Fehlerbehandlung", t2);
                LOG.error(EreignisSchluessel.FEHLER_FEHLERBEHANDLUNG, "Usprnglicher Fehler war {}", t,
                        t.getMessage());
            }

            PlisTechnicalToException toException = PlisExceptionMapper.mapException(plisRuntimeException,
                    this.exceptionMappingSource.getGenericTechnicalToException(remoteBeanMethod));
            throw toException;

        }
    }

    /**
     * Erstellt den Signatur-String des gegebenen Aufrufs.
     *
     * @param invocation
     *            der Methodenaufruf
     *
     * @return der Signatur-String
     */
    protected String getMethodSignatureString(MethodInvocation invocation) {
        Method method = invocation.getMethod();
        return getMethodSignatureString(method);
    }

    /**
     * Erstellt den Signatur-String des gegebenen Aufrufs.
     *
     * @param method
     *            die gerufene Methode
     *
     * @return der Signatur-String
     */
    protected String getMethodSignatureString(Method method) {
        return method.getDeclaringClass().getName() + "." + method.getName();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void validateConfiguration(Class<?> remoteBeanInterface, Object target) {

        Class<?> targetClass = AopUtils.getTargetClass(target);

        for (Method remoteBeanMethod : remoteBeanInterface.getMethods()) {
            Class<? extends PlisTechnicalToException> genericTechnicalToExceptionClass = this.exceptionMappingSource
                    .getGenericTechnicalToException(remoteBeanMethod);
            if (genericTechnicalToExceptionClass == null) {
                throw new IllegalStateException("Fehler in der statischen Konfiguration der Exception-Fassade fr "
                        + remoteBeanInterface.getName() + ": Keine generische TO-Exception definiert");
            }
            if (!ArrayUtils.contains(remoteBeanMethod.getExceptionTypes(), genericTechnicalToExceptionClass)) {
                throw new IllegalStateException("Fehler in der statischen Konfiguration der Exception-Fassade fr "
                        + getMethodSignatureString(remoteBeanMethod) + ": Die generische TO-Exception "
                        + genericTechnicalToExceptionClass.getSimpleName()
                        + " ist nicht im RemoteBean-Interface deklariert");
            }

            Method coreMethod = this.methodMappingSource.getTargetMethod(remoteBeanMethod, targetClass);

            for (Class<?> exceptionClass : coreMethod.getExceptionTypes()) {
                if (PlisException.class.isAssignableFrom(exceptionClass)) {
                    @SuppressWarnings("unchecked")
                    Class<? extends PlisException> plisExceptionClass = (Class<? extends PlisException>) exceptionClass;
                    Class<? extends PlisToException> plisToExceptionClass = this.exceptionMappingSource
                            .getToExceptionClass(remoteBeanMethod, plisExceptionClass);
                    if (plisToExceptionClass == null) {
                        throw new IllegalStateException(
                                "Fehler in der statischen Konfiguration der Exception-Fassade fr "
                                        + getMethodSignatureString(remoteBeanMethod)
                                        + ": Keine TO-Exception fr AWK-Exception "
                                        + plisExceptionClass.getSimpleName() + " definiert");
                    }
                    if (!ArrayUtils.contains(remoteBeanMethod.getExceptionTypes(), plisToExceptionClass)) {
                        throw new IllegalStateException(
                                "Fehler in der statischen Konfiguration der Exception-Fassade fr "
                                        + getMethodSignatureString(remoteBeanMethod) + ": Die TO-Exception "
                                        + plisToExceptionClass.getSimpleName()
                                        + " ist nicht im RemoteBean-Interface deklariert");
                    }
                }
            }
        }
    }
}