ch.elexis.data.Query.java Source code

Java tutorial

Introduction

Here is the source code for ch.elexis.data.Query.java

Source

/*******************************************************************************
 * Copyright (c) 2005-2015, G. Weirich and Elexis
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    G. Weirich - initial implementation
 *    D. Lutz    - case insenitive add()
 *      MEDEVIT <office@medevit.at>
 *******************************************************************************/

package ch.elexis.data;

import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.lang.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.elexis.core.constants.StringConstants;
import ch.elexis.core.data.activator.CoreHub;
import ch.elexis.core.data.status.ElexisStatus;
import ch.elexis.core.exceptions.PersistenceException;
import ch.elexis.core.jdt.NonNull;
import ch.elexis.core.jdt.Nullable;
import ch.rgw.tools.ExHandler;
import ch.rgw.tools.IFilter;
import ch.rgw.tools.JdbcLink;
import ch.rgw.tools.JdbcLink.Stm;
import ch.rgw.tools.StringTool;
import ch.rgw.tools.TimeTool;

/**
 * Query manages all database queries of PersistentObjects and derived classes
 * 
 * Die Query-Klasse erledigt alle Datenbankabfragen auf PersistentObjects und davon abgeleitete
 * Klassen.
 * 
 * @author Gerry Weirich
 */

public class Query<T> {
    private static Logger log = LoggerFactory.getLogger(Query.class);

    public static final String EQUALS = "=";
    public static final String GREATER = ">";
    public static final String LESS = "<";
    public static final String LESS_OR_EQUAL = "<=";
    public static final String GREATER_OR_EQUAL = ">=";
    public static final String NOT_EQUAL = "<>";

    private final static String SELECT_ID_FROM = "SELECT ID FROM ";
    public static final String LIKE = "LIKE";
    private String link = " WHERE ";
    private final boolean clearEntityCache;

    private StringBuilder sql;
    private PersistentObject template;
    private Method load;
    private String lastQuery = "";
    private final LinkedList<IFilter> postQueryFilters = new LinkedList<IFilter>();
    private String ordering;
    private final ArrayList<String> exttables = new ArrayList<String>(2);

    private final String[] ID_FETCH_VAL = new String[] { "ID" };
    private final String[] fetchVals;

    /**
     * @param cl
     *            the class to apply the query on
     */
    public Query(@NonNull final Class<? extends PersistentObject> cl) {
        this(cl, null, null);
    }

    /**
     * convenience constructor adding a query condition
     * 
     * @param cl
     *            the class to apply the query on
     * @param field
     * @param value
     *            value that field should be equal with
     */
    public Query(final Class<? extends PersistentObject> cl, @Nullable final String field,
            @Nullable final String value) {
        this(cl, field, value, null, (String[]) null);
    }

    /**
     * Initialize a query with optional pre-fetch
     * 
     * @param cl
     * @param field
     *            an initial condition that {@link #EQUALS} to the parameter value, can be
     *            <code>null</code>
     * @param value
     * @param tableName
     *            the name of the database table the values are stored in
     * @param prefetch
     *            array of values to pre-fetch, or <code>null</code>. If not <code>null</code>, must
     *            map to columns in the database.
     * @throws UnsupportedOperationException
     *             for unsupported pre-fetch fields
     * @since 3.1
     */
    public Query(final Class<? extends PersistentObject> cl, @Nullable final String field,
            @Nullable final String value, @Nullable final String tableName, @Nullable final String[] prefetch) {

        this(cl, tableName, field, value, false, prefetch);
    }

