de.terrestris.shogun.dao.DatabaseDao.java Source code

Java tutorial

Introduction

Here is the source code for de.terrestris.shogun.dao.DatabaseDao.java

Source

/* Copyright (c) 2012-2014, terrestris GmbH & Co. KG
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * (This is the BSD 3-Clause, sometimes called 'BSD New' or 'BSD Simplified',
 * see http://opensource.org/licenses/BSD-3-Clause)
 */
package de.terrestris.shogun.dao;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javassist.Modifier;

import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.FetchMode;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Conjunction;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Disjunction;
import org.hibernate.criterion.ProjectionList;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.impl.CriteriaImpl;
import org.hibernate.impl.SessionImpl;
import org.hibernate.loader.OuterJoinLoader;
import org.hibernate.loader.criteria.CriteriaLoader;
import org.hibernate.persister.entity.OuterJoinLoadable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.fasterxml.jackson.annotation.JsonIgnore;

import de.terrestris.shogun.exception.ShogunDatabaseAccessException;
import de.terrestris.shogun.hibernatecriteria.filter.Filter.LogicalOperator;
import de.terrestris.shogun.hibernatecriteria.filter.HibernateFilter;
import de.terrestris.shogun.hibernatecriteria.filter.HibernateFilterItem;
import de.terrestris.shogun.hibernatecriteria.paging.HibernatePagingObject;
import de.terrestris.shogun.hibernatecriteria.sort.HibernateSortItem;
import de.terrestris.shogun.hibernatecriteria.sort.HibernateSortObject;
import de.terrestris.shogun.model.BaseModel;
import de.terrestris.shogun.model.BaseModelInheritance;
import de.terrestris.shogun.model.BaseModelInterface;
import de.terrestris.shogun.model.Group;
import de.terrestris.shogun.model.MapLayer;
import de.terrestris.shogun.model.User;

/**
 * The database Data Access Object of SHOGun.
 *
 * <p>
 * This is a generic database DAO in order to do queries against the
 * connected database using Hibernate/Hibernate Spatial.
 * </p>
 *
 * @author terrestris GmbH & Co. KG
 *
 */
@Repository
@Transactional
@Primary
public class DatabaseDao {

    /**
     * the logger instance
     */
    private static Logger LOGGER = Logger.getLogger(DatabaseDao.class);

    /**
     * the Hibernate SessionFactory reference
     */
    private SessionFactory sessionFactory;

