org.opencms.db.generic.CmsSubscriptionDriver.java Source code

Java tutorial

Introduction

Here is the source code for org.opencms.db.generic.CmsSubscriptionDriver.java

Source

/*
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.db.generic;

import org.opencms.configuration.CmsConfigurationManager;
import org.opencms.configuration.CmsParameterConfiguration;
import org.opencms.db.CmsDbConsistencyException;
import org.opencms.db.CmsDbContext;
import org.opencms.db.CmsDbSqlException;
import org.opencms.db.CmsDriverManager;
import org.opencms.db.CmsPreparedStatementLongParameter;
import org.opencms.db.CmsPreparedStatementStringParameter;
import org.opencms.db.CmsSubscriptionFilter;
import org.opencms.db.CmsSubscriptionReadMode;
import org.opencms.db.CmsVisitEntry;
import org.opencms.db.CmsVisitEntryFilter;
import org.opencms.db.CmsVisitedByFilter;
import org.opencms.db.I_CmsDriver;
import org.opencms.db.I_CmsPreparedStatementParameter;
import org.opencms.db.I_CmsSubscriptionDriver;
import org.opencms.file.CmsDataAccessException;
import org.opencms.file.CmsGroup;
import org.opencms.file.CmsResource;
import org.opencms.file.CmsUser;
import org.opencms.file.history.I_CmsHistoryResource;
import org.opencms.main.CmsException;
import org.opencms.main.CmsLog;
import org.opencms.main.OpenCms;
import org.opencms.security.CmsPrincipal;
import org.opencms.util.CmsFileUtil;
import org.opencms.util.CmsPair;
import org.opencms.util.CmsStringUtil;
import org.opencms.util.CmsUUID;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.commons.logging.Log;

/**
 * Generic implementation of the user tracking and subscription driver interface.<p>
 * 
 * @since 8.0.0
 */
public class CmsSubscriptionDriver implements I_CmsDriver, I_CmsSubscriptionDriver {

    /** The log object for this class. */
    private static final Log LOG = CmsLog.getLog(org.opencms.db.generic.CmsSubscriptionDriver.class);

    /** A reference to the driver manager used by this driver. */
    protected CmsDriverManager m_driverManager;

    /** The SQL manager used by this driver. */
    protected CmsSqlManager m_sqlManager;

    /**
     * @see org.opencms.db.I_CmsSubscriptionDriver#deleteVisits(org.opencms.db.CmsDbContext, java.lang.String, org.opencms.db.CmsVisitEntryFilter)
     */
    public void deleteVisits(CmsDbContext dbc, String poolName, CmsVisitEntryFilter filter)
            throws CmsDataAccessException {

        Connection conn = null;
        PreparedStatement stmt = null;

        try {
            if (CmsStringUtil.isNotEmpty(poolName)) {
                conn = m_sqlManager.getConnection(poolName);
            } else {
                conn = m_sqlManager.getConnection(dbc);
            }

            // compose statement 
            StringBuffer queryBuf = new StringBuffer(256);
            queryBuf.append(m_sqlManager.readQuery("C_VISIT_DELETE_ENTRIES"));

            CmsPair<String, List<I_CmsPreparedStatementParameter>> conditionsAndParams = prepareVisitConditions(
                    filter);
            queryBuf.append(conditionsAndParams.getFirst());
            if (LOG.isDebugEnabled()) {
                LOG.debug(queryBuf.toString());
            }
            stmt = m_sqlManager.getPreparedStatementForSql(conn, queryBuf.toString());
            List<I_CmsPreparedStatementParameter> params = conditionsAndParams.getSecond();
            for (int i = 0; i < params.size(); i++) {
                I_CmsPreparedStatementParameter param = conditionsAndParams.getSecond().get(i);
                param.insertIntoStatement(stmt, i + 1);
            }

            // execute
            stmt.executeUpdate();
        } catch (SQLException e) {
            throw new CmsDbSqlException(
                    Messages.get().container(Messages.ERR_GENERIC_SQL_1, CmsDbSqlException.getErrorQuery(stmt)), e);
        } finally {
            m_sqlManager.closeAll(dbc, conn, stmt, null);
        }
    }

    /**
     * @see org.opencms.db.I_CmsSubscriptionDriver#getDateLastVisitedBy(org.opencms.db.CmsDbContext, java.lang.String, org.opencms.file.CmsUser, org.opencms.file.CmsResource)
     */
    public long getDateLastVisitedBy(CmsDbContext dbc, String poolName, CmsUser user, CmsResource resource)
            throws CmsException {

        CmsVisitEntryFilter filter = CmsVisitEntryFilter.ALL.filterResource(resource.getStructureId())
                .filterUser(user.getId());
        List<CmsVisitEntry> entries = readVisits(dbc, poolName, filter);
        if (!entries.isEmpty()) {
            return entries.get(0).getDate();
        }
        return 0;
    }

    /**
     * @see org.opencms.db.I_CmsSubscriptionDriver#getSqlManager()
     */
    public CmsSqlManager getSqlManager() {

        return m_sqlManager;
    }