    /**
     * Initialize a query with optional value prefetch (i.e. cache population)
     * 
     * @param cl
     * @param tableName
     *            the name of the database table the values are stored in
     * @param field
     *            an initial condition that {@link #EQUALS} to the parameter value, can be
     *            <code>null</code>
     * @param value
     * @param clearCache
     *            clears all cached attributes for this element, only populating the cache with the
     *            current prefetches. ONLY use on objects supporting
     *            {@link PersistentObject#clearCachedAttributes()}
     * @param prefetch
     *            array of values to pre-fetch, or <code>null</code>. If not <code>null</code>, must
     *            map to columns in the database.
     * @throws UnsupportedOperationException
     *             for unsupported prefetch fields, or on objects not supporting
     *             {@link #clearEntityCache}
     * @since 3.7
     */
    public Query(final Class<? extends PersistentObject> cl, String tableName, @Nullable final String field,
            @Nullable final String value, boolean clearCache, String[] prefetch) {
        try {
            // load class first to make sure field mapping is initialized
            template = CoreHub.poFactory.createTemplate(cl);
            load = cl.getMethod("load", new Class[] { String.class });

            clearEntityCache = clearCache;

            if (prefetch != null) {
                // resolve the delivered field names to the real database columns
                // consider the resp. datatypes stored
                List<String> mappedPrefetchValues = new ArrayList<String>(Arrays.asList(ID_FETCH_VAL));
                for (int i = 0; i < prefetch.length; i++) {
                    String map = PersistentObject.map(tableName, prefetch[i]);
                    if (!map.contains(":")) {
                        mappedPrefetchValues.add(map);
                    } else if (map.startsWith("S:")) {
                        mappedPrefetchValues.add(map.substring(4));
                    } else {
                        throw new UnsupportedOperationException(
                                "prefetch value not supported: " + prefetch[i] + " maps to " + map);
                    }
                }

                fetchVals = mappedPrefetchValues.toArray(new String[] {});
            } else {
                fetchVals = ID_FETCH_VAL;
            }
            clear(false);

            if (field != null && value != null) {
                add(field, EQUALS, value);
            }

        } catch (Exception ex) {
            ElexisStatus status = new ElexisStatus(ElexisStatus.ERROR, CoreHub.PLUGIN_ID, ElexisStatus.CODE_NONE,
                    "Query: Konnte Methode load auf " + cl.getName() + " nicht auflsen", ex,
                    ElexisStatus.LOG_ERRORS);
            throw new PersistenceException(status);
        }
    }

    /**
     * Convenience constructor
     * 
     * @param cl
     * @param clearCache
     * @param prefetch
     * @see #Query(Class, String, String, boolean, String[])
     * @since 3.7
     */
    public Query(final Class<? extends PersistentObject> cl, String tableName, boolean clearCache,
            String[] prefetch) {
        this(cl, tableName, null, null, clearCache, prefetch);
    }

    /**
     * This method allows to set a custom sql query string; E.g. The original Query does not support
     * the usage of INNER JOINS, to use them nevertheless we need to provide a direct method to set
     * query strings
     * 
     * @param cl
     *            the persistent object to set the query for
     * @param string
     *            the SQL query string
     * @author Marco Descher
     */
    public Query(Class<? extends PersistentObject> cl, final String string) {
        try {
            template = CoreHub.poFactory.createTemplate(cl);
            load = cl.getMethod("load", new Class[] { String.class });
            sql = new StringBuilder(500);
            sql.append(string);
            ordering = null;
            fetchVals = ArrayUtils.EMPTY_STRING_ARRAY;
            clearEntityCache = false;
        } catch (Exception ex) {
            ElexisStatus status = new ElexisStatus(ElexisStatus.ERROR, CoreHub.PLUGIN_ID, ElexisStatus.CODE_NONE,
                    "Query: Konnte Methode load auf " + cl.getName() + " nicht auflsen", ex,
                    ElexisStatus.LOG_ERRORS);
            throw new PersistenceException(status);
        }
    }

    /**
     * Delete query to e.g. re-use the query for a new execution run
     * 
     * @see #clear(boolean)
     */
    public void clear() {
        clear(false);
    }

    /**
     * Delete query to e.g. re-use the query for a new execution run
     * 
     * @param includeDeletedEntriesInQuery
     *            to include deleted elements in your query initialize your query with
     *            {@link #clear(boolean)} == <code>true</code>, the default as executed by
     *            {@link #clear()} is <code>false</code>
     */
    public void clear(boolean includeDeletedEntriesInQuery) {
        sql = new StringBuilder(500);
        String table = template.getTableName();

        sql.append("SELECT ");
        for (int i = 0; i < fetchVals.length; i++) {
            sql.append(fetchVals[i]);
            if (i + 1 < fetchVals.length) {
                sql.append(", ");
            }
        }
        sql.append(" FROM ").append(table);

        String cns = template.getConstraint();
        if (cns.equals("")) {
            if (includeDeletedEntriesInQuery) {
                link = " WHERE ";
            } else {
                sql.append(" WHERE deleted=").append(JdbcLink.wrap("0"));
                link = " AND ";
            }
        } else {
            sql.append(" WHERE ").append(cns);
            if (!includeDeletedEntriesInQuery) {
                sql.append(" AND deleted=").append(JdbcLink.wrap("0"));
            }
            link = " AND ";
        }
        ordering = null;
        exttables.clear();
    }