    /**
     * Retrieves entities of the database by a given filter, sort-object
     * and paging-object
     *
     * @param hibernateSortObject
     * @param hibernateFilter
     * @param hibernatePagingObject
     * @param hibernateAdditionalFilter
     * @return
     * @throws ShogunDatabaseAccessException
     *
     */
    @SuppressWarnings("unchecked")
    public List<Object> getDataByFilter(HibernateSortObject hibernateSortObject, HibernateFilter hibernateFilter,
            Set<String> fields, Set<String> ignoreFields, HibernatePagingObject hibernatePagingObject,
            HibernateFilter hibernateAdditionalFilter) throws ShogunDatabaseAccessException {

        boolean isPlainModelRequest = (fields == null && ignoreFields == null);
        Class<?> clazz = hibernateSortObject.getMainClass();

        Criteria criteria = this.sessionFactory.getCurrentSession().createCriteria(clazz);

        // Fields
        if (fields != null) {
            ProjectionList pl = Projections.projectionList();

            for (Iterator<String> iterator = fields.iterator(); iterator.hasNext();) {
                String field = iterator.next();
                pl.add(Projections.property(field));
            }
            criteria.setProjection(Projections.distinct(pl));

        }

        // Ignore Fields
        // -> get all fields of the class and remove the ignorefields, works like a blacklist
        Set<String> cleanedFieldNames = new HashSet<String>();
        if (ignoreFields != null) {
            ProjectionList pl = Projections.projectionList();
            List<Field> allFields = getAllFields(new ArrayList<Field>(), clazz);

            for (Iterator<Field> iterator = allFields.iterator(); iterator.hasNext();) {
                Field field = (Field) iterator.next();

                if (!ignoreFields.contains(field.getName())) {
                    cleanedFieldNames.add(field.getName());
                }

            }

            for (Iterator<String> iterator = cleanedFieldNames.iterator(); iterator.hasNext();) {
                String cleanField = iterator.next();

                pl.add(Projections.property(cleanField), cleanField);
            }
            criteria.setProjection(Projections.distinct(pl));
        }

        // PAGING
        if (hibernatePagingObject != null) {
            criteria.setFirstResult(hibernatePagingObject.getStart());
            criteria.setMaxResults(hibernatePagingObject.getLimit());
        }

        // SORT
        List<HibernateSortItem> hibernateSortItems = hibernateSortObject.getSortItems();

        for (HibernateSortItem hibernateSortItem : hibernateSortItems) {
            criteria.addOrder(hibernateSortItem.createHibernateOrder());
        }

        /*
         * Check additional filters:
         * These are being sent from a client and represent AND conditions to be
         * applied globally.
         * The use-case that lead us to implement the additionalFilter is
         * the requirement that users are allowed to have both a AND and an OR
         * filter (e.g. in the frontend for EigeneLayer).
         * Usually one would implement this requirement with nested logical
         * filters
         */
        if (hibernateAdditionalFilter != null) {
            Conjunction afConjunction = Restrictions.conjunction();
            Criterion afCriterion = null;
            try {
                HibernateFilterItem hfi = (HibernateFilterItem) hibernateAdditionalFilter.getFilterItem(0);
                afCriterion = hfi.makeCriterion(clazz);
                afConjunction.add(afCriterion);
            } catch (Exception e) {
                e.printStackTrace();
                throw new ShogunDatabaseAccessException("Error creating a criterion for additionalFilter.", e);
            }

            criteria.add(afConjunction);
        }

        // FILTER
        int filterItemCount = hibernateFilter.getFilterItemCount();

        if (filterItemCount > 0) {
            // OR connected filter items
            if (hibernateFilter.getLogicalOperator().equals(LogicalOperator.OR)) {
                try {
                    Disjunction dis = Restrictions.disjunction();
                    for (int i = 0; i < filterItemCount; i++) {
                        HibernateFilterItem hfi = (HibernateFilterItem) hibernateFilter.getFilterItem(i);
                        if (hfi.getFieldName() != null && hfi.getFieldName().contains(".")) {
                            String ownFieldName = hfi.getFieldName().split("\\.")[0];

                            criteria.createCriteria(ownFieldName, ownFieldName);

                            // todo move outside
                            Criterion criterion = hfi.makeCriterion(clazz);
                            if (criterion != null) {
                                dis.add(criterion);
                            }

                        } else {
                            Criterion criterion = hfi.makeCriterion(clazz);
                            if (criterion != null) {
                                dis.add(criterion);
                            }
                        }
                    }
                    criteria.add(dis);

                } catch (Exception e) {
                    throw new ShogunDatabaseAccessException(
                            "(getDataByFilter)" + " Error while adding an OR connected filter: " + e.getMessage(),
                            e);
                }

            } else {
                // AND connected filter items
                try {
                    Conjunction conjunction = Restrictions.conjunction();
                    for (int i = 0; i < filterItemCount; i++) {
                        HibernateFilterItem hfi = (HibernateFilterItem) hibernateFilter.getFilterItem(i);
                        if (hfi.getFieldName() != null && hfi.getFieldName().contains(".")) {

                            String ownFieldName = hfi.getFieldName().split("\\.")[0];

                            criteria.createCriteria(ownFieldName, ownFieldName);

                            // todo move outside
                            Criterion criterion = hfi.makeCriterion(clazz);
                            if (criterion != null) {
                                conjunction.add(criterion);
                            }

                        } else {
                            Criterion criterion = hfi.makeCriterion(clazz);
                            if (criterion != null) {
                                conjunction.add(criterion);
                            }
                        }
                    }
                    criteria.add(conjunction);

                } catch (Exception e) {
                    throw new ShogunDatabaseAccessException(
                            "(getDataByFilter)" + " Error while adding an AND connected filter", e);
                }
            }
        }

        // Ok we're done creating the criteria.

        //      System.out.println("Querying for " + clazz.getSimpleName() + " with this SQL:");
        //      String niceSql = (new BasicFormatterImpl()).format(this.toSql(criteria));
        //      System.out.println(niceSql);

        // next we need to know whether we are being filtered with fields
        // because we then do NOT get a List of instances of BaseModelInterface.

        List<Object> list = null;

        if (isPlainModelRequest == false) {
            // We are filtered and will have to create a sane hashmap instead of
            // relying on the serilisation of BaseModelInterface classes.

            // Please beware that we can NOT setResultTransformer here,
            // otherwise we'll loose all but the first filtered field.

            if (fields == null && cleanedFieldNames.size() > 0) {
                fields = cleanedFieldNames;
            }
            // we dont really know what criteria.list() will return, can be List<Object> or List<Object[]>
            // will be determined later
            List<Object> rawListOfResults = criteria.list();
            List<Object> saneResultList = new ArrayList<Object>();
            for (Object rawRow : rawListOfResults) {

                Map<String, Object> newRowMap = new HashMap<String, Object>();
                int fieldIdx = 0;
                for (Iterator<String> fieldIter = fields.iterator(); fieldIter.hasNext();) {
                    String fieldName = fieldIter.next();
                    Object fieldVal = null;

                    if (rawRow != null) {
                        if (rawRow.getClass().isArray()) {
                            Object[] objArr = (Object[]) rawRow;
                            fieldVal = objArr[fieldIdx];
                        } else {
                            fieldVal = rawRow;
                        }
                    }

                    // store the pair in the newRowMap.
                    newRowMap.put(fieldName, fieldVal);

                    fieldIdx++;
                }
                // OK, one result row has been trasformed, store it back
                saneResultList.add(newRowMap);
            }

            // now overwrite the list we'll rerturn to the caller.
            list = saneResultList;
        } else {
            // We are NOT filtered, we can rely on the serialization process
            // that takes instances of ou models and transforms them
            // to (possibly huge) JSON structures.

            // Please beware that we can only setResultTransformer here,
            // otherwise we'd loose all but the first filtered field.

            // this ensures that no cartesian product is returned when
            // having sub objects, e.g. User <-> Modules
            criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

            // we need to set the fetch mode for all sets in our class, as most
            // of them are defined to be fetched lazily:

            criteria = this.setEagerFetchModeForCollections(criteria, clazz);

            list = criteria.list();
        }

        // Since we have modelled entities with sub entities with the lazy
        // fetching strategy, we have to check whether we should
        // initialize the needed fields.
        //
        // This will only happen
        //   * if we got a raw result,
        //   * and we weren't being filtered for only a subset of fields
        //   * and if we have been explicitly been told to go deep.

        // as we do not use LAZY at the moment, this will not be fired!
        //      if (list != null && deepInitialize == true && fields == null) {
        //         this.initializeDeep(list, hibernateSortObject.getMainClass());
        //      }

        return list;
    }

    private Criteria setEagerFetchModeForCollections(Criteria criteria, Class<?> clazz) {

        List<Field> fields = getAllFields(new ArrayList<Field>(), clazz);

        for (Field field : fields) {

            boolean isJsonIgnore = false;

            Method getterMethod;
            try {
                getterMethod = new PropertyDescriptor(field.getName(), clazz).getReadMethod();
                Annotation[] anoArr = getterMethod.getAnnotations();
                for (Annotation annotation : anoArr) {
                    if (annotation instanceof JsonIgnore) {
                        isJsonIgnore = true;
                    }
                }
            } catch (IntrospectionException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            if (!isJsonIgnore && field.getType().isAssignableFrom(Set.class)) {
                // yes, we have to set the fetch mode
                criteria.setFetchMode(field.getName(), FetchMode.JOIN);
            }
        }

        return criteria;
    }

    /**
     * TODO move to a better place or use existing functionality elsewhere.
     * TODO we have a very similar method in {@link HibernateFilterItem}.
     *
     * @param fields
     * @param type
     * @return
     * @throws NoSuchFieldException
     * @throws SecurityException
     * @throws IntrospectionException
     */
    public static List<Field> getAllFields(List<Field> fields, Class<?> type) {
        for (Field field : type.getDeclaredFields()) {

            // check if the filed is not a constant
            if (Modifier.isStatic(field.getModifiers()) == false
                    && Modifier.isFinal(field.getModifiers()) == false) {

                // now we check if the readmethod of the field
                // has NOT a transient annotation
                try {
                    PropertyDescriptor pd = new PropertyDescriptor(field.getName(), type);
                    Method readmethod = pd.getReadMethod();
                    Annotation[] annotationsArr = readmethod.getAnnotations();
                    if (annotationsArr.length == 0) {
                        fields.add(field);
                    } else {
                        for (Annotation annotation : annotationsArr) {
                            if (annotation.annotationType().equals(javax.persistence.Transient.class) == false) {
                                fields.add(field);
                            }
                        }
                    }
                } catch (IntrospectionException e) {
                    LOGGER.error("Trying to determine the getter for field '" + field.getName() + "' in "
                            + type.getSimpleName() + " threw IntrospectionException."
                            + " Is there a getter following the Java-Beans" + " Specification?");
                }
            }

        }

        if (type.getSuperclass() != null) {
            fields = getAllFields(fields, type.getSuperclass());
        }

        return fields;
    }

    /**
     * TODO turn the logic around... initializeDeep(List, Class) should make
     * many calls to this method, not the other way around.
     *
     *
     * @param obj
     * @param mainClass
     */
    protected void initializeDeep(Object obj, Class<?> mainClass) {
        List<Object> list = new ArrayList<Object>();
        list.add(obj);
        this.initializeDeepList(list, mainClass);
    }

    /**
     *
     * @param list
     * @param mainClass
     */
    protected void initializeDeepList(List<? extends Object> list, Class<?> mainClass) {
        List<Field> fields = getAllFields(new ArrayList<Field>(), mainClass);
        List<Method> methods = new ArrayList<Method>();

        for (Field field : fields) {
            if (field.getType().isAssignableFrom(Set.class)) {
                // yes, we have to initialize this field via its getter
                Method method = null;
                try {
                    method = new PropertyDescriptor(field.getName(), mainClass).getReadMethod();
                } catch (IntrospectionException e) {
                    LOGGER.error("Failed to determine getter for field '" + field.getName() + "' of class '"
                            + mainClass.getSimpleName() + "'.");
                }
                methods.add(method);
            }
        }

        for (Iterator<Object> iterator = (Iterator<Object>) list.iterator(); iterator.hasNext();) {
            Object obj = iterator.next();
            if (obj == null) {
                continue;
            }
            for (Method method : methods) {
                String errMsg = "Failed to invoke getter '" + method.getName() + "' of class '"
                        + mainClass.getSimpleName() + "': ";
                try {
                    Hibernate.initialize(method.invoke(obj));
                } catch (HibernateException e) {
                    LOGGER.error(errMsg + " HibernateException '" + e.getMessage() + "'.");
                } catch (IllegalArgumentException e) {
                    LOGGER.error(errMsg + " IllegalArgumentException '" + e.getMessage() + "'.");
                } catch (IllegalAccessException e) {
                    LOGGER.error(errMsg + " IllegalAccessException '" + e.getMessage() + "'.");
                } catch (InvocationTargetException e) {
                    LOGGER.error(errMsg + " InvocationTargetException '" + e.getMessage() + "'.");
                }
            }
        }
    }

    /**
     * Method returns distinct field values of a defined entity type. <br>
     * For example: retrieving all streets of the user table without
     * duplicated values
     * <br>
     * <br>
     * You can decide whether the returned field values should be
     * filtered by the current group logged in in the session or
     * if all values are returned
     *
     * @param clazz The model which should be filtered as Class object
     * @param field The field which should be returned
     * @param groupDependent flag to decide whether it is filtered by the
     *                    current group
     *
     * @return List of String representations of the values of the desired field
     *
     */
    public List<String> getDistinctEntitiesByField(Class<?> clazz, String field, boolean groupDependent) {

        Criteria criteria = this.sessionFactory.getCurrentSession().createCriteria(clazz);

        criteria.setProjection(
                Projections.distinct(Projections.projectionList().add(Projections.property(field), field)));

        // this ensures that no cartesian product is returned when
        // having sub objects, e.g. User <-> Modules
        criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

        return criteria.list();
    }

    /**
     * Returns an list of all Objects of a given Entity class,
     * which is specified by the parameter clazz<br>
     * For example: Get a all Modules which are stored in tbl_module <br>
     *
     * @param clazz The class of the entity to be used
     * @return The object list fulfilling the filter request
     */
    @SuppressWarnings("unchecked")
    public List<Object> getAllEntities(Class<?> clazz, boolean... initializeDeep) {

        boolean initializeDeeply = initializeDeep.length > 0 ? initializeDeep[0] : false;

        Criteria criteria = null;
        criteria = this.sessionFactory.getCurrentSession().createCriteria(clazz);

        // this ensures that no cartesian product is returned when
        // having sub objects, e.g. User <-> Modules
        criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

        List<Object> resultSetlist = (List<Object>) criteria.list();

        if (initializeDeeply == true) {
            this.initializeDeepList(resultSetlist, clazz);
        }

        return resultSetlist;
    }

    /**
     * Determines all records of the passed entity owned by the
     * currently logged in user.
     *
     * @param clazz
     * @return
     */
    public List<Object> getAllEntitiesByUser(Class<?> clazz) {

        // get user ID of logged in User and check if there is the
        // and it to the query as WHERE
        Criteria criteria = this.sessionFactory.getCurrentSession().createCriteria(clazz);
        int userId = this.getUserIdFromSession();
        criteria.add(Restrictions.eq("user_id", userId));
        List<Object> records = criteria.list();

        // this ensures that no cartesian product is returned when
        // having sub objects, e.g. User <-> Modules
        criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

        return records;
    }

    /**
     *
     * @param id
     * @param clazz
     * @return
     */
    public Object getEntityById(int id, Class<?> clazz) {
        return this.getEntityById(id, clazz, true);
    }

    /**
     * Returns an object of a certain entity defined by its ID.
     *
     * @param id the ID of the object to query
     * @param clazz The entity to be queried
     * @return the object matching the passed entity and the passed ID
     */
    public Object getEntityById(int id, Class<?> clazz, boolean initializeDeep) {

        Criteria criteria = null;
        criteria = this.sessionFactory.getCurrentSession().createCriteria(clazz);
        criteria.add(Restrictions.eq("id", id));
        // we expect a single record or null
        Object result = criteria.uniqueResult();

        if (initializeDeep) {
            this.initializeDeep(result, clazz);
        }

        return result;
    }

    // method used to keep the old behaviour, which means
    // getting entities without initializing lazy fields
    public List<? extends Object> getEntitiesByIds(Object[] values, Class<?> clazz) {
        return this.getEntitiesByIds(values, clazz, null);
    }

    public List<? extends Object> getEntitiesByIds(Set<?> values, Class<?> clazz) {
        Object[] objectValues = new Object[values.size()];
        int i = 0;
        for (Iterator<?> iterator = values.iterator(); iterator.hasNext();) {
            Object object = (Object) iterator.next();
            objectValues[i] = object;
            i++;
        }
        return this.getEntitiesByIds(objectValues, clazz, null);
    }

    /**
     * Returns a list of object of a certain entity defined by its ID.
     *
     * @param values a list of IDs as array
     * @param clazz The entity to be queried
     * @return the objects matching the passed entity and the passed IDs
     */
    @SuppressWarnings("unchecked")
    public List<? extends Object> getEntitiesByIds(Object[] values, Class<?> clazz, String[] eagerfields) {
        final int maxInElems = 999;
        Criteria criteria = null;
        criteria = this.sessionFactory.getCurrentSession().createCriteria(clazz);

        if (eagerfields != null && eagerfields.length > 0) {
            for (String field : eagerfields) {
                criteria.setFetchMode(field, FetchMode.JOIN);
            }
        }

        if (values.length > 0 && values.length <= maxInElems) {
            criteria.add(Restrictions.in("id", values));
        } else if (values.length > maxInElems) {
            List<Criterion> listOfInRestrictions = new ArrayList<Criterion>();
            int numSubArrays = (int) Math.ceil(((double) values.length) / ((double) maxInElems));
            int start = 0;
            for (int i = 0; i < numSubArrays; i++) {
                // int start = i * maxInElems;
                int end = (i + 1) * maxInElems;
                if (end > values.length) {
                    end = values.length;
                }
                Object[] subArr = Arrays.copyOfRange(values, start, end);
                listOfInRestrictions.add(Restrictions.in("id", subArr));
                start = end;
            }
            Disjunction disj = Restrictions.disjunction();
            for (Criterion restrictions : listOfInRestrictions) {
                disj.add(restrictions);
            }
            criteria.add(disj);
        } else {
            // we add a restriction that can never be fullfilled
            // this is the case when e.g. an empty object has been passed
            criteria.add(Restrictions.sqlRestriction("1 = 2"));
        }

        // this ensures that no cartesian product is returned when
        // having sub objects, e.g. User <-> Modules
        criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

        return criteria.list();
    }

    /**
     * Returns all entites besides the ones passed (as IDs).
     *
     * @param values a list of IDs as array
     * @param clazz The entity to be queried
     * @return the objects matching the passed entity and NOT matching the passed IDs
     */
    @SuppressWarnings("unchecked")
    public List<? extends Object> getEntitiesByExcludingIds(Object[] values, Class<?> clazz) {

        Criteria criteria = null;
        criteria = this.sessionFactory.getCurrentSession().createCriteria(clazz);

        if (values.length > 0) {
            criteria.add(Restrictions.not(Restrictions.in("id", values)));
        }

        // this ensures that no cartesian product is returned when
        // having sub objects, e.g. User <-> Modules
        criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

        return criteria.list();
    }

    /**
     * Returns an Object by a String comparison of a specified field
     * <br>
     * For example: Get a country by a given name
     * (Return the country where the name equals GERMANY)
     * <br>
     * <br>
     * NOTE: The operator used is 'ILIKE'
     * <br>
     * <br>
     *
     * @param clazz The class of the object model to be used
     * @param fieldname the column which should be filtered
     * @param value the value to filter
     *
     * @return The object fulfilling the filter request
     * @throws ShogunDatabaseAccessException
     */
    public Object getEntityByStringField(Class<?> clazz, String fieldname, String value)
            throws ShogunDatabaseAccessException {

        HashMap<String, String> fieldsAndValues = new HashMap<String, String>();
        fieldsAndValues.put(fieldname, value);

        return this.getEntityByStringFields(clazz, fieldsAndValues);
    }

    /**
     * Returns an Object by a String comparison of specified fields <br>
     * For example: Get a record by a given name
     * (Return the city where the name equals NEUSTADT) and its postcode.
     * <br>
     * <br>
     * NOTE: The operator used is 'ILIKE'
     * <br>
     * <br>
     * TODO: add an explicit connector (AND/OR, no usage of default because
     *       of readability)
     * TODO: exception handling
     *
     * @param clazz
     * @param fieldsAndValues
     *
     * @return The object fulfilling the filter request
     * @throws ShogunDatabaseAccessException
     */
    public Object getEntityByStringFields(Class clazz, HashMap<String, String> fieldsAndValues)
            throws ShogunDatabaseAccessException {
        Object returnObject = null;
        List<Object> listOfEntities = this.getEntitiesByStringFields(clazz, fieldsAndValues);
        if (listOfEntities != null && listOfEntities.size() > 0) {
            returnObject = listOfEntities.get(0);
        }
        return returnObject;
    }

    /**
     * Returns a set of Objects from database by a Integer comparison
     * of a specified field <br>
     *
     * @param clazz
     * @param fieldname
     * @param value
     * @return
     */
    public List<Object> getEntitiesByIntegerField(Class<?> clazz, String fieldname, Integer value) {

        Criteria criteria = this.sessionFactory.getCurrentSession().createCriteria(clazz);
        criteria.add(Restrictions.eq(fieldname, value));

        // this ensures that no cartesian product is returned when
        // having sub objects, e.g. User <-> Modules
        criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

        return criteria.list();
    }

    /**
     * Returns a set of objects from database by a boolean comparison
     * with a specified field. <br>
     *
     * @param clazz The class of the object model to be used
     * @param fieldname the column which should be filtered
     * @param value the value to filter (type Boolean)
     * @return The objects fulfilling the filter request
     */
    @SuppressWarnings("unchecked")
    public <T> List<T> getEntitiesByBooleanField(Class<T> clazz, String fieldname, Boolean value) {

        Criteria criteria = this.sessionFactory.getCurrentSession().createCriteria(clazz);
        criteria.add(Restrictions.eq(fieldname, value));

        // this ensures that no cartesian product is returned when
        // having sub objects, e.g. User <-> Modules
        criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

        return (List<T>) criteria.list();
    }

    /**
     * Returns a list of entities where the given fields match their respective
     * values.
     *
     * <p>NOTE: The operator used is <code>'ILIKE'</code><p>
     *
     * @param clazz
     * @param fieldsAndValues
     * @return
     * @throws ShogunDatabaseAccessException
     */
    @SuppressWarnings("unchecked")
    public <T extends BaseModelInterface> List<T> getEntitiesByStringFields(Class<T> clazz,
            HashMap<String, String> fieldsAndValues) throws ShogunDatabaseAccessException {
        Criteria criteria = null;
        List<T> returnList = null;
        try {
            criteria = this.sessionFactory.getCurrentSession().createCriteria(clazz);
            for (Iterator<String> iter = fieldsAndValues.keySet().iterator(); iter.hasNext();) {
                String fieldname = iter.next();
                String value = fieldsAndValues.get(fieldname);
                criteria.add(Restrictions.ilike(fieldname, value));
            }

            // this ensures that no cartesian product is returned when
            // having sub objects, e.g. User <-> Modules
            criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

            returnList = (List<T>) criteria.list();

        } catch (Exception e) {
            throw new ShogunDatabaseAccessException(
                    "Error getting entities of class " + clazz.getSimpleName() + " by text fields.", e);
        }

        return returnList;
    }

    /**
     * Returns a list of entities where the given field matches the given value
     *
     * <p>NOTE: The operator used is <code>'ILIKE'</code><p>
     *
     * <p>This is a utility method to easily get a list of all entities
     * matching a string comparison. Will construct a HashMap of the given field
     * and value and then call
     * {@link DatabaseDao#getEntitiesByStringFields(Class, HashMap)} to fetch
     * the list of matching entities.</p>
     *
     * @param clazz
     * @param fieldsAndValues
     * @return
     * @throws ShogunDatabaseAccessException
     */
    public <T extends BaseModelInterface> List<T> getEntitiesByStringField(Class<T> clazz, String field,
            String value) throws ShogunDatabaseAccessException {
        HashMap<String, String> fieldsAndValues = new HashMap<String, String>();
        fieldsAndValues.put(field, value);
        return this.getEntitiesByStringFields(clazz, fieldsAndValues);
    }

    /**
     * Returns the {@linkplain MapLayer}s owned by the given {@linkplain User}
     *
     * @param user
     *            the {@linkplain User} object owning the {@linkplain MapLayer}
     *            objects to be returned
     * @return a {@linkplain List} of {@linkplain MapLayer} objects owned by the
     *         given {@linkplain User}
     */
    public List<MapLayer> getOwnedMapLayers(User user) {

        Criteria criteria = this.getSessionFactory().getCurrentSession().createCriteria(MapLayer.class);

        criteria.add(Restrictions.eq("owner", user));
        // this ensures that no cartesian product is returned when
        // having sub objects, e.g. User <-> Modules
        criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

        return (List<MapLayer>) criteria.list();
    }

    /**
     * Creates a record of a given Entity in the database
     *
     * @param entityClass Entity class of the new object
     * @param objToCreate the new object to be created in the DB
     * @return the created object
     */
    public Object createEntity(String entityClass, Object objToCreate) {

        this.sessionFactory.getCurrentSession().save(entityClass, objToCreate);

        return objToCreate;
    }

    /**
     * Creates a record of a given Entity in the database. This method will return the
     * newly created object, whereas the createEntity(String entityClass,
     * Object objToCreate) will return the originally passed object.
     *
     * @param objToCreate the new object to be created in the DB
     * @return the object that was created in the database
     * @throws ShogunDatabaseAccessException
     */
    public <T> T createEntity(T objToCreate) {

        Class<? extends Object> clazz = objToCreate.getClass();
        Object createdObjectId = this.getSessionFactory().getCurrentSession().save(clazz.getSimpleName(),
                objToCreate);
        return (T) this.getEntityById((Integer) createdObjectId, clazz);
    }

    /**
     * Creates records for the given entities in the database. This method will
     * return the newly created objects.
     *
     * @param objsToCreate the new objects to be created in the DB
     * @return the objects that were created in the database
     * @throws ShogunDatabaseAccessException
     */
    @Transactional
    public <T extends BaseModel> List<T> createEntities(List<T> objsToCreate) {
        List<T> createdObjs = new ArrayList<T>();
        for (T t : objsToCreate) {
            createdObjs.add(this.createEntity(t));
        }
        return createdObjs;
    }

    /**
     * Creates or updates a record of a given Entity in the database. <br>
     * Method checks automatically if the passed object has to be created or
     * updated.
     *
     * @param entityClass Entity class of the object
     * @param objToCreateOrUpdate the new object to be created/updated in the DB
     * @return the created/updated object
     */
    public Object createOrUpdateEntity(String entityClass, Object objToCreateOrUpdate) {

        this.sessionFactory.getCurrentSession().saveOrUpdate(entityClass, objToCreateOrUpdate);

        return objToCreateOrUpdate;
    }

    /**
     * Updates a record of a given Entity in the database with the passed one
     *
     * @param entityClass Entity class of the object
     * @param objToUpdate the object to be updated in the DB
     * @return the updated object
     */
    public Object updateEntity(String entityClass, Object objToUpdate) {

        Object updatedObject = this.sessionFactory.getCurrentSession().merge(entityClass, objToUpdate);
        this.sessionFactory.getCurrentSession().flush();

        return updatedObject;
    }

    /**
     * Deletes a record of a given Entity in the database. The record to be
     * deleted is defined by its ID.
     *
     * @param clazz Entity class of the object to be deleted
     * @param id the ID of the record to be deleted
     */
    public void deleteEntity(Class<?> clazz, Integer id) {

        // delete the object record
        Object record = this.sessionFactory.getCurrentSession().load(clazz, id);
        this.sessionFactory.getCurrentSession().delete(record);
    }

    /**
     * Deletes a record of a given entity-class in the database.
     * The record to be deleted is given by its object representation.
     *
     * @param <T> Template class, here the class of the object to be deleted
     * @param clazz Entity class of the object to be deleted
     * @param objectToDelete the instance to be deleted from database
     */
    public <T> void deleteEntity(Class<T> clazz, BaseModelInterface objectToDelete) {
        this.sessionFactory.getCurrentSession().delete(objectToDelete);
    }

    /**
     * Deletes a record of a given Entity in the database. The record to be
     * deleted is defined by a certain value of one its data columns.
     * (For example WHERE name='Peter')
     *
     * @param clazz Entity class of the object to be deleted
     * @param column Column name which is used to determine the record
     * @param value The value which is used to determine the record
     */
    public void deleteEntityByValue(Class<?> clazz, String column, String value) {

        Criteria criteria = this.sessionFactory.getCurrentSession().createCriteria(clazz);
        criteria.add(Restrictions.eq(column, value));
        List<Object> records = criteria.list();

        Object record = null;
        if (records.size() == 1) {
            record = records.get(0);
        }

        this.sessionFactory.getCurrentSession().delete(record);
    }

    /**
     * Delete an object of an entity NOT regarding the logged
     * in user is in the same group
     *
     * @param clazz Class object defining the entity
     * @param id the id to delete
     *
     * @throws Exception
     */
    public void deleteEntityGroupDependent(Class<?> clazz, Integer id) throws ShogunDatabaseAccessException {

        // get group ID of logged in User and check if there is the
        // user to be deleted is a child of the current group
        Criteria criteria = this.sessionFactory.getCurrentSession().createCriteria(clazz);
        criteria.add(Restrictions.eq("id", id));

        List<Integer> groupIdsOfSessionUser = this.getGroupIdsFromSession();

        if (groupIdsOfSessionUser.size() > 0) {
            criteria.createCriteria("groups").add(Restrictions.in("id", groupIdsOfSessionUser));
        }

        List<Object> records = criteria.list();

        // group check --> user allowed to delete this instance?
        if (records.size() > 0) {

            // delete the instance record in DB
            this.deleteEntity(clazz, id);

        } else {
            throw new ShogunDatabaseAccessException(
                    "The " + clazz.getSimpleName() + " to be deleted is not accessible for the logged in user!");
        }
    }

    /**
     * Deletes all entities in the given list.
     *
     * <p>This method is supposed to be called with lists of instances which
     * implement the {@link BaseModelInterface}. This qualifies all
     * subclasses of either {@link BaseModel} or {@link BaseModelInheritance}
     * as valid list items.</p>
     *
     * <p>If the needed criteria is met, this method will call
     * {@link DatabaseDao#deleteEntity(Class, Integer)} for every member of the
     * list.</p>
     *
     * @param entities
     */
    public void deleteEntities(List<? extends BaseModelInterface> entities) {
        // ignore empty lists and null
        if (entities != null && entities.size() > 0) {
            for (BaseModelInterface entity : entities) {
                if (entity != null) {
                    // get the class of the current entity, we need to do it in
                    // the loop as the originally passed list can contain
                    // instances of more than one concrete class.
                    Class<? extends BaseModelInterface> clazz = entity.getClass();
                    this.deleteEntity(clazz, entity.getId());
                }
            }
        }
    }

    // ---------------------------------------------------------------------------

    // USER RELATED STUFF

    // ---------------------------------------------------------------------------

    /**
     * Returns the {@link User} object defined by the passed user name.
     *
     * @param name the user_name of the record in the database
     * @return
     * @throws ShogunDatabaseAccessException
     */
    @SuppressWarnings("unchecked")
    public List<User> getUserByName(String name) throws ShogunDatabaseAccessException {

        Criteria criteria = null;

        try {

            criteria = this.sessionFactory.getCurrentSession().createCriteria(User.class);

            criteria.add(Restrictions.eq("user_name", name));

        } catch (Exception e) {
            throw new ShogunDatabaseAccessException("Error while getting User by name: " + name, e);
        }

        // we have to ensure that the modules are distinct
        // @see http://docs.jboss.org/hibernate/orm/3.6/javadocs/org/hibernate/Criteria.html#createAlias(java.lang.String, java.lang.String, int)
        //      criteria.createAlias("modules", "module", CriteriaSpecification.INNER_JOIN);

        criteria.setFetchMode("mapLayers", FetchMode.JOIN);

        // this ensures that no cartesian product is returned when
        // having sub objects, e.g. User <-> Modules
        criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

        return criteria.list();
    }

    /**
     * Returns the {@link User} object defined by the passed user name.
     *
     * @param name the user_name of the record in the database
     * @return
     * @throws ShogunDatabaseAccessException
     */
    public User getUserByName(String name, String additionalCriteriaPath, Criterion additionalCriterion)
            throws ShogunDatabaseAccessException {

        Criteria criteria = null;

        try {

            criteria = this.sessionFactory.getCurrentSession().createCriteria(User.class);

            // add additional restrictions like
            // where user is in group with ID xy
            if (additionalCriteriaPath != null && additionalCriteriaPath.isEmpty() == false
                    && additionalCriterion != null) {

                criteria.createCriteria(additionalCriteriaPath).add(additionalCriterion);
            }

            criteria.add(Restrictions.ilike("user_name", name));

        } catch (Exception e) {
            throw new ShogunDatabaseAccessException("Error while getting User by name: " + name, e);
        }

        // we have to ensure that the modules are distinct
        // @see http://docs.jboss.org/hibernate/orm/3.6/javadocs/org/hibernate/Criteria.html#createAlias(java.lang.String, java.lang.String, int)
        criteria.createAlias("modules", "module", CriteriaSpecification.INNER_JOIN);

        // this ensures that no cartesian product is returned when
        // having sub objects, e.g. User <-> Modules
        criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

        return (User) criteria.uniqueResult();
    }

    /**
     * Returns a User object defined by its ID in the database.
     * <br>
     * <br>
     * Can be filtered by a further criterion (additionalCriterion) put on a
     * specific path (additionalCriteriaPath). This could model a filter like
     * <br>
     * <code>
     * criteria.createCriteria("groups")
        .add(Restrictions.in("id", groupIdsOfSessionUser));
     * <code>
     *
     * @param id the ID of the {@link User} to query
     * @param additionalCriteriaPath a criteria path to put add a further criterion
     * @param additionalCriterion additional criterion to filter query
     * @return the {@link User} object matching the query
     */
    public User getUserById(int id, String additionalCriteriaPath, Criterion additionalCriterion) {

        Criteria criteria = this.sessionFactory.getCurrentSession().createCriteria(User.class);

        // add additional restrictions like
        // where user is in group with ID xy
        if (additionalCriteriaPath != null && additionalCriteriaPath.isEmpty() == false
                && additionalCriterion != null) {

            criteria.createCriteria(additionalCriteriaPath).add(additionalCriterion);
        }

        // filter on ID
        criteria.add(Restrictions.eq("id", id));

        return (User) criteria.uniqueResult();
    }

    /**
     * Create a new User on the database or
     * update User
     */
    public User saveUser(User user, boolean isNew) {

        // check if update or create
        if (isNew == false) {
            // it is an update
            this.sessionFactory.getCurrentSession().merge(user);
            this.sessionFactory.getCurrentSession().flush();
        } else {
            // you are saving a new one
            this.sessionFactory.getCurrentSession().save(user);
        }

        return user;
    }

    /**
     * Creates a new {@link User} object in the database.
     * The roles, modules and granted layers of the user depend on the groups
     * he is assigned to. We dont have to care about this here.
     *
     * @param user the User object to create
     * @param setSessionGroup flag controls if the current user group should be set to new user
     * @return
     * @throws Exception
     */
    public User createUser(User user, boolean setSessionGroup) throws ShogunDatabaseAccessException {

        try {

            this.sessionFactory.getCurrentSession().save(user);

            // TODO NB: What is the sense of setSessionGroup?
            // Due to the use of getFirstGroupObjectFromSessionUser()
            // it seems that the user is already associated with the
            // group that we will update in the following code.
            // So why do we do this?
            if (setSessionGroup == true) {

                try {

                    // add the saved user to current session group
                    Group sessionGroup = this.getFirstGroupObjectFromSessionUser();

                    if (sessionGroup != null) {

                        sessionGroup.getUsers().add(user);

                        // persist the changes of the group object
                        this.updateEntity("Group", sessionGroup);
                    }

                } catch (Exception e) {
                    throw new ShogunDatabaseAccessException(
                            "Error adding the saved user to current session group. " + e.getMessage());
                }
            }

        } catch (Exception e) {
            throw new ShogunDatabaseAccessException("Error creating User with roles. ", e);
        }

        return user;
    }

    /**
     * TODO: this method should call the internal updateEntity-method!
     */
    public User updateUser(User user) {

        this.sessionFactory.getCurrentSession().merge(user);
        this.sessionFactory.getCurrentSession().flush();

        return user;
    }

    /**
     * Deletes a user instance given by its user id. <br>
     *
     * Here it is NOT checked if the logged in group matches the one stored
     * for the user. This method is intended to be called by the super user
     * with the role ROLE_SUPERADMIN
     *
     * @param id
     * @throws Exception
     */
    public void deleteUser(int id) throws ShogunDatabaseAccessException {

        Criteria criteria = this.sessionFactory.getCurrentSession().createCriteria(User.class);
        criteria.add(Restrictions.eq("id", id));
        User userToDelete = (User) criteria.uniqueResult();

        if (userToDelete != null) {

            // delete the user instance
            this.deleteEntity(User.class, userToDelete.getId());

        } else {
            throw new ShogunDatabaseAccessException("No User found with ID " + id);
        }
    }

    /**
     * Empties the session completely.
     * <br><br>
     * <b>BE CAREFULL in use with this!</b>
     *
     * @see http://www.torsten-horn.de/techdocs/java-hibernate.htm#First-Level-Cache
     */
    public void clearSession() {
        this.sessionFactory.getCurrentSession().clear();
    }

    /**
     * Return the total count of a request.
     * Additionally a global AND filter could be passed.
     *
     * @param hibernateFilter the {@link HibernateFilter} object which filters the amount of records
     * @param hibernateAdditionalFilter an additional {@link HibernateFilter} connected with a global AND
     *   to the rest of the query filter. Most of the cases not needed (pass null)
     * @return the amount of matching records
     * @throws ShogunDatabaseAccessException
     */
    public long getTotal(HibernateFilter hibernateFilter, HibernateFilter hibernateAdditionalFilter)
            throws ShogunDatabaseAccessException {

        Criteria criteria = null;

        try {
            criteria = this.sessionFactory.getCurrentSession().createCriteria(hibernateFilter.getMainClass());

        } catch (Exception e) {
            throw new ShogunDatabaseAccessException(
                    "The requested model " + hibernateFilter.getMainClass() + " is not defined ", e);
        }

        /*
         * check additional filters:
         *
         * These are being sent from a client and represent AND conditions to be
         * applied globally.
         * The use-case that lead us to implement the additionalFilter is
         * the requirement that users are allowed to have both a AND and an OR
         * filter.
         * Usually one would implement this requirement with nested logical
         * filters
         */
        if (hibernateAdditionalFilter != null) {
            Conjunction afConjunction = Restrictions.conjunction();
            Criterion afCriterion = null;
            try {
                HibernateFilterItem hfi = (HibernateFilterItem) hibernateAdditionalFilter.getFilterItem(0);
                afCriterion = hfi.makeCriterion(hibernateAdditionalFilter.getMainClass());
                afConjunction.add(afCriterion);
            } catch (Exception e) {
                throw new ShogunDatabaseAccessException("Error creating a criterion for additionalFilter.", e);
            }

            criteria.add(afConjunction);
        }

        criteria.setProjection(Projections.rowCount());

        if (hibernateFilter != null) {

            int filterItemCount = hibernateFilter.getFilterItemCount();

            // FILTER
            // OR connected filter items
            if (hibernateFilter.getLogicalOperator().equals(LogicalOperator.OR)) {
                try {
                    Disjunction dis = Restrictions.disjunction();
                    for (int i = 0; i < filterItemCount; i++) {
                        HibernateFilterItem hfi = (HibernateFilterItem) hibernateFilter.getFilterItem(i);
                        if (hfi.getFieldName() != null && hfi.getFieldName().contains(".")) {
                            String ownFieldName = hfi.getFieldName().split("\\.")[0];
                            criteria.createCriteria(ownFieldName, ownFieldName);
                            // todo move outside
                            Criterion criterion = hfi.makeCriterion(hibernateFilter.getMainClass());
                            if (criterion != null) {
                                dis.add(criterion);
                            }

                        } else {
                            Criterion criterion = hfi.makeCriterion(hibernateFilter.getMainClass());
                            if (criterion != null) {
                                dis.add(criterion);
                            }
                        }
                    }
                    criteria.add(dis);
                } catch (Exception e) {
                    throw new ShogunDatabaseAccessException(
                            "(getTotal) Error" + " while adding an OR connected filter: " + e.getMessage(), e);
                }

            } else {

                try {
                    for (int i = 0; i < filterItemCount; i++) {
                        HibernateFilterItem hfi = (HibernateFilterItem) hibernateFilter.getFilterItem(i);
                        if (hfi.getFieldName() != null && hfi.getFieldName().contains(".")) {
                            String ownFieldName = hfi.getFieldName().split("\\.")[0];

                            criteria.createCriteria(ownFieldName, ownFieldName);

                            // todo move outside
                            Criterion criterion = hfi.makeCriterion(hibernateFilter.getMainClass());
                            if (criterion != null) {
                                criteria.add(criterion);
                            }

                        } else {
                            Criterion criterion = hfi.makeCriterion(hibernateFilter.getMainClass());

                            if (criterion != null) {
                                criteria.add(criterion);
                            }
                        }
                    }

                } catch (Exception e) {
                    throw new ShogunDatabaseAccessException(
                            "(getTotal) Error" + " while combining criteria with AND", e);
                }
            }
        }
        List<?> totalList = criteria.list();

        return (Long) totalList.get(0);
    }

    // ---------------------------------------------------------------------------

    // SESSION RELATED STUFF

    // ---------------------------------------------------------------------------

    /**
     * Determines the name of the logged in user from Security Context
     *
     * @return the name of the logged in user or NULL if not found
     */
    public String getUserNameFromSession() {
        // get the authorization context, incl. user name
        Authentication authResult = SecurityContextHolder.getContext().getAuthentication();

        if (authResult != null) {
            return authResult.getName();
        } else {
            return null;
        }
    }

    /**
     * Determines the ID of the logged in user from Security Context.
     *
     * TODO this is the only method in the dbDao that is secured through the
     *      @PreAuthorize annotation. It is possibly secured since
     *      {@link UserAdministrationController#getLoggedInUserId()} directly
     *      calls into the database dao instead of using the appropriate
     *      {@link UserAdministrationService}. We might consider adding a
     *      dedicated method to that service.
     *
     * @return the name of the logged in user or NULL if not found
     */
    @PreAuthorize("hasAnyRole('ROLE_USER', 'ROLE_ADMIN', 'ROLE_SUPERADMIN')")
    public Integer getUserIdFromSession() {

        String username = this.getUserNameFromSession();

        if (username != null) {

            Criteria criteria = this.sessionFactory.getCurrentSession().createCriteria(User.class);

            criteria.setProjection(Projections.property("id"));
            criteria.add(Restrictions.eq("user_name", username));

            Integer id = (Integer) criteria.list().get(0);

            return id;
        } else {
            return null;
        }
    }

    /**
     * Returns the {@link User} object of the logged in user
     *
     * @return the {@link User} object of the logged in user or NULL if not found
     */
    public User getUserObjectFromSession() {

        LOGGER.debug("Starting to get user object from session.");

        // get the authorization context, incl. user name
        Authentication authResult = SecurityContextHolder.getContext().getAuthentication();

        LOGGER.debug("Got authResult: " + authResult.getName());
        LOGGER.debug("Creating criteria now to get the user.");

        Criteria criteria = this.sessionFactory.getCurrentSession().createCriteria(User.class);

        criteria.add(Restrictions.eq("user_name", authResult.getName()));

        LOGGER.debug("Requesting user now");

        User u = (User) criteria.uniqueResult();

        LOGGER.debug("Got user from session: " + u.getId());

        return u;

    }

    /**
     * Checks whether the user identified by given userName belongs to at least
     * one group which has the given roleName.
     *
     * @param userName
     * @param roleName
     * @return
     */
    public boolean hasUserRoleByUsernameAndRolename(String userName, String roleName) {
        boolean hasRole = false;

        Criteria criteria = this.getSessionFactory().getCurrentSession().createCriteria(User.class);
        criteria.add(Restrictions.eq("user_name", userName));
        criteria.createCriteria("groups", "g");
        criteria.createCriteria("g.roles", "r");
        criteria.add(Restrictions.eq("r.name", roleName));
        criteria.setProjection(Projections.rowCount());

        long rowCnt = 0;

        try {
            rowCnt = ((Number) criteria.uniqueResult()).longValue();
            hasRole = (rowCnt > 0l);
        } catch (HibernateException he) {
            LOGGER.error("Failed to determine whether" + " user with username '" + userName + "'"
                    + " has the role with name '" + roleName + "':" + " " + he.getMessage());
        }

        return hasRole;
    }

    /**
     * Returns the IDs of all groups ID of the logged in user
     *
     * @return
     * @throws ShogunDatabaseAccessException
     * @throws Exception
     */
    public List<Integer> getGroupIdsFromSession() {

        Set<Group> sessionGroups = this.getGroupObjectsFromSessionUser();
        List<Integer> groupIdsOfSessionUser = new ArrayList<Integer>();

        for (Group group : sessionGroups) {
            groupIdsOfSessionUser.add(group.getId());
        }

        return groupIdsOfSessionUser;
    }

    /**
     * Returns all {@link Group} objects of the logged in user.
     *
     * @return the set of {@link Group} objects of the logged in user
     *          or NULL if not found
     */
    public Set<Group> getGroupObjectsFromSessionUser() {

        // get the logged-in user
        User sessionUser = this.getUserObjectFromSession();

        // get a set of the groups of the logged-in user
        if (sessionUser != null) {
            return sessionUser.getGroups();
        } else {
            return null;
        }
    }

    /**
     * Returns the first {@link Group} object of the logged in user.
     *
     * @return the first {@link Group} object of the logged in user or
     *          NULL if not found
     */
    public Group getFirstGroupObjectFromSessionUser() {

        // get the logged-in user
        User sessionUser = this.getUserObjectFromSession();

        if (sessionUser != null && sessionUser.getGroups() != null) {
            return sessionUser.getGroups().iterator().next();
        } else {
            return null;
        }
    }

    /**
     * Determines if the logged in User is a SuperAdmin.
     *
     * @return flag SuperAdmin=true/false
     */
    public boolean isSuperAdmin() {
        // get the logged-in user and check if he has the SuperAdmin role
        User sessionUser = this.getUserObjectFromSession();
        return sessionUser.hasSuperAdminRole();
    }

    /**
     * Helper function to print out the SQL from a criteria object
     *
     * @param criteria
     * @return
     */
    private String toSql(Criteria criteria) {
        try {
            CriteriaImpl c = (CriteriaImpl) criteria;
            SessionImpl s = (SessionImpl) c.getSession();
            SessionFactoryImplementor factory = (SessionFactoryImplementor) s.getSessionFactory();
            String[] implementors = factory.getImplementors(c.getEntityOrClassName());

            CriteriaLoader loader = new CriteriaLoader(
                    (OuterJoinLoadable) factory.getEntityPersister(implementors[0]), factory, c, implementors[0],
                    s.getLoadQueryInfluencers());
            Field f = OuterJoinLoader.class.getDeclaredField("sql");
            f.setAccessible(true);
            return (String) f.get(loader);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Sets the SessionFactory of Hibernate via Spring's DI
     *
     * @param sessionFactory
     */
    @Autowired
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    /**
     * @return the session factory of Hibernate
     */
    public SessionFactory getSessionFactory() {
        return this.sessionFactory;
    }

}