    /**
     * @see org.opencms.db.I_CmsDriver#init(org.opencms.db.CmsDbContext, org.opencms.configuration.CmsConfigurationManager, java.util.List, org.opencms.db.CmsDriverManager)
     */
    public void init(CmsDbContext dbc, CmsConfigurationManager configurationManager, List<String> successiveDrivers,
            CmsDriverManager driverManager) {

        CmsParameterConfiguration config = configurationManager.getConfiguration();

        String poolUrl = config.get("db.subscription.pool");
        String classname = config.get("db.subscription.sqlmanager");
        m_sqlManager = initSqlManager(classname);
        m_sqlManager.init(I_CmsSubscriptionDriver.DRIVER_TYPE_ID, poolUrl);

        m_driverManager = driverManager;

        if (CmsLog.INIT.isInfoEnabled()) {
            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_ASSIGNED_POOL_1, poolUrl));
        }

        if ((successiveDrivers != null) && !successiveDrivers.isEmpty()) {
            if (LOG.isWarnEnabled()) {
                LOG.warn(Messages.get().getBundle().key(Messages.LOG_SUCCESSIVE_DRIVERS_UNSUPPORTED_1,
                        getClass().getName()));
            }
        }
    }

    /**
     * @see org.opencms.db.I_CmsSubscriptionDriver#initSqlManager(java.lang.String)
     */
    public org.opencms.db.generic.CmsSqlManager initSqlManager(String classname) {

        return CmsSqlManager.getInstance(classname);
    }

    /**
     * @see org.opencms.db.I_CmsSubscriptionDriver#markResourceAsVisitedBy(org.opencms.db.CmsDbContext, java.lang.String, org.opencms.file.CmsResource, org.opencms.file.CmsUser)
     */
    public void markResourceAsVisitedBy(CmsDbContext dbc, String poolName, CmsResource resource, CmsUser user)
            throws CmsDataAccessException {

        boolean entryExists = false;
        CmsVisitEntryFilter filter = CmsVisitEntryFilter.ALL.filterResource(resource.getStructureId())
                .filterUser(user.getId());
        // delete existing visited entry for the resource
        if (readVisits(dbc, OpenCms.getSubscriptionManager().getPoolName(), filter).size() > 0) {
            entryExists = true;
            deleteVisits(dbc, OpenCms.getSubscriptionManager().getPoolName(), filter);
        }

        CmsVisitEntry entry = new CmsVisitEntry(user.getId(), System.currentTimeMillis(),
                resource.getStructureId());
        addVisit(dbc, poolName, entry);

        if (!entryExists) {
            // new entry, check if maximum number of stored visited resources is exceeded
            PreparedStatement stmt = null;
            Connection conn = null;
            ResultSet res = null;
            int count = 0;

            List<Long> dates = new ArrayList<Long>();

            try {
                conn = m_sqlManager.getConnection(poolName);
                stmt = m_sqlManager.getPreparedStatement(conn, dbc.currentProject(), "C_VISITED_USER_COUNT_1");

                stmt.setString(1, user.getId().toString());
                res = stmt.executeQuery();

                if (res.next()) {
                    count = res.getInt(1);
                    while (res.next()) {
                        // do nothing only move through all rows because of mssql odbc driver
                    }
                } else {
                    throw new CmsDbConsistencyException(
                            Messages.get().container(Messages.ERR_COUNTING_VISITED_RESOURCES_1, user.getName()));
                }

                int maxCount = OpenCms.getSubscriptionManager().getMaxVisitedCount();
                if (count > maxCount) {
                    // delete old visited entries
                    m_sqlManager.closeAll(dbc, null, stmt, res);
                    stmt = m_sqlManager.getPreparedStatement(conn, dbc.currentProject(),
                            "C_VISITED_USER_DELETE_GETDATE_2");

                    stmt.setString(1, user.getId().toString());
                    stmt.setInt(2, count - maxCount);
                    res = stmt.executeQuery();
                    while (res.next()) {
                        // get last date of result set
                        dates.add(Long.valueOf(res.getLong(1)));
                    }
                }
            } catch (SQLException e) {
                throw new CmsDbSqlException(
                        Messages.get().container(Messages.ERR_GENERIC_SQL_1, CmsDbSqlException.getErrorQuery(stmt)),
                        e);
            } finally {
                m_sqlManager.closeAll(dbc, conn, stmt, res);
            }
            long deleteDate = 0;
            for (Long date : dates) {
                deleteDate = date.longValue();
                if (deleteDate > 0) {
                    filter = CmsVisitEntryFilter.ALL.filterUser(user.getId()).filterTo(deleteDate);
                    deleteVisits(dbc, OpenCms.getSubscriptionManager().getPoolName(), filter);
                }
            }
        }
    }

    /**
     * @see org.opencms.db.I_CmsSubscriptionDriver#readAllSubscribedResources(org.opencms.db.CmsDbContext, java.lang.String, org.opencms.security.CmsPrincipal)
     */
    public List<CmsResource> readAllSubscribedResources(CmsDbContext dbc, String poolName, CmsPrincipal principal)
            throws CmsDataAccessException {

        PreparedStatement stmt = null;
        Connection conn = null;
        ResultSet res = null;
        CmsResource currentResource = null;
        List<CmsResource> resources = new ArrayList<CmsResource>();

        try {
            conn = m_sqlManager.getConnection(poolName);
            stmt = m_sqlManager.getPreparedStatement(conn, dbc.currentProject(), "C_SUBSCRIPTION_READ_ALL_1");

            stmt.setString(1, principal.getId().toString());
            res = stmt.executeQuery();

            while (res.next()) {
                currentResource = m_driverManager.getVfsDriver(dbc).createFile(res, dbc.currentProject().getUuid(),
                        false);
                resources.add(currentResource);
            }
        } catch (SQLException e) {
            throw new CmsDbSqlException(
                    Messages.get().container(Messages.ERR_GENERIC_SQL_1, CmsDbSqlException.getErrorQuery(stmt)), e);
        } finally {
            m_sqlManager.closeAll(dbc, conn, stmt, res);
        }
        return resources;
    }

    /**
     * @see org.opencms.db.I_CmsSubscriptionDriver#readResourcesVisitedBy(org.opencms.db.CmsDbContext, java.lang.String, org.opencms.db.CmsVisitedByFilter)
     */
    public List<CmsResource> readResourcesVisitedBy(CmsDbContext dbc, String poolName, CmsVisitedByFilter filter)
            throws CmsDataAccessException {

        PreparedStatement stmt = null;
        Connection conn = null;
        ResultSet res = null;
        CmsResource currentResource = null;
        StringBuffer conditions = new StringBuffer(256);
        List<String> params = new ArrayList<String>(1);
        List<CmsResource> resources = new ArrayList<CmsResource>();

        try {
            // path filter
            if (CmsStringUtil.isNotEmpty(filter.getParentPath())) {
                CmsResource parent = m_driverManager.getVfsDriver(dbc).readResource(dbc,
                        dbc.currentProject().getUuid(), filter.getParentPath(), false);
                conditions.append(BEGIN_INCLUDE_CONDITION);
                if (filter.isIncludeSubFolders()) {
                    conditions.append(
                            m_sqlManager.readQuery(dbc.currentProject(), "C_RESOURCES_SELECT_BY_PATH_PREFIX"));
                    params.add(
                            CmsFileUtil.addTrailingSeparator(CmsVfsDriver.escapeDbWildcard(filter.getParentPath()))
                                    + "%");
                } else {
                    conditions.append(
                            m_sqlManager.readQuery(dbc.currentProject(), "C_RESOURCES_SELECT_BY_PARENT_UUID"));
                    params.add(parent.getStructureId().toString());
                }
                conditions.append(END_CONDITION);
            }

            conn = m_sqlManager.getConnection(poolName);
            String query = m_sqlManager.readQuery(dbc.currentProject(), "C_VISITED_USER_READ_4");
            query = CmsStringUtil.substitute(query, "%(CONDITIONS)", conditions.toString());
            stmt = m_sqlManager.getPreparedStatementForSql(conn, query);

            stmt.setString(1, filter.getUser().getId().toString());
            stmt.setLong(2, filter.getFromDate());
            stmt.setLong(3, filter.getToDate());
            for (int i = 0; i < params.size(); i++) {
                stmt.setString(i + 4, params.get(i));
            }

            res = stmt.executeQuery();

            while (res.next()) {
                currentResource = m_driverManager.getVfsDriver(dbc).createFile(res, dbc.currentProject().getUuid(),
                        false);
                resources.add(currentResource);
            }
        } catch (SQLException e) {
            throw new CmsDbSqlException(
                    Messages.get().container(Messages.ERR_GENERIC_SQL_1, CmsDbSqlException.getErrorQuery(stmt)), e);
        } finally {
            m_sqlManager.closeAll(dbc, conn, stmt, res);
        }
        return resources;
    }

    /**
     * @see org.opencms.db.I_CmsSubscriptionDriver#readSubscribedDeletedResources(org.opencms.db.CmsDbContext, java.lang.String, org.opencms.file.CmsUser, java.util.List, org.opencms.file.CmsResource, boolean, long)
     */
    public List<I_CmsHistoryResource> readSubscribedDeletedResources(CmsDbContext dbc, String poolName,
            CmsUser user, List<CmsGroup> groups, CmsResource parent, boolean includeSubFolders, long deletedFrom)
            throws CmsDataAccessException {

        PreparedStatement stmt = null;
        Connection conn = null;
        ResultSet res = null;
        List<I_CmsHistoryResource> resources = new ArrayList<I_CmsHistoryResource>();
        Set<CmsUUID> historyIDs = new HashSet<CmsUUID>();

        List<String> principalIds = new ArrayList<String>();
        // add user ID
        principalIds.add(user.getId().toString());
        // add group IDs
        if ((groups != null) && !groups.isEmpty()) {
            Iterator<CmsGroup> it = groups.iterator();
            while (it.hasNext()) {
                principalIds.add(it.next().getId().toString());
            }
        }

        StringBuffer conditions = new StringBuffer(256);
        List<String> params = new ArrayList<String>();
        conditions.append(m_sqlManager.readQuery("C_SUBSCRIPTION_DELETED"));

        if (principalIds.size() == 1) {
            // single principal filter
            conditions.append(BEGIN_INCLUDE_CONDITION);
            conditions.append(m_sqlManager.readQuery("C_SUBSCRIPTION_DELETED_FILTER_PRINCIPAL_SINGLE"));
            params.add(principalIds.get(0));
            conditions.append(END_CONDITION);
        } else {
            // multiple principals filter
            conditions.append(BEGIN_INCLUDE_CONDITION);
            conditions.append(m_sqlManager.readQuery("C_SUBSCRIPTION_DELETED_FILTER_PRINCIPALS"));
            conditions.append(BEGIN_CONDITION);
            Iterator<String> it = principalIds.iterator();
            while (it.hasNext()) {
                params.add(it.next());
                conditions.append("?");
                if (it.hasNext()) {
                    conditions.append(", ");
                }
            }
            conditions.append(END_CONDITION);
            conditions.append(END_CONDITION);
        }

        try {
            conn = m_sqlManager.getConnection(poolName);
            stmt = m_sqlManager.getPreparedStatementForSql(conn, conditions.toString());

            // set parameters
            stmt.setLong(1, deletedFrom);
            for (int i = 0; i < params.size(); i++) {
                stmt.setString(i + 2, params.get(i));
            }
            res = stmt.executeQuery();
            while (res.next()) {
                historyIDs.add(new CmsUUID(res.getString(1)));
            }
        } catch (SQLException e) {
            throw new CmsDbSqlException(
                    Messages.get().container(Messages.ERR_GENERIC_SQL_1, CmsDbSqlException.getErrorQuery(stmt)), e);
        } finally {
            m_sqlManager.closeAll(dbc, conn, stmt, res);
        }

        // get the matching history resources from the found structure IDs
        String parentFolderPath = "";
        if (parent != null) {
            parentFolderPath = CmsResource.getFolderPath(parent.getRootPath());
        }
        for (Iterator<CmsUUID> i = historyIDs.iterator(); i.hasNext();) {
            CmsUUID id = i.next();
            int version = m_driverManager.getHistoryDriver(dbc).readLastVersion(dbc, id);
            if (version > 0) {
                I_CmsHistoryResource histRes = m_driverManager.getHistoryDriver(dbc).readResource(dbc, id, version);
                if (parent != null) {
                    if (!includeSubFolders
                            && !parentFolderPath.equals(CmsResource.getFolderPath(histRes.getRootPath()))) {
                        // deleted history resource is not in the specified parent folder, skip it
                        continue;
                    } else if (includeSubFolders && !histRes.getRootPath().startsWith(parentFolderPath)) {
                        // deleted history resource is not in the specified parent folder or sub folder, skip it
                        continue;
                    }
                }
                resources.add(histRes);
            }
        }

        return resources;
    }

    /**
     * @see org.opencms.db.I_CmsSubscriptionDriver#readSubscribedResources(org.opencms.db.CmsDbContext, java.lang.String, org.opencms.db.CmsSubscriptionFilter)
     */
    public List<CmsResource> readSubscribedResources(CmsDbContext dbc, String poolName,
            CmsSubscriptionFilter filter) throws CmsDataAccessException {

        PreparedStatement stmt = null;
        Connection conn = null;
        ResultSet res = null;
        CmsResource currentResource = null;
        List<CmsResource> resources = new ArrayList<CmsResource>();

        String queryBuf = m_sqlManager.readQuery(dbc.currentProject(), "C_SUBSCRIPTION_FILTER_READ");

        StringBuffer conditions = new StringBuffer(256);
        List<I_CmsPreparedStatementParameter> params = new ArrayList<I_CmsPreparedStatementParameter>();

        boolean userDefined = filter.getUser() != null;
        boolean groupsDefined = !filter.getGroups().isEmpty();
        if (!groupsDefined && !userDefined) {
            filter.setUser(dbc.currentUser());
            userDefined = true;
        }
        // check if a user has been set for the "visited" and "unvisited" mode
        if (!filter.getMode().isAll() && (filter.getUser() == null)) {
            // change the mode, without user the other modes are not applicable
            filter.setMode(CmsSubscriptionReadMode.ALL);
        }

        List<String> principalIds = new ArrayList<String>();
        // add user ID
        if (userDefined) {
            principalIds.add(filter.getUser().getId().toString());
        }
        // add group IDs
        if (groupsDefined) {
            Iterator<CmsGroup> it = filter.getGroups().iterator();
            while (it.hasNext()) {
                principalIds.add(it.next().getId().toString());
            }
        }

        if (principalIds.size() == 1) {
            // single principal filter
            conditions.append(BEGIN_CONDITION);
            conditions
                    .append(m_sqlManager.readQuery(dbc.currentProject(), "C_SUBSCRIPTION_FILTER_PRINCIPAL_SINGLE"));
            params.add(new CmsPreparedStatementStringParameter(principalIds.get(0)));
            conditions.append(END_CONDITION);
        } else {
            // multiple principals filter
            conditions.append(BEGIN_CONDITION);
            conditions.append(m_sqlManager.readQuery("C_SUBSCRIPTION_FILTER_PRINCIPALS"));
            conditions.append(BEGIN_CONDITION);
            Iterator<String> it = principalIds.iterator();
            while (it.hasNext()) {
                params.add(new CmsPreparedStatementStringParameter(it.next()));
                conditions.append("?");
                if (it.hasNext()) {
                    conditions.append(", ");
                }
            }
            conditions.append(END_CONDITION);
            conditions.append(m_sqlManager.readQuery(dbc.currentProject(), "C_SUBSCRIPTION_FILTER_PRINCIPALS_END"));
            conditions.append(END_CONDITION);
        }

        // path filter
        if (CmsStringUtil.isNotEmpty(filter.getParentPath())) {
            CmsResource parent = m_driverManager.getVfsDriver(dbc).readResource(dbc, dbc.currentProject().getUuid(),
                    filter.getParentPath(), false);
            conditions.append(BEGIN_INCLUDE_CONDITION);
            if (filter.isIncludeSubFolders()) {
                conditions
                        .append(m_sqlManager.readQuery(dbc.currentProject(), "C_RESOURCES_SELECT_BY_PATH_PREFIX"));
                params.add(new CmsPreparedStatementStringParameter(
                        CmsFileUtil.addTrailingSeparator(CmsVfsDriver.escapeDbWildcard(filter.getParentPath()))
                                + "%"));
            } else {
                conditions
                        .append(m_sqlManager.readQuery(dbc.currentProject(), "C_RESOURCES_SELECT_BY_PARENT_UUID"));
                params.add(new CmsPreparedStatementStringParameter(parent.getStructureId().toString()));
            }
            conditions.append(END_CONDITION);
        }

        // check from and to date
        if ((filter.getFromDate() > 0) || (filter.getToDate() < Long.MAX_VALUE)) {
            conditions.append(BEGIN_INCLUDE_CONDITION);
            conditions.append(
                    m_sqlManager.readQuery(dbc.currentProject(), "C_SUBSCRIPTION_FILTER_RESOURCES_DATE_MODIFIED"));
            params.add(new CmsPreparedStatementLongParameter(filter.getFromDate()));
            params.add(new CmsPreparedStatementLongParameter(filter.getToDate()));
            conditions.append(END_CONDITION);
        }

        try {
            conn = m_sqlManager.getConnection(poolName);
            queryBuf = CmsStringUtil.substitute(queryBuf, "%(CONDITIONS)", conditions.toString());
            if (LOG.isDebugEnabled()) {
                LOG.debug(queryBuf.toString());
            }
            stmt = m_sqlManager.getPreparedStatementForSql(conn, queryBuf);

            // set parameters
            for (int i = 0; i < params.size(); i++) {
                I_CmsPreparedStatementParameter param = params.get(i);
                param.insertIntoStatement(stmt, i + 1);
            }
            res = stmt.executeQuery();

            while (res.next()) {
                currentResource = m_driverManager.getVfsDriver(dbc).createFile(res, dbc.currentProject().getUuid(),
                        false);
                resources.add(currentResource);
            }
        } catch (SQLException e) {
            throw new CmsDbSqlException(
                    Messages.get().container(Messages.ERR_GENERIC_SQL_1, CmsDbSqlException.getErrorQuery(stmt)), e);
        } finally {
            m_sqlManager.closeAll(dbc, conn, stmt, res);
        }

        // filter the result if in visited/unvisited mode (faster as creating a query with even more joined tables)
        if (!filter.getMode().isAll()) {
            List<CmsResource> result = new ArrayList<CmsResource>(resources.size());
            for (Iterator<CmsResource> i = resources.iterator(); i.hasNext();) {
                CmsResource resource = i.next();
                long visitedDate = 0;
                try {
                    visitedDate = m_driverManager.getDateLastVisitedBy(dbc, poolName, filter.getUser(), resource);
                } catch (CmsException e) {
                    throw new CmsDbSqlException(Messages.get().container(Messages.ERR_GENERIC_SQL_0), e);
                }
                if (filter.getMode().isUnVisited() && (visitedDate >= resource.getDateLastModified())) {
                    // unvisited mode: resource was visited after the last modification, skip it
                    continue;
                }
                if (filter.getMode().isVisited() && (resource.getDateLastModified() > visitedDate)) {
                    // visited mode: resource was not visited after last modification, skip it
                    continue;
                }
                // add the current resource to the result
                result.add(resource);
            }
            resources = result;
        }
        return resources;
    }

    /**
     * Reads {@link CmsVisitEntry} objects from the database.<p>
     * 
     * @param dbc the database context to use 
     * @param poolName the name of the pool which should be used for the database operation 
     * @param filter a filter for constraining the list of results
     *  
     * @return a list of visit entries 
     * 
     * @throws CmsDataAccessException if the database operation fails 
     */
    public List<CmsVisitEntry> readVisits(CmsDbContext dbc, String poolName, CmsVisitEntryFilter filter)
            throws CmsDataAccessException {

        List<CmsVisitEntry> entries = new ArrayList<CmsVisitEntry>();

        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet res = null;

        try {
            if (CmsStringUtil.isNotEmpty(poolName)) {
                conn = m_sqlManager.getConnection(poolName);
            } else {
                conn = m_sqlManager.getConnection(dbc);
            }

            // compose statement 
            StringBuffer queryBuf = new StringBuffer(256);
            queryBuf.append(m_sqlManager.readQuery("C_VISIT_READ_ENTRIES"));
            CmsPair<String, List<I_CmsPreparedStatementParameter>> conditionsAndParameters = prepareVisitConditions(
                    filter);
            List<I_CmsPreparedStatementParameter> params = conditionsAndParameters.getSecond();
            queryBuf.append(conditionsAndParameters.getFirst());

            if (LOG.isDebugEnabled()) {
                LOG.debug(queryBuf.toString());
            }
            stmt = m_sqlManager.getPreparedStatementForSql(conn, queryBuf.toString());
            for (int i = 0; i < params.size(); i++) {
                I_CmsPreparedStatementParameter param = params.get(i);
                param.insertIntoStatement(stmt, i + 1);
            }

            // execute
            res = stmt.executeQuery();
            while (res.next()) {
                // get results
                entries.add(internalReadVisitEntry(res));
            }
        } catch (SQLException e) {
            throw new CmsDbSqlException(
                    Messages.get().container(Messages.ERR_GENERIC_SQL_1, CmsDbSqlException.getErrorQuery(stmt)), e);
        } finally {
            m_sqlManager.closeAll(dbc, conn, stmt, res);
        }
        return entries;
    }

    /**
     * @see org.opencms.db.I_CmsSubscriptionDriver#setSubscribedResourceAsDeleted(org.opencms.db.CmsDbContext, java.lang.String, org.opencms.file.CmsResource)
     */
    public void setSubscribedResourceAsDeleted(CmsDbContext dbc, String poolName, CmsResource resource)
            throws CmsDataAccessException {

        PreparedStatement stmt = null;
        Connection conn = null;
        long deletedTime = System.currentTimeMillis();

        try {
            conn = getSqlManager().getConnection(poolName);
            // set resource as deleted for all users and groups
            stmt = m_sqlManager.getPreparedStatement(conn, "C_SUBSCRIPTION_UPDATE_DATE_2");
            stmt.setLong(1, deletedTime);
            stmt.setString(2, resource.getStructureId().toString());
            stmt.executeUpdate();
        } catch (SQLException e) {
            throw new CmsDbSqlException(
                    Messages.get().container(Messages.ERR_GENERIC_SQL_1, CmsDbSqlException.getErrorQuery(stmt)), e);
        } finally {
            m_sqlManager.closeAll(dbc, conn, stmt, null);
        }

    }

    /**
     * @see org.opencms.db.I_CmsSubscriptionDriver#subscribeResourceFor(org.opencms.db.CmsDbContext, java.lang.String, org.opencms.security.CmsPrincipal, org.opencms.file.CmsResource)
     */
    public void subscribeResourceFor(CmsDbContext dbc, String poolName, CmsPrincipal principal,
            CmsResource resource) throws CmsDataAccessException {

        ResultSet res = null;
        PreparedStatement stmt = null;
        Connection conn = null;

        try {
            conn = getSqlManager().getConnection(poolName);
            stmt = m_sqlManager.getPreparedStatement(conn, "C_SUBSCRIPTION_CHECK_2");
            stmt.setString(1, principal.getId().toString());
            stmt.setString(2, resource.getStructureId().toString());
            res = stmt.executeQuery();

            // only create subscription entry if principal is not subscribed to resource
            if (res.next()) {
                while (res.next()) {
                    // do nothing only move through all rows because of mssql odbc driver
                }
            } else {
                // subscribe principal
                m_sqlManager.closeAll(dbc, null, stmt, null);
                stmt = m_sqlManager.getPreparedStatement(conn, "C_SUBSCRIPTION_CREATE_2");
                stmt.setString(1, principal.getId().toString());
                stmt.setString(2, resource.getStructureId().toString());
                stmt.executeUpdate();
            }
        } catch (SQLException e) {
            throw new CmsDbSqlException(
                    Messages.get().container(Messages.ERR_GENERIC_SQL_1, CmsDbSqlException.getErrorQuery(stmt)), e);
        } finally {
            m_sqlManager.closeAll(dbc, conn, stmt, res);
        }
    }

    /**
     * @see org.opencms.db.I_CmsSubscriptionDriver#unsubscribeAllDeletedResources(org.opencms.db.CmsDbContext, java.lang.String, long)
     */
    public void unsubscribeAllDeletedResources(CmsDbContext dbc, String poolName, long deletedTo)
            throws CmsDataAccessException {

        PreparedStatement stmt = null;
        Connection conn = null;

        try {
            conn = getSqlManager().getConnection(poolName);
            StringBuffer conditions = new StringBuffer(256);

            // unsubscribe all deleted resources
            conditions.append(m_sqlManager.readQuery("C_SUBSCRIPTION_DELETE"));
            conditions.append(BEGIN_CONDITION);
            conditions.append(m_sqlManager.readQuery("C_SUBSCRIPTION_DELETE_FILTER_DATE"));
            conditions.append(END_CONDITION);
            stmt = m_sqlManager.getPreparedStatementForSql(conn, conditions.toString());
            stmt.setLong(1, deletedTo);
            stmt.executeUpdate();

        } catch (SQLException e) {
            throw new CmsDbSqlException(
                    Messages.get().container(Messages.ERR_GENERIC_SQL_1, CmsDbSqlException.getErrorQuery(stmt)), e);
        } finally {
            m_sqlManager.closeAll(dbc, conn, stmt, null);
        }
    }

    /**
     * @see org.opencms.db.I_CmsSubscriptionDriver#unsubscribeAllResourcesFor(org.opencms.db.CmsDbContext, java.lang.String, org.opencms.security.CmsPrincipal)
     */
    public void unsubscribeAllResourcesFor(CmsDbContext dbc, String poolName, CmsPrincipal principal)
            throws CmsDataAccessException {

        PreparedStatement stmt = null;
        Connection conn = null;

        try {
            if (principal != null) {
                conn = getSqlManager().getConnection(poolName);
                StringBuffer conditions = new StringBuffer(256);

                conditions.append(m_sqlManager.readQuery("C_SUBSCRIPTION_DELETE"));
                conditions.append(BEGIN_CONDITION);
                conditions.append(m_sqlManager.readQuery("C_SUBSCRIPTION_DELETE_FILTER_PRINCIPAL"));
                conditions.append(END_CONDITION);
                stmt = m_sqlManager.getPreparedStatementForSql(conn, conditions.toString());
                stmt.setString(1, principal.getId().toString());
                stmt.executeUpdate();
            }
        } catch (SQLException e) {
            throw new CmsDbSqlException(
                    Messages.get().container(Messages.ERR_GENERIC_SQL_1, CmsDbSqlException.getErrorQuery(stmt)), e);
        } finally {
            m_sqlManager.closeAll(dbc, conn, stmt, null);
        }
    }

    /**
     * @see org.opencms.db.I_CmsSubscriptionDriver#unsubscribeResourceFor(org.opencms.db.CmsDbContext, java.lang.String, org.opencms.security.CmsPrincipal, org.opencms.file.CmsResource)
     */
    public void unsubscribeResourceFor(CmsDbContext dbc, String poolName, CmsPrincipal principal,
            CmsResource resource) throws CmsDataAccessException {

        PreparedStatement stmt = null;
        Connection conn = null;

        try {
            conn = getSqlManager().getConnection(poolName);
            StringBuffer conditions = new StringBuffer(256);
            conditions.append(m_sqlManager.readQuery("C_SUBSCRIPTION_DELETE"));
            conditions.append(BEGIN_CONDITION);
            conditions.append(m_sqlManager.readQuery("C_SUBSCRIPTION_DELETE_FILTER_PRINCIPAL"));
            conditions.append(END_CONDITION);
            conditions.append(BEGIN_INCLUDE_CONDITION);
            conditions.append(m_sqlManager.readQuery("C_SUBSCRIPTION_DELETE_FILTER_STRUCTURE"));
            conditions.append(END_CONDITION);
            stmt = m_sqlManager.getPreparedStatementForSql(conn, conditions.toString());
            stmt.setString(1, principal.getId().toString());
            stmt.setString(2, resource.getStructureId().toString());
            stmt.executeUpdate();
        } catch (SQLException e) {
            throw new CmsDbSqlException(
                    Messages.get().container(Messages.ERR_GENERIC_SQL_1, CmsDbSqlException.getErrorQuery(stmt)), e);
        } finally {
            m_sqlManager.closeAll(dbc, conn, stmt, null);
        }
    }

    /**
     * @see org.opencms.db.I_CmsSubscriptionDriver#unsubscribeResourceForAll(org.opencms.db.CmsDbContext, java.lang.String, org.opencms.file.CmsResource)
     */
    public void unsubscribeResourceForAll(CmsDbContext dbc, String poolName, CmsResource resource)
            throws CmsDataAccessException {

        PreparedStatement stmt = null;
        Connection conn = null;

        try {
            conn = getSqlManager().getConnection(poolName);
            StringBuffer conditions = new StringBuffer(256);

            // unsubscribe resource for all principals
            conditions.append(m_sqlManager.readQuery("C_SUBSCRIPTION_DELETE"));
            conditions.append(BEGIN_CONDITION);
            conditions.append(m_sqlManager.readQuery("C_SUBSCRIPTION_DELETE_FILTER_STRUCTURE"));
            conditions.append(END_CONDITION);
            stmt = m_sqlManager.getPreparedStatementForSql(conn, conditions.toString());
            stmt.setString(1, resource.getStructureId().toString());
            stmt.executeUpdate();
        } catch (SQLException e) {
            throw new CmsDbSqlException(
                    Messages.get().container(Messages.ERR_GENERIC_SQL_1, CmsDbSqlException.getErrorQuery(stmt)), e);
        } finally {
            m_sqlManager.closeAll(dbc, conn, stmt, null);
        }
    }

    /**
     * Adds an entry to the table of visits.<p>
     * 
     * @param dbc the database context to use 
     * @param poolName the name of the database pool to use 
     * @param visit the visit bean
     *  
     * @throws CmsDbSqlException if the database operation fails  
     */
    protected void addVisit(CmsDbContext dbc, String poolName, CmsVisitEntry visit) throws CmsDbSqlException {

        Connection conn = null;
        PreparedStatement stmt = null;

        try {
            if (CmsStringUtil.isNotEmpty(poolName)) {
                conn = m_sqlManager.getConnection(poolName);
            } else {
                conn = m_sqlManager.getConnection(dbc);
            }

            stmt = m_sqlManager.getPreparedStatement(conn, "C_VISIT_CREATE_3");

            stmt.setString(1, visit.getUserId().toString());
            stmt.setLong(2, visit.getDate());
            stmt.setString(3, visit.getStructureId() == null ? null : visit.getStructureId().toString());
            try {
                stmt.executeUpdate();
            } catch (SQLException e) {
                // ignore, most likely a duplicate entry
                LOG.debug(Messages.get()
                        .container(Messages.ERR_GENERIC_SQL_1, CmsDbSqlException.getErrorQuery(stmt)).key(), e);
            }

        } catch (SQLException e) {
            throw new CmsDbSqlException(
                    Messages.get().container(Messages.ERR_GENERIC_SQL_1, CmsDbSqlException.getErrorQuery(stmt)), e);
        } finally {
            try {
                m_sqlManager.closeAll(dbc, conn, stmt, null);
            } catch (Throwable t) {
                // this could happen during shutdown
                LOG.debug(t.getLocalizedMessage(), t);
            }
        }
    }

    /**
     * Creates a new {@link CmsVisitEntry} object from the given result set entry.<p>
     * 
     * @param res the result set 
     *  
     * @return the new {@link CmsVisitEntry} object
     * 
     * @throws SQLException if something goes wrong
     */
    protected CmsVisitEntry internalReadVisitEntry(ResultSet res) throws SQLException {

        CmsUUID userId = new CmsUUID(res.getString(m_sqlManager.readQuery("C_VISIT_USER_ID")));
        long date = res.getLong(m_sqlManager.readQuery("C_VISIT_DATE"));
        CmsUUID structureId = new CmsUUID(res.getString(m_sqlManager.readQuery("C_VISIT_STRUCTURE_ID")));
        return new CmsVisitEntry(userId, date, structureId);
    }

    /**
     * Build the whole WHERE SQL statement part for the given visit entry filter.<p>
     * 
     * @param filter the filter
     * 
     * @return a pair containing both the SQL and the parameters for it
     */
    protected CmsPair<String, List<I_CmsPreparedStatementParameter>> prepareVisitConditions(
            CmsVisitEntryFilter filter) {

        List<I_CmsPreparedStatementParameter> params = new ArrayList<I_CmsPreparedStatementParameter>();
        StringBuffer conditions = new StringBuffer();

        // user id filter
        if (filter.getUserId() != null) {
            if (conditions.length() == 0) {
                conditions.append(BEGIN_CONDITION);
            } else {
                conditions.append(BEGIN_INCLUDE_CONDITION);
            }
            conditions.append(m_sqlManager.readQuery("C_VISIT_FILTER_USER_ID"));
            params.add(new CmsPreparedStatementStringParameter(filter.getUserId().toString()));
            conditions.append(END_CONDITION);
        }

        // resource id filter
        if (filter.getStructureId() != null) {
            if (conditions.length() == 0) {
                conditions.append(BEGIN_CONDITION);
            } else {
                conditions.append(BEGIN_INCLUDE_CONDITION);
            }
            conditions.append(m_sqlManager.readQuery("C_VISIT_FILTER_STRUCTURE_ID"));
            params.add(new CmsPreparedStatementStringParameter(filter.getStructureId().toString()));
            conditions.append(END_CONDITION);
        }

        // date from filter
        if (filter.getDateFrom() != CmsResource.DATE_RELEASED_DEFAULT) {
            if (conditions.length() == 0) {
                conditions.append(BEGIN_CONDITION);
            } else {
                conditions.append(BEGIN_INCLUDE_CONDITION);
            }
            conditions.append(m_sqlManager.readQuery("C_VISIT_FILTER_DATE_FROM"));
            params.add(new CmsPreparedStatementLongParameter(filter.getDateFrom()));
            conditions.append(END_CONDITION);
        }

        // date to filter
        if (filter.getDateTo() != CmsResource.DATE_RELEASED_DEFAULT) {
            if (conditions.length() == 0) {
                conditions.append(BEGIN_CONDITION);
            } else {
                conditions.append(BEGIN_INCLUDE_CONDITION);
            }
            conditions.append(m_sqlManager.readQuery("C_VISIT_FILTER_DATE_TO"));
            params.add(new CmsPreparedStatementLongParameter(filter.getDateTo()));
            conditions.append(END_CONDITION);
        }
        return CmsPair.create(conditions.toString(), params);
    }

}