    private void append(final String... s) {
        sql.append(link);
        for (String a : s) {
            sql.append(StringConstants.SPACE).append(a);
        }
        if (link.equals(" WHERE ") || link.equals(StringConstants.EMPTY)) {
            link = " AND ";
        }
    }

    /**
     * Folgende Ausdrcke bis endGroup gruppieren
     */
    public void startGroup() {
        append("(");
        link = StringConstants.EMPTY;
    }

    /**
     * Gruppierung ende
     */
    public void endGroup() {
        sql.append(")");
    }

    /** Bedingung einsetzen, die immer erfllt ist */
    public void insertTrue() {
        append("1=1");
    }

    /** Bedingung einsetzen, die nie erfllt ist */
    public void insertFalse() {
        append("1=0");
    }

    /**
     * AND-Verknpfung anfgen.
     */
    public void and() {
        if (link.equals(" OR ")) {
            link = " AND ";
        }
    }

    /**
     * OR-Verknpfung anfgen
     */
    public void or() {
        link = " OR ";
    }

    /**
     * Bedingung zufgen. Mehrere Bedingungen knnen hinzugefgt werden, indem jeweils zwischen zwei
     * add() Aufrufen and() oder or() aufgerufen wird. Die Abfrage wird noch nicht ausgefhrt,
     * sondern erst beim abschliessenden execute().
     *
     * @param feld
     *            Das Feld, fr das die Bedingung gilt
     * @param operator
     *            Vergleich (z.B. "=", "LIKE", ">", "<")
     * @param wert
     *            Der Wert, der gesucht wird. Fr Wildcard suche kann der Wert % enthalten, der
     *            Operator muss dann aber "LIKE" sein.
     *            Falls ein Feldname angegeben wird, wird die Vergleichsoperation auf dem Feld
     *            ausgefhrt. Es ist auch <code>null</code> erlaubt.
     * @param toLower
     *            ei true werden die Parameter mit der SQL-Funktion "lower()" in Kleinschreibung
     *            umgewandelt, so dass die Gross-/Kleinschreibung egal ist.
     * @return bei Fehler in der Syntax oder nichtexistenten Feldern
     */
    public boolean add(final String feld, String operator, String wert, final boolean toLower) {
        String mapped = template.map(feld);
        String mappedValue = (wert == null) ? null : template.map(template.getTableName(), wert, false);
        // treat date parameter separately
        // TODO This works only for european-style dates (dd.mm.yyyy)
        if (mapped.startsWith("S:D:")) {
            mapped = mapped.substring(4);
            // if a date should be matched partially
            wert = (wert == null) ? StringConstants.EMPTY : wert;
            if (operator.equalsIgnoreCase("LIKE") && !wert.matches("[0-9]{8,8}")) {
                StringBuilder sb = null;
                wert = wert.replaceAll("%", "");
                final String filler = "%%%%%%%%";
                // are we looking for the year?
                if (wert.matches("[0-9]{3,}")) {
                    sb = new StringBuilder(wert);
                    sb.append(filler);
                    wert = sb.substring(0, 8);
                } else {
                    // replace single digits as in 1.2.1932 with double digits
                    // as in 01.02.1932
                    wert = wert.replaceAll("[^0-9]([0-9])\\.", "0$1.");
                    // remove dots
                    sb = new StringBuilder(wert.replaceAll("\\.", ""));
                    // String must consist of 8 or more digits (ddmmYYYY)
                    sb.append(filler);
                    // convert to YYYYmmdd format
                    wert = sb.substring(4, 8) + sb.substring(2, 4) + sb.substring(0, 2);
                }
            } else {
                TimeTool tm = new TimeTool();
                if (tm.set(wert) == true) {
                    wert = tm.toString(TimeTool.DATE_COMPACT);
                }
            }
        } else if (mapped.startsWith("EXT:")) {
            int ix = mapped.indexOf(':', 5);
            if (ix == -1) {
                log.error("Ungltiges Feld " + feld);
                return false;
            }
            String table = mapped.substring(4, ix);
            mapped = table + "." + mapped.substring(ix + 1);
            String firsttable = template.getTableName() + ".";
            if (!exttables.contains(table)) {
                exttables.add(table);
                sql.insert(SELECT_ID_FROM.length(), table + ",");
                ix = sql.indexOf("deleted=");
                if (ix != -1) {
                    sql.insert(ix, firsttable);
                }
            }

            if (exttables.size() == 1) {
                sql.insert(7, firsttable); // Select ID from
                // firsttable,secondtable
            }
            append(table + ".ID=" + firsttable + "ID");
            // append(mapped,operator,wert);

        } else if ((mapped.startsWith("S:N:"))) {
            mapped = mapped.substring(4);
        } else if (mapped.matches(".*:.*")) {
            log.error("Ungltiges Feld " + feld);
            return false;
        }

        if (wert == null) {
            if (operator.equalsIgnoreCase("is") || operator.equals("=")) {
                // let's be a bit fault tolerant
                operator = "";
            } else if (NOT_EQUAL.equalsIgnoreCase(operator)) {
                operator = "NOT";
            }
            append(mapped, "IS", operator, "NULL");
        } else {
            if (mappedValue != null && !mappedValue.equals(wert)) {
                if (mappedValue.startsWith("S:N:")) {
                    mappedValue = mappedValue.substring(4);
                }
                append(mapped, operator, mappedValue);
            } else {
                wert = PersistentObject.getDefaultConnection().wrapFlavored(wert);
                if (toLower) {
                    mapped = "lower(" + mapped + ")";
                    wert = "lower(" + wert + ")";
                }
                append(mapped, operator, wert);

            }
        }

        return true;
    }

    public boolean add(final String feld, final String operator, final String wert) {
        return add(feld, operator, wert, false);
    }

    /** Unverndertes Token in den SQL-String einfgen */
    public void addToken(final String token) {
        append(token);
    }

    /**
     * Bequemlichkeitsmethode fr eine Abfrage, die nur einen einzigen Treffer liefern soll. Die
     * Syntax ist wie bei der add() Methode, aber die Abfrage wird gleich ausgefhrt
     * 
     * @param f
     *            Feld
     * @param op
     *            Vergleichsoperator (s. auch unter add())
     * @param v
     *            Wert (@see Query#add() )
     * @return Die ID des gefundenen Objekts oder null, wenn nicht gefunden
     */
    public String findSingle(final String f, final String op, final String v) {
        clear();
        sql.append(link).append(template.map(f)).append(op).append(JdbcLink.wrap(v));
        String ret = PersistentObject.getConnection().queryString(sql.toString());
        return ret;
    }

    /**
     * Finds one entry - in difference to {@link #findSingle(String, String, String)} the value is
     * wrapped by the underlying db flavor
     * 
     * @param f
     * @param op
     * @param v
     * @param wrapValue
     * @return
     */
    public String findOne(final String f, final String op, final String v) {
        clear();
        JdbcLink connection = PersistentObject.getConnection();
        sql.append(link).append(template.map(f)).append(op).append(connection.wrapFlavored(v));
        String ret = connection.queryString(sql.toString());
        return ret;
    }

    /**
     * Bequemlichkeitsmethode, um gleich mehrere Felder auf einmal anzugeben, welche mit AND
     * verknpft werden. Dies ist dasselbe, wie mehrere Aufrufe nacheinander von add() und and(),
     * aber die Abfrage wird gleich ausgefhrt und die Resultate werden nach den bergebenen Feldern
     * sortiert, in der Reihenfolge, in der sie bergeben wurden.
     * 
     * @param fields
     *            Die Felder, die in die abfrage eingesetzt werden sollen
     * @param values
     *            die Werte, nach denen gesucht werden soll. Wenn values fr ein Feld leer ist (null
     *            oder ""), dann wird dieses Feld aus der Abfrage weggelassen
     * @param exact
     *            false, wenn die Abfrage mit LIKE erfolgen soll, sonst mit =
     * @return eine Liste mit den gefundenen Objekten
     */
    public List<T> queryFields(final String[] fields, final String[] values, final boolean exact) {
        clear();
        String op = EQUALS;
        if (exact == false) {
            op = " LIKE ";
        }
        and();
        for (int i = 0; i < fields.length; i++) {
            if (StringTool.isNothing(values[i])) {
                continue;
            }
            add(fields[i], op, values[i]);
        }
        return execute();
    }

    /**
     * Sortierung angeben. Dies muss als letzter Befehl nach einer Reihe von add() Sequenzen
     * erfolgen.
     * 
     * @param reverse
     *            true bei umgekehrter Sortierung
     * @param n1
     *            Beliebig viele Strings, die in absteigender Prioritt die Felder angeben, nach
     *            denen sortiert werden soll.
     */
    public void orderBy(final boolean reverse, final String... n1) {
        if (n1 != null && n1.length > 0) {
            StringBuilder sb = new StringBuilder();
            sb.append(" ORDER BY ");
            for (String s : n1) {
                String mapped = template.map(s);
                if (mapped.matches("[A-Z]{2,}:.+")) {
                    log.error("Ungltiges Feld " + s);
                    return;
                }
                if (mapped.startsWith("S:D:") || mapped.startsWith("S:N:")) {
                    mapped = mapped.substring(4);
                }
                sb.append(mapped);
                if (reverse == true) {
                    sb.append(" DESC");
                }
                sb.append(",");
            }
            sb.delete(sb.length() - 1, 10000);
            ordering = sb.toString();
        }
    }

    /**
     * Die zusammengestellte Abfrage ausfhren Dies kann aufgerufen werden, nachdem alle ntigen
     * add(), AND(), or() und orderBy() Operationen eingegeben wurden und liefert das Ergebnis
     * dieser Abfrage. execute() kann mit derselben Abfrage beliebig oft aufgerufen werden (und kann
     * unzterschiedliche Resultate liefern, wenn von anderer Stelle zwischenzeitlich eine nderung
     * der Datenbank erfolgte)
     * 
     * @return eine Liste aus Objekten, die das Resultat der Abfrage sind.
     */
    public List<T> execute() {
        return execute(PersistentObject.getDefaultConnection());
    }

    /**
     * Execute the query on the specified DBConnection.
     * 
     * @param connection
     * @return
     */
    public List<T> execute(DBConnection connection) {
        LinkedList<T> ret = new LinkedList<T>();
        return (List<T>) execute(ret, connection);
    }

    public Collection<T> execute(final Collection<T> collection) {
        return execute(collection, PersistentObject.getDefaultConnection());
    }

    /**
     * Execute the query on the specified DBConnection. The collection will be used to store the
     * results.
     * 
     * @param connection
     * @return
     */
    public Collection<T> execute(final Collection<T> collection, DBConnection connection) {
        if (ordering != null) {
            sql.append(ordering);
        }
        lastQuery = sql.toString();
        return queryExpression(lastQuery, collection, connection);
    }

    /**
     * Execute the {@link PreparedStatement} on the database.
     * 
     * @param ps
     * @param values
     * @return
     */
    public ArrayList<String> execute(final PreparedStatement ps, final String[] values) {
        try {
            for (int i = 0; i < values.length; i++) {
                ps.setString(i + 1, values[i]);
            }
            if (ps.execute() == true) {
                ArrayList<String> ret = new ArrayList<String>();
                ResultSet res = ps.getResultSet();
                while (res.next()) {
                    ret.add(res.getString(1));
                }
                return ret;
            }
        } catch (Exception ex) {
            ElexisStatus status = new ElexisStatus(ElexisStatus.ERROR, CoreHub.PLUGIN_ID, ElexisStatus.CODE_NONE,
                    "Fehler beim Ausfhren von " + sql.toString(), ex, ElexisStatus.LOG_ERRORS);
            throw new PersistenceException(status);
        }
        return null;
    }

    /**
     * Eine komplexe selbst zusammengestellte Abfrage ausfhren. Die Methoden von Query erlauben
     * eine einfache Zusammenstellung einer SQL-Abfrage, Fr spezielle Flle will man aber
     * vielleicht die SQL-Abfrage doch selber direkt angeben. Dies kann hier erfolgen.
     * 
     * @param expr
     *            ein fr die verwendete Datenbank akzeptabler SQL-String. Es soll nach Mglichkeit
     *            nur Standard-SQL verwendet werden, um sich nicht von einer bestimmten Datenbank
     *            abhngig zu machen. Die Abfrage muss nur nach dem Feld ID fragen; das Objekt wird
     *            von query selbst hergestellt.
     * @return Eine Liste der Objekte, die als Antwort auf die Anfrage geliefert wurden.
     */
    @SuppressWarnings("unchecked")
    public Collection<T> queryExpression(final String expr, Collection<T> ret) {
        return queryExpression(expr, ret, PersistentObject.getDefaultConnection());
    }

    /**
     * Execute the query on the database using the specified {@link DBConnection}.
     * 
     * @param expr
     * @param ret
     * @param connection
     * @return
     */
    public Collection<T> queryExpression(final String expr, Collection<T> ret, DBConnection connection) {
        if (ret == null) {
            ret = new LinkedList<T>();
        }
        // loaded objects should use provided connection if it is not the default connection
        boolean setConnection = connection != PersistentObject.getDefaultConnection();

        Stm stm = connection.getStatement();
        try (ResultSet res = stm.query(expr)) {
            log.debug("Executed " + expr);
            while ((res != null) && (res.next() == true)) {
                final String id = res.getString(1);
                T o = (T) load.invoke(null, new Object[] { id });
                if (o == null) {
                    continue;
                } else if (setConnection) {
                    ((PersistentObject) o).setDBConnection(connection);
                }

                PersistentObject po = (PersistentObject) o;

                if (clearEntityCache) {
                    po.clearCachedAttributes();
                }

                if (fetchVals.length > 1) {
                    for (int i = 1; i < fetchVals.length; i++) {
                        Object prefetchVal = res.getObject(i + 1);
                        po.putInCache(fetchVals[i], prefetchVal);
                    }
                }

                boolean bAdd = true;
                for (IFilter fi : postQueryFilters) {
                    if (fi.select(o) == false) {
                        bAdd = false;
                        break;
                    }
                }
                if (bAdd == true) {
                    ret.add(o);
                }

            }
            return ret;

        } catch (Exception ex) {
            ElexisStatus status = new ElexisStatus(ElexisStatus.ERROR, CoreHub.PLUGIN_ID, ElexisStatus.CODE_NONE,
                    "Fehler bei Datenbankabfrage " + ex.getMessage(), ex, ElexisStatus.LOG_ERRORS);
            log.warn("Fehler [{}] bei Datenbankabfrage:\n[{};]", ex.getMessage(), expr);
            throw new PersistenceException(status);
        } finally {
            connection.releaseStatement(stm);
        }
    }

    /**
     * Die Grsse des zu erwartenden Resultats abfragen. Dieses Resultat stimmt nur ungefhr, da es
     * bis zur tatschlichen Abfrage noch nderungen geben kann, und da allfllige postQueryFilter
     * das Resultat verkleinern knnten.
     * 
     * @return die ungefhre Zahl der erwarteten Objekte.
     */
    public int size() {
        try {
            Stm stm = PersistentObject.getConnection().getStatement();
            String res = stm.queryString("SELECT COUNT(*) FROM " + template.getTableName());
            PersistentObject.getConnection().releaseStatement(stm);
            return Integer.parseInt(res);
        } catch (Exception ex) {
            ExHandler.handle(ex);
            return 10000;
        }
    }

    public String getLastQuery() {
        return lastQuery;
    }

    public String getActualQuery() {
        return sql.toString();
    }

    /**
     * PostQueryFilters sind Filter-Objeckte, die <i>nach</i> der Datenbankanfrage auf das
     * zurckgelieferte Resultat angewendet werden. Diese sind weniger effizient, als Filter, die
     * bereits im Query-String enthalten sind, aber sie erlauben Datenbankunabhngig feinere
     * Filterungen. Sie sind auch die einzige Mglichkeit, auf komprimierte oder codierte Felder zu
     * filtern.
     * 
     * @param f
     *            ein Filter
     */
    public void addPostQueryFilter(final IFilter f) {
        postQueryFilters.add(f);
    }

    public void removePostQueryFilter(final IFilter f) {
        postQueryFilters.remove(f);
    }
}