/**
     * @param array $params
     * @return array
     */
    public function getList(array $params = null)
    {
        require_once 'Util/Sql.php';
        require_once 'Util/Array.php';

        $params = is_array($params) ? $params : array();

        if (isset($params['entourage'])) {
            require_once 'models/AclHandler/Entourage.php';
            $entourageHandler = new Default_Model_AclHandler_Entourage($this->getAcl(), $this->getAclContextUser());
            $data = $entourageHandler->getList(array($this->_resourceName => $params));
            return $data[$this->_resourceName];
        }

        if (isset($params['where'])) {
            // use default properties to search against if none are provided
            if (!is_array($params['where'])) {
                // no where terms specified, use the defaults at the 'or' level
                $defaultWhereStruct = array();
                foreach ($this->_defaultListWhere as $whereTerm) {
                    $defaultWhereStruct[] = array($whereTerm => $params['where']);
                }
                $params['where'] = array($defaultWhereStruct);
            }
        } else {
            $params['where'] = array();
        }

        if (!isset($params['sort']) || (!is_string($params['sort']) && !is_array($params['sort']))) {
            $params['sort'] = $this->_defaultListSort;
        }
        $params['sort'] = Util_Sql::generateSqlOrderBy($params['sort'], $this->getPropertyKeys());

        // expected that: 0 < limit <= _listMaxLength
        if (!isset($params['limit']) || 0 >= $params['limit'] || $this->_listMaxLength < $params['limit']) {
            $params['limit'] = $this->_listMaxLength;
        }
        $params['limit'] = (integer) $params['limit'];

        // group by
        if (!isset($params['groupBy']) || !in_array($params['groupBy'], $this->getPropertyKeys())) {
            $params['groupBy'] = null;
        }

        // order of elements before group by, determines which row gets used
        // by group by from within the group, the last in the group wins
        if (!isset($params['condenseOn']) || (!is_string($params['condenseOn']) && !is_array($params['condenseOn']))) {
            $params['condenseOn'] = $this->_defaultListSort;
        }
        $params['condenseOn'] = Util_Sql::generateSqlOrderBy($params['condenseOn'], $this->getPropertyKeys());

        // properties is expected to be an array of string property keys or a
        // string of space separated property keys
        //
        // array('id', 'discussion_id', 'comment', 'modified')
        //   - or -
        // 'id discussion_id comment modified'
        {
            if (!isset($params['properties'])) {
                $params['properties'] = $this->getPropertyKeys();
            }
            if (!is_array($params['properties'])) {
                $params['properties'] = explode(' ', $params['properties']);
            }
            $validatedProps = array_intersect($this->getPropertyKeys(), $params['properties']);
            if (count($validatedProps) != count($params['properties'])) {
                require_once 'Rest/Model/BadRequestException.php';
                throw new Rest_Model_BadRequestException('[' . implode(', ', array_diff($params['properties'], $validatedProps)) . '] are not valid properties for ' . $this->_resourceName);
            }
        }

        // this is an optimization. Imposes a slight overhead performance hit,
        // but for queries where there is very restrictive dependency and a
        // large resource set being scanned, this optimization is very important
        if (0 < count($this->_permissionDependency)) {
            // go through all the dependies and make sure there are WHERE
            // clauses for them
            foreach ($this->_permissionDependency as $depResource => $depAssoc) {
                foreach ($depAssoc as $depId => $resourceId) {
                    // if there is a WHERE already for the resource id
                    // associated with the dependency, then can't optimize further
                    if (in_array($resourceId, array_keys($params['where']))) {
                        continue;
                    }

                    $parentResourceHandler = $this->_createAclHandler($depResource);
                    $list = Util_Array::arrayFromKeyValuesOfSet($depId, $parentResourceHandler->getList(array('limit' => $this->_listMaxLength)));

                    if (0 == count($list)) {
                        // none of the dependencies, done
                        return array();
                    }

                    if ($this->_listMaxLength == count($list)) {
                        // optimization isn't optimal in this situation abort
                        // for dependency association
                        continue;
                    }

                    $params['where'][$resourceId] = $list;
                }
            }
        }

        $whereSqlAndParam = Util_Sql::generateSqlWheresAndParams($params['where'], $this->getPropertyKeys());

        $limit = $params['limit'];
        $dbLimit = floor($limit * $this->_listPermissionFilteredRate);

        $cumulativeRowSet = array();
        $offset = 0;
        do {
            $sql = '';
            // do sub select in order to handle sub ordering in group and get
            // the row we want
            $sql .= (($params['groupBy'] && $params['condenseOn']) ? ' SELECT * FROM (' : '');

            // RESOURCE
            $sql .= $this->_getListResourceSqlFragment();

            $sql .= ''
                // ACL
                . $this->_getGenericAclListJoins()

                // ACL
                . ' WHERE ' . $this->_getGenericAclListWheres()

                // RESOURCE
                . ' AND ' . $whereSqlAndParam['sql']
                . ($params['groupBy'] ? (($params['condenseOn'] ? (' ORDER BY ' . implode(', ', $params['condenseOn']) . ')') : '') . ' GROUP BY ' . $params['groupBy']) : '')
                . ' ORDER BY ' . implode(', ', $params['sort'])
                . ' LIMIT ' . $dbLimit
                . ' OFFSET ' . $offset

                . '';
            $query = $this->_getDbHandler()->prepare($sql);
            $query->execute(array_merge($this->_getGenericAclListParams(), $whereSqlAndParam['param']));
            $rowSet = $query->fetchAll(PDO::FETCH_ASSOC);

            $countUnfiltered = count($rowSet);

            $this->_filterDependenciesNotAllowed($rowSet);

            $cumulativeRowSet += $rowSet;

            $countCumulativeFiltered = count($cumulativeRowSet);

            $offset += $dbLimit;
        } while ($countCumulativeFiltered < $limit && $countUnfiltered == $dbLimit);

        // ensure that only the desired properties are returned
        if (!empty($cumulativeRowSet) && count($params['properties']) != count($cumulativeRowSet[0])) {
            foreach ($cumulativeRowSet as &$row) {
                $row = array_intersect_key($row, array_flip($params['properties']));
            }
        }

        return array_slice($cumulativeRowSet, 0, $limit);
    }
    /**
     * Loops through the roles to check for one that is allowed for the method.
     *
     * @param string $method, same as what Zend_Acl referers to as 'privilege' but 'method' used for REST context
     * @return boolean
     */
    public function isAllowed($privilege, array $roleResourceId = null, array $resourceId = null)
    {
        // for regular resources the $roleResourceId can be specified and
        // it is used for the resource as well. For permission type resources
        // $roleResourceId should be passed as null and will be determined
        if (null !== $roleResourceId && null === $resourceId) {
            $resourceId = $roleResourceId;
        }

        $user = $this->getAclContextUser();
        if (null === $user) {
            $roleSet = $this->getRoles();
        } else {
            $roleResourceGeneral = $this->getRoleResourceId();

            // first get the possible roles this user has with the resource
            if (null === $resourceId) {
                $sql = 'SELECT role FROM resource_role'
                    . ' WHERE user_id = :userId'
                    . ' AND resource = :roleResourceGeneral'
                    . ' AND resource_id IS NULL'
                    . '';
                $query = $this->_getDbHandler()->query($sql);
                $query->execute(array(
                    ':userId' => $user->id,
                    ':roleResourceGeneral' => $roleResourceGeneral,
                ));
            } else {
                if (null === $roleResourceId) {
                    $sql = 'SELECT role FROM resource_role'
                        . ' WHERE user_id = :userId'
                        . ' AND ('
                        . '     resource = :roleResourceGeneral'
                        . '     AND id = :resourceId'
                        . ' )'
                        . '';
                    $query = $this->_getDbHandler()->query($sql);
                    $query->execute(array(
                        ':userId' => $user->id,
                        ':roleResourceGeneral' => $roleResourceGeneral,
                        ':resourceId' => $resourceId['id'],
                    ));
                } else {
                    // include roles from specific case
                    $roleResourceSpecific = $this->getSpecificRoleResourceId($roleResourceId);

                    $sql = 'SELECT role FROM resource_role'
                        . ' WHERE user_id = :userId'
                        . ' AND ('
                        . '     resource = :roleResourceGeneral'
                        . '     AND ('
                        . '         resource_id IS NULL'
                        . '         OR resource_id = :roleResourceSpecific'
                        . '     )'
                        . ' )'
                        . '';
                    $query = $this->_getDbHandler()->query($sql);
                    $query->execute(array(
                        ':userId' => $user->id,
                        ':roleResourceGeneral' => $roleResourceGeneral,
                        ':roleResourceSpecific' => $roleResourceSpecific,
                    ));
                }
            }
            $rowSet = $query->fetchAll(PDO::FETCH_ASSOC);
            $roleSet = Util_Array::arrayFromKeyValuesOfSet('role', $rowSet);
        }

        // make sure 'default' role is in there
        if (!in_array('default', $roleSet)) {
            $roleSet[] = 'default';
        }

        $resourceGeneral = $this->getResourceId();

        if (null !== $resourceId && null !== $roleResourceId) {
            $resourceSpecific = $this->getSpecificResourceId($resourceId);

            // first check if against this specific resource things are
            // allowed or denied
            $roleVarKeyLookup = array();
            foreach ($roleSet as $index => $role) {
                $roleVarKeyLookup[':role_' . $index] = $role;
            }

            $sql = 'SELECT p.id, p.permission, p.privilege, p.resource, p.role'
                . ' FROM permission AS p'
                . ' WHERE p.role IN (' . implode(', ', array_keys($roleVarKeyLookup)) . ')'
                . ' AND p.resource = :resourceGeneral'
                . ' AND p.resource_id = :resourceSpecific'
                . ' AND p.privilege = :privilege'
                . ' ORDER BY p.permission ASC'
                . '';
            $query = $this->_getDbHandler()->prepare($sql);
            $query->execute(array_merge(
                array(
                    ':resourceGeneral' => $resourceGeneral,
                    ':resourceSpecific' => $resourceSpecific,
                    ':privilege' => $privilege,
                ),
                $roleVarKeyLookup
            ));
            $row = $query->fetch(PDO::FETCH_ASSOC);

            if (false !== $row) {
                // able to say that this specific resource is either allowed or denied
                return $row['permission'] == 'allow';
            }
        }

        // specific resource check wasn't definitive, check the general resource

        // add the default role
        $allowed = false;
        foreach ($roleSet as $role) {
            // check if a role is accepted
            if ($this->getAcl()->isAllowed($role, $resourceGeneral, $privilege)) {
                // if any role is found that allows, the whole thing allows
                return true;
            }
        }

        // no allows found in the general resource, so permission is denied
        return false;
    }
    /**
     *
     * @param mixed $entourageSetParam
     * @param array $resourceList
     * @param Rest_Model_EntourageImplementer_Interface
     */
    protected function _entouragePopulate($entourageSetParam, &$resourceList, $resourceHandler)
    {
        require_once 'Util/Array.php';

        // attach to the resource all the entourage resources
        if (empty($entourageSetParam)) {
            require_once 'Rest/Model/BadRequestException.php';
            throw new Rest_Model_BadRequestException('entourage resources not provided');
        }

        // entourage wasn't passed as an array, get it set up
        if (!is_array($entourageSetParam)) {
            $entourageSetParam = array($entourageSetParam);
        }

        if (empty($resourceList)) {
            return;
        }

        foreach ($entourageSetParam as $name => $entourageParam) {
            // get the entourage set and attach the values to the matching
            // items in the resource list

            // if entourage item wasn't passed as key/value pair, load from value
            if (is_int($name)) {
                $name = $entourageParam;
                $entourageParam = true;
            }

            // if entourage item wasn't expanded then get it from resource
            if (!is_array($entourageParam)) {
                $entourageParam = $resourceHandler->expandEntourageAlias($name);
                if (null === $entourageParam) {
                    require_once 'Rest/Model/BadRequestException.php';
                    throw new Rest_Model_BadRequestException('entourage alias "' . $name . '" is not known');
                }
            }

            // validate the input param
            if (!isset($entourageParam['resourceIdKey'])) {
                require_once 'Rest/Model/BadRequestException.php';
                throw new Rest_Model_BadRequestException('entourage alias "' . $name . '" does not specify a resourceIdKey');
            }
            if (!isset($entourageParam['entourageIdKey'])) {
                require_once 'Rest/Model/BadRequestException.php';
                throw new Rest_Model_BadRequestException('entourage alias "' . $name . '" does not specify a entourageIdKey');
            }
            if (!isset($entourageParam['entourageModel'])) {
                require_once 'Rest/Model/BadRequestException.php';
                throw new Rest_Model_BadRequestException('entourage alias "' . $name . '" does not specify a entourageModel');
            }

            $resourceIdKey = $entourageParam['resourceIdKey'];
            unset($entourageParam['resourceIdKey']); // wont be passed into entourage getList
            $entourageIdKey = $entourageParam['entourageIdKey'];
            unset($entourageParam['entourageIdKey']); // wont be passed into entourage getList
            $entourageModel = $entourageParam['entourageModel'];
            unset($entourageParam['entourageModel']); // wont be passed into entourage getList

            // use entourage name if specified otherwise use the alias name
            $entourageName = isset($entourageParam['entourageName']) ? $entourageParam['entourageName'] : $name;
            unset($entourageParam['entourageName']); // wont be passed into entourage getList

            // if specified only return the first match for entourages that match, can make for a
            // cleaner api for some use cases
            $singleOnly = isset($entourageParam['singleOnly']) && $entourageParam['singleOnly'] && $entourageParam['singleOnly'] !== 'false' ? $entourageParam['singleOnly'] : false;
            unset($entourageParam['singleOnly']); // wont be passed into entourage getList

            $entourageHandler = $this->_createAclHandler($entourageModel);

            // get only the entourage resources needed for the resource
            $resourceJoinIdList = Util_Array::arrayFromKeyValuesOfSet($resourceIdKey, $resourceList);
            if (empty($resourceJoinIdList)) {
                require_once 'Rest/Model/BadRequestException.php';
                throw new Rest_Model_BadRequestException('entourage alias "' . $name . '" specifies an invalid resourceIdKey "' . $resourceIdKey . '"');
            }

            // default, assume that the sub results will be more than the limit
            $resultWillBeLessThanLimit = false;

            if ($singleOnly && empty($entourageParam['groupBy'])) {
                $entourageParam['groupBy'] = $entourageIdKey;
                $resultWillBeLessThanLimit = true;
            }

            // TODO: figure out nature of sort versus condenseOn and if
            // condenseOn should be presented to user, either way some
            // checks and better implementation of condenseOn is needed
            if ($singleOnly && isset($entourageParam['condenseOn']) && $entourageParam['condenseOn']) {
                $entourageParam['condenseOn'] = $entourageParam['condenseOn'];
            }

            if (empty($entourageParam['where'])) {
                $entourageParam['where'] = array();
            }

            // load the entourage items
            if ($resultWillBeLessThanLimit) {
                $entourageParam['where'] = array_merge($entourageParam['where'], array(
                    // because there could be duplicate ids: array_unique and reindex with array_values
                    $entourageIdKey => array_values(array_unique($resourceJoinIdList))
                ));

                $entourageList = $entourageHandler->getList($entourageParam);
            } else {
                $entourageList = array();
                foreach (array_values(array_unique($resourceJoinIdList)) as $resourceId) {
                    $entourageParam['where'][$entourageIdKey] = $resourceId;
                    $entourageList = array_merge($entourageList, $entourageHandler->getList($entourageParam));
                }
            }

            // attach the entourage items to the resource
            $entourageJoinIdList = Util_Array::arrayFromKeyValuesOfSet($entourageIdKey, $entourageList);
            foreach ($resourceList as &$resource) {
                $joinKeySet = array_keys($entourageJoinIdList, $resource[$resourceIdKey]);

                if ($singleOnly) {
                    $first = current($joinKeySet);
                    $resource[$entourageName] = $first !== false ? $entourageList[$first] : null;
                } else {
                    $resource[$entourageName] = array();
                    foreach ($joinKeySet as $joinKey) {
                        $resource[$entourageName][] = $entourageList[$joinKey];
                    }
                }
            }
        }
    }