de.bund.bva.pliscommon.ueberwachung.common.jmx.ServiceStatistikMBean.java Source code

Java tutorial

Introduction

Here is the source code for de.bund.bva.pliscommon.ueberwachung.common.jmx.ServiceStatistikMBean.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.ueberwachung.common.jmx;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.util.ClassUtils;

import de.bund.bva.isyfact.logging.IsyLogger;
import de.bund.bva.isyfact.logging.IsyLoggerFactory;
import de.bund.bva.pliscommon.exception.service.PlisBusinessToException;
import de.bund.bva.pliscommon.exception.service.PlisToException;
import de.bund.bva.pliscommon.serviceapi.annotations.FachlicherFehler;
import de.bund.bva.pliscommon.serviceapi.service.httpinvoker.v1_0_0.AufrufKontextTo;

/**
 * Diese Klasse implementiert eine berwachungs-MBean fr Services. Sie liefert die berwachungsoptionen,
 * welche jeder Service der PLIS anbieten muss.
 *
 * @author sd&m Simon Spielmann
 * @version $Id: ServiceStatistikMBean.java 144975 2015-08-18 13:07:56Z sdm_jmeisel $
 */
@ManagedResource(description = "Diese MBean liefert berwacht die Aufrufe eines Services.")
public class ServiceStatistikMBean implements MethodInterceptor, InitializingBean {
    /**
     * Standard-Wert fuer Anzahl Suchen, anhand derer der Durchschnitt berechnet wird.
     */
    private static final int ANZAHL_AUFRUFE_FUER_DURCHSCHNITT = 10;

    /**
     * Gibt an, ob die Rckgabeobjektstrukturen auf fachliche Fehler berprft werden sollen. Kann
     * Auswirkungen auf die Performance haben.
     */
    private boolean erweiterteFachlicheFehlerpruefung;

    /**
     * Gibt an, ob die Rckgabeobjektstrukturen auf fachliche Fehler berprft werden sollen. Kann
     * Auswirkungen auf die Performance haben.
     *
     * @param fachlicheFehlerpruefungAktiviert
     *            <code>true</code> wenn die Rckgabeobjektstruktur auf fachliche Fehler hin untersucht werden
     *            soll, ansonsten <code>false</code>.
     */
    public void setErweiterteFachlicheFehlerpruefung(boolean fachlicheFehlerpruefungAktiviert) {
        this.erweiterteFachlicheFehlerpruefung = fachlicheFehlerpruefungAktiviert;
    }

    /**
     * Dauern der letzten Such-Aufrufe (in Millisekunden).
     */
    private List<Long> letzteSuchdauern = new LinkedList<Long>();

    /**
     * Merker fr die Minute, in der Werte der letzten Minute ermittelt wurden.
     */
    private volatile int letzteMinute;

    /**
     * Anzahl der nicht fehlerhaften Aufrufe, die in der durch letzteMinute bezeichneten Minute durchgefhrt
     * wurden.
     *
     */
    private volatile int anzahlAufrufeLetzteMinute;

    /**
     * Anzahl der nicht fehlerhaften Aufrufe, die in der aktuellen Minute durchgefhrt wurden.
     */
    private volatile int anzahlAufrufeAktuelleMinute;

    /**
     * Anzahl der Aufrufe, die in der durch letzteMinute bezeichneten Minute durchgefhrt wurden, bei denen
     * ein techinscher Fehler aufgetreten ist.
     */
    private volatile int anzahlFehlerLetzteMinute;

    /**
     * Anzahl der Aufrufe, die in der aktuellen Minute durchgefhrt wurden, bei denen ein techinscher Fehler
     * aufgetreten ist.
     */
    private volatile int anzahlFehlerAktuelleMinute;

    /**
     * Die Anzahl der fachlichen Fehler in der aktuellen Minute. Ein fachlicher Fehler liegt vor, wenn
     * entweder eine Exception vom Typ PlusBusinessException geworfen wurde oder die zurckgegebene
     * Fehlerliste Eintrge enthielt.
     */
    private volatile int anzahlFachlicheFehlerLetzteMinute;

    /**
     * Die Anzahl der fachlichen Fehler in der letzten Minute. Ein fachlicher Fehler liegt vor, wenn entweder
     * eine Exception vom Typ PlusBusinessException geworfen wurde oder die zurckgegebene Fehlerliste
     * Eintrge enthielt.
     */
    private volatile int anzahlFachlicheFehlerAktuelleMinute;

    /** Fehlercode einer eventuell geworfenen Exception. */
    private String fehlerCode;

    /**
     * Logger.
     */
    private static final IsyLogger LOGISY = IsyLoggerFactory.getLogger(ServiceStatistikMBean.class);

    /**
     * Die maximale Tiefe bei der rekursiven Prfung auf fachliche Fehler bei der erweiterten fachlichen
     * Fehlerprfung.
     */
    private static final int MAXTIEFE = 10;

    /**
     * Diese Methode zhlt einen Aufruf der Komponente fr die Statistik. Fr die Statistik wird die Angabe
     * der Dauer und ob der Aufruf fehlerhaft war bentigt.
     * @param dauer
     *            Die Dauer des Aufrufs in Millisekunden.
     * @param erfolgreich
     *            Kennzeichen, ob der Aufruf erfolgreich war (<code>true</code>) oder ein technischer Fehler
     *            aufgetreten ist (<code>false</code>).
     * @param fachlichErfolgreich
     *            Kennzeichen, ob der Aufruf fachlich erfolgreich war (<code>true</code>) oder ein fachlicher
     *            Fehler aufgetreten ist (<code>false</code>).
     */
    public synchronized void zaehleAufruf(long dauer, boolean erfolgreich, boolean fachlichErfolgreich) {
        aktualisiereZeitfenster();
        this.anzahlAufrufeAktuelleMinute++;
        if (!erfolgreich) {
            this.anzahlFehlerAktuelleMinute++;
        }
        if (!fachlichErfolgreich) {
            this.anzahlFachlicheFehlerAktuelleMinute++;
        }
        if (this.letzteSuchdauern.size() == ANZAHL_AUFRUFE_FUER_DURCHSCHNITT) {
            this.letzteSuchdauern.remove(ANZAHL_AUFRUFE_FUER_DURCHSCHNITT - 1);
        }
        this.letzteSuchdauern.add(0, dauer);
    }

    /**
     * Diese Methode veranlasst, dass das Zeitfenster fr die Zhler der Fehler und Aufrufe in der aktuellen
     * und letzten Minute aktualisiert wird. Falls eine Minute verstrichen ist, werden die Werte der aktuellen
     * Minute in die der Zhler fr die letzte Minut kopiert. Die Zhler fr die aktuelle Minute werden auf 0
     * gesetzt. Die Methode sorg dafr, dass dieser Vorgang nur einmal pro Minute ausgefhrt werden kann.
     */
    private synchronized void aktualisiereZeitfenster() {
        int aktuelleMinute = getAktuelleMinute();
        if (aktuelleMinute != this.letzteMinute) {
            if ((aktuelleMinute - this.letzteMinute) > 1) {
                // keine infos von letzten Minute
                this.anzahlAufrufeLetzteMinute = 0;
                this.anzahlFehlerLetzteMinute = 0;
                this.anzahlFachlicheFehlerLetzteMinute = 0;
            } else {
                this.anzahlAufrufeLetzteMinute = this.anzahlAufrufeAktuelleMinute;
                this.anzahlFehlerLetzteMinute = this.anzahlFehlerAktuelleMinute;
                this.anzahlFachlicheFehlerLetzteMinute = this.anzahlFachlicheFehlerAktuelleMinute;
            }

            this.anzahlAufrufeAktuelleMinute = 0;
            this.anzahlFehlerAktuelleMinute = 0;
            this.anzahlFachlicheFehlerAktuelleMinute = 0;
            this.letzteMinute = aktuelleMinute;
        }
    }

    /**
     * Berechnet die aktuelle Minute der Systemzeit.
     * @return Der Minuten-Anteil der aktuellen Systemzeit
     */
    private static final int getAktuelleMinute() {
        return (int) (System.currentTimeMillis() / 60000);
    }

    /**
     * Liefert die durchschnittliche Dauer der letzten 10 Aurufe. Definiert eine Methode fr das
     * Management-Interface dieser MBean.
     * @return Die durchschnittliche Dauer der letzten 10 Aufrufe in ms.
     */
    @ManagedAttribute(description = "Liefert die durchschnittliche Dauer der letzten 10 Aufrufe in ms.")
    public long getDurchschnittsDauerLetzteAufrufe() {
        long result = 0;
        if (this.letzteSuchdauern.size() > 0) {
            // Kopiere Liste um konkurrierende nderungen zu vermeiden
            // Explizit keine Synchronisierung, um die Anwendungsperformance
            // nicht zu verschlechtern.
            Long[] dauern = this.letzteSuchdauern.toArray(new Long[0]);
            for (long dauer : dauern) {
                result += dauer;
            }
            result /= this.letzteSuchdauern.size();
        }
        return result;
    }

    /**
     * Liefert die Anzahl der in der letzten Minute gezhlten Aufrufe, bei denen kein Fehler aufgetreten ist.
     * Definiert eine Methode fr das Management-Interface dieser MBean.
     * @return Die Anzahl der in der letzten Minute gezhlten Aufrufe, bei denen kein Fehler aufgetreten ist.
     */
    @ManagedAttribute(description = "Liefert die Anzahl der nicht fehlerhaften Aufrufe in der letzten Minute")
    public int getAnzahlAufrufeLetzteMinute() {
        aktualisiereZeitfenster();
        return this.anzahlAufrufeLetzteMinute;
    }

    /**
     * Liefert die Anzahl der in der letzten Minute gezhlten Aufrufe, bei denen ein Fehler aufgetreten ist.
     * Definiert eine Methode fr das Management-Interface dieser MBean.
     * @return Die Anzahl der in der letzten Minute gezhlten Aufrufe, bei denen ein Fehler aufgetreten ist.
     */
    @ManagedAttribute(description = "Liefert die Anzahl der fehlerhaften Aufrufe in der letzten Minute")
    public int getAnzahlFehlerLetzteMinute() {
        aktualisiereZeitfenster();
        return this.anzahlFehlerLetzteMinute;
    }

    /**
     * Liefert die Anzahl der in der letzten Minute gezhlten Aufrufe, bei denen ein fachlicher Fehler
     * aufgetreten ist.
     * @return Die Anzahl der in der letzten Minute gezhlten Aufrufe, bei denen ein fachlicher Fehler
     *         aufgetreten ist.
     */
    @ManagedAttribute(description = "Liefert die Anzahl der fachlich fehlerhaften Aufrufe in der letzten Minute")
    public int getAnzahlFachlicheFehlerLetzteMinute() {
        aktualisiereZeitfenster();
        return this.anzahlFachlicheFehlerLetzteMinute;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        long startZeit = System.currentTimeMillis();
        boolean erfolgreich = false;
        boolean fachlichErfolgreich = false;

        try {
            Object result = invocation.proceed();
            erfolgreich = true;
            if (this.erweiterteFachlicheFehlerpruefung) {
                fachlichErfolgreich = !sindFachlicheFehlerVorhanden(result);
            } else {
                fachlichErfolgreich = true;
            }
            return result;
        } catch (PlisBusinessToException t) {
            // BusinessExceptions werden nicht als technischer Fehler gezhlt.
            erfolgreich = true;
            this.fehlerCode = t.getAusnahmeId();
            throw t;
        } catch (PlisToException t) {
            this.fehlerCode = t.getAusnahmeId();
            throw t;
        } finally {
            long aufrufDauer = System.currentTimeMillis() - startZeit;
            zaehleAufruf(aufrufDauer, erfolgreich, fachlichErfolgreich);
        }
    }

    /**
     * Prft ob im Rckgabeobjekt fachliche Fehler enthalten waren. Das Rckgabeobjekt muss eine Collection
     * enthalten, die mit @FachlicheFehlerListe annotiert ist.
     *
     * @param result
     *            Das Rckgabeobjekt des Aufrufs.
     * @return true bei Fehlern, sonst false
     */
    private boolean sindFachlicheFehlerVorhanden(final Object result) {
        return pruefeObjektAufFehler(result, null, 1);
    }

    /**
     * Durchsucht eine Klasse nach Fehlerobjekten, die nicht null sind, oder Fehlercollections, die nicht leer
     * sind. Fehlerobjekten sind mit {link FachlicherFehler} annotiert.
     *
     * Durchsucht Oberklassen & untergeordnete Objektstrukturen ebenfalls rekursiv.
     *
     * @param result
     *            Das Objekt
     * @param clazz
     *            Die Klasse des Objekts durchsucht werden soll (optional). Kann leergelassen werden beim
     *            Start, kann aber genutzt werden um auf Oberklassen eines Objekts zu prfen.
     * @param tiefe
     *            tiefe Gibt die aktuelle Tiefe des Aufrufs an. Muss erhht werden wenn man die
     *            Klassenstruktur nach unten durchluft.
     * @return <code>true</code> wenn Fehler gefunden, ansonsten <code>false</code>
     */
    boolean pruefeObjektAufFehler(final Object result, Class<?> clazz, int tiefe) {
        boolean fehlerGefunden = false;
        Class<?> clazzToScan = clazz;
        // Wenn keine Klasse bergeben, selber ermitteln
        if (clazzToScan == null) {
            clazzToScan = result.getClass();
        }

        // Wenn max. Tiefe erreicht, nicht weiter prfen
        if (tiefe > MAXTIEFE) {
            LOGISY.trace("Max. Tiefe erreicht, prfe nicht weiter auf fachliche Fehler");
            return false;
        }

        Field[] fields = clazzToScan.getDeclaredFields();

        LOGISY.trace("{} Analysiere Objekt {} (Klasse {}) {} Felder gefunden.", StringUtils.repeat("-", tiefe),
                result.toString(), clazzToScan.getSimpleName(), fields.length);

        for (Field field : fields) {
            if (!ClassUtils.isPrimitiveOrWrapper(field.getType()) && !field.getType().isEnum()) {
                LOGISY.trace("{} {}.{}, Type {}", StringUtils.repeat("-", tiefe), clazzToScan.getSimpleName(),
                        field.getName(), field.getType().getSimpleName());
                field.setAccessible(true);
                try {
                    // Prfe einzelne Klassenfelder (non-Collection) auf annotierten Typ und Vorhandensein
                    if (!Collection.class.isAssignableFrom(field.getType())) {
                        if (field.get(result) != null) {
                            Object fieldObject = field.get(result);
                            if (fieldObject.getClass().isAnnotationPresent(FachlicherFehler.class)) {
                                // Fachliches Fehlerobjekt gefunden
                                return true;
                            }

                            // Wenn kein String, dann prfe rekursiv Objektstruktur
                            if (fieldObject.getClass() != String.class) {
                                fehlerGefunden = pruefeObjektAufFehler(fieldObject, null, tiefe + 1) ? true
                                        : fehlerGefunden;
                            }
                        }
                    } else {
                        // Collection, prfen ob fachliche Fehlerliste
                        ParameterizedType type = (ParameterizedType) field.getGenericType();
                        Class<?> collectionTypeArgument = (Class<?>) type.getActualTypeArguments()[0];
                        if (collectionTypeArgument.isAnnotationPresent(FachlicherFehler.class)) {
                            // Ist Fehlerliste, prfen ob nicht leer
                            Collection<?> collection = (Collection<?>) field.get(result);
                            if (collection != null && !collection.isEmpty()) {
                                // Fachliche Fehler in Fehlerliste gefunden
                                return true;
                            }
                        }
                    }
                } catch (IllegalAccessException e) {
                    // Nichts tun, Feld wird ignoriert
                    LOGISY.debug("Feldzugriffsfehler: {}", e.getMessage());
                }
            }
        }

        // Die Klassen-Hierachie rekursiv nach oben prfen
        if (clazzToScan.getSuperclass() != null && !clazzToScan.getSuperclass().equals(Object.class)) {
            LOGISY.trace("{}> Climb up class hierarchy! Source {}, Target {}", StringUtils.repeat("-", tiefe),
                    clazzToScan.getSimpleName(), clazzToScan.getSuperclass());
            fehlerGefunden =
                    // Aufruf mit gleicher Tiefe, da Vererbung nach oben durchlaufen wird
                    pruefeObjektAufFehler(result, clazzToScan.getSuperclass(), tiefe) ? true : fehlerGefunden;
        }

        return fehlerGefunden;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        LOGISY.debug("ServiceStatistikMBean "
                + (this.erweiterteFachlicheFehlerpruefung ? " mit erweiterter fachlicher Fehlerprfung " : "")
                + " initialisiert.");
    }

    /**
     * Ldt den {@link AufrufKontextTo} aus den Parametern der aufgerufenen Funktion.
     *
     * @param args
     *            die Argumente der Service-Operation
     *
     * @return das AufrufKontextTo Objekt
     */
    private AufrufKontextTo leseAufrufKontextTo(Object[] args) {
        if (ArrayUtils.isNotEmpty(args) && args[0] instanceof AufrufKontextTo) {
            return (AufrufKontextTo) args[0];
        } else {
            return null;
        }
    }

}