qnameCache;
    
    /**
     * Default constructor.
     * 
     * This sets up the DAO accessors to bypass any caching to handle the case where the caches are not
     * supplied in the setters.
     */
    protected AbstractQNameDAOImpl()
    {
        this.namespaceCache = new EntityLookupCache(new NamespaceCallbackDAO());
        this.qnameCache = new EntityLookupCache(new QNameCallbackDAO());
    }
    
    /**
     * Set the cache that maintains the ID-Namespace mappings and vice-versa.
     * 
     * @param namespaceCache        the cache
     */
    public void setNamespaceCache(SimpleCache namespaceCache)
    {
        this.namespaceCache = new EntityLookupCache(
                namespaceCache,
                CACHE_REGION_NAMESPACE,
                new NamespaceCallbackDAO());
    }
    /**
     * Set the cache that maintains the ID-Namespace mappings and vice-versa.
     * 
     * @param qnameCache            the cache
     */
    public void setQnameCache(SimpleCache qnameCache)
    {
        this.qnameCache = new EntityLookupCache(
                qnameCache,
                CACHE_REGION_QNAME,
                new QNameCallbackDAO());
    }
    //================================
    // 'alf_namespace' accessors
    //================================
    public Pair getNamespace(Long id)
    {
        if (id == null)
        {
            throw new IllegalArgumentException("Cannot look up entity by null ID.");
        }
        Pair entityPair = namespaceCache.getByKey(id);
        if (entityPair == null)
        {
            throw new DataIntegrityViolationException("No namespace exists for ID " + id);
        }
        return entityPair;
    }
    
    public Pair getNamespace(String namespaceUri)
    {
        if (namespaceUri == null)
        {
            throw new IllegalArgumentException("Namespace URI cannot be null");
        }
        Pair entityPair = namespaceCache.getByValue(namespaceUri);
        return entityPair;
    }
    public Pair getOrCreateNamespace(String namespaceUri)
    {
        if (namespaceUri == null)
        {
            throw new IllegalArgumentException("Namespace URI cannot be null");
        }
        Pair entityPair = namespaceCache.getOrCreateByValue(namespaceUri);
        return entityPair;
    }
    public void updateNamespace(String oldNamespaceUri, String newNamespaceUri)
    {
        ParameterCheck.mandatory("newNamespaceUri", newNamespaceUri);
        Pair oldEntityPair = getNamespace(oldNamespaceUri);   // incl. null check
        if (oldEntityPair == null)
        {
            throw new DataIntegrityViolationException(
                    "Cannot update namespace as it doesn't exist: " + oldNamespaceUri);
        }
        // Find the value
        int updated = namespaceCache.updateValue(oldEntityPair.getFirst(), newNamespaceUri);
        if (updated != 1)
        {
            throw new ConcurrencyFailureException(
                    "Incorrect update count: \n" +
                    "   Namespace:    " + oldNamespaceUri + "\n" +
                    "   Rows Updated: " + updated);
        }
        // All the QNames need to be dumped
        qnameCache.clear();
        // Done
    }
    /**
     * Callback for alf_namespace DAO.
     */
    private class NamespaceCallbackDAO extends EntityLookupCallbackDAOAdaptor
    {
        @Override
        public String getValueKey(String value)
        {
            return value;
        }
        public Pair findByKey(Long id)
        {
            NamespaceEntity entity = findNamespaceEntityById(id);
            if (entity == null)
            {
                return null;
            }
            else
            {
                return new Pair(id, entity.getUriSafe());
            }
        }
        @Override
        public Pair findByValue(String uri)
        {
            NamespaceEntity entity = findNamespaceEntityByUri(uri);
            if (entity == null)
            {
                return null;
            }
            else
            {
                return new Pair(entity.getId(), uri);
            }
        }
        
        public Pair createValue(String uri)
        {
            NamespaceEntity entity = createNamespaceEntity(uri);
            return new Pair(entity.getId(), uri);
        }
        @Override
        public int updateValue(Long id, String uri)
        {
            NamespaceEntity entity = findNamespaceEntityById(id);
            if (entity == null)
            {
                // Client can decide if this is a problem
                return 0;
            }
            return updateNamespaceEntity(entity, uri);
        }
    }
    
    protected abstract NamespaceEntity findNamespaceEntityById(Long id);
    protected abstract NamespaceEntity findNamespaceEntityByUri(String uri);
    protected abstract NamespaceEntity createNamespaceEntity(String uri);
    protected abstract int updateNamespaceEntity(NamespaceEntity entity, String uri);
    
    //================================
    // 'alf_qname' accessors
    //================================
    public Pair getQName(Long id)
    {
        if (id == null)
        {
            throw new IllegalArgumentException("Cannot look up entity by null ID.");
        }
        Pair entityPair = qnameCache.getByKey(id);
        if (entityPair == null)
        {
            throw new DataIntegrityViolationException("No qname exists for ID " + id);
        }
        return entityPair;
    }
    public Pair getQName(final QName qname)
    {
        if (qname == null)
        {
            throw new IllegalArgumentException("QName cannot be null");
        }
        Pair entityPair = qnameCache.getByValue(qname);
        return entityPair;
    }
    public Pair getOrCreateQName(QName qname)
    {
        if (qname == null)
        {
            throw new IllegalArgumentException("QName cannot be null");
        }
        Pair entityPair = qnameCache.getOrCreateByValue(qname);
        return entityPair;
    }
    public Pair updateQName(QName qnameOld, QName qnameNew)
    {
        if (qnameOld == null|| qnameNew == null)
        {
            throw new IllegalArgumentException("QName cannot be null");
        }
        if (qnameOld.equals(qnameNew))
        {
            throw new IllegalArgumentException("Cannot update QNames: they are the same");
        }
        // See if the old QName exists
        Pair qnameOldPair = qnameCache.getByValue(qnameOld);
        if (qnameOldPair == null)
        {
            throw new IllegalArgumentException("Cannot rename QName.  QName " + qnameOld + " does not exist");
        }
        // See if the new QName exists
        if (qnameCache.getByValue(qnameNew) != null)
        {
            throw new IllegalArgumentException("Cannot rename QName.  QName " + qnameNew + " already exists");
        }
        // Update
        Long qnameId = qnameOldPair.getFirst();
        int updated = qnameCache.updateValue(qnameId, qnameNew);
        if (updated != 1)
        {
            throw new ConcurrencyFailureException("Failed to update QName entity " + qnameId);
        }
        return new Pair(qnameId, qnameNew);
    }
    @Override
    public void deleteQName(QName qname)
    {
        if (qname == null)
        {
            throw new IllegalArgumentException("QName cannot be null");
        }
        // See if the QName exists
        Pair qnamePair = qnameCache.getByValue(qname);
        if (qnamePair == null)
        {
            throw new IllegalArgumentException("Cannot delete QName.  QName " + qname + " does not exist");
        }
        // Delete
        Long qnameId = qnamePair.getFirst();
        int deleted = qnameCache.deleteByKey(qnameId);
        if (deleted != 1)
        {
            throw new ConcurrencyFailureException("Failed to delete QName entity " + qnameId);
        }
    }
    /**
     * Callback for alf_qname DAO.
     */
    private class QNameCallbackDAO extends EntityLookupCallbackDAOAdaptor
    {
        @Override
        public QName getValueKey(QName value)
        {
            return value;
        }
        public Pair findByKey(Long id)
        {
            QNameEntity entity = findQNameEntityById(id);
            if (entity == null)
            {
                return null;
            }
            else
            {
                Long namespaceId = entity.getNamespaceId();
                String uri = getNamespace(namespaceId).getSecond();
                String localName = entity.getLocalNameSafe();
                QName qname = QName.createQName(uri, localName);
                return new Pair(id, qname);
            }
        }
        @Override
        public Pair findByValue(QName qname)
        {
            String uri = qname.getNamespaceURI();
            String localName = qname.getLocalName();
            Pair namespaceEntity = getNamespace(uri);
            if (namespaceEntity == null)
            {
                // There is no match on NS, so there is no QName like this
                return null;
            }
            Long nsId = namespaceEntity.getFirst();
            QNameEntity entity = findQNameEntityByNamespaceAndLocalName(nsId, localName);
            if (entity == null)
            {
                return null;
            }
            else
            {
                return new Pair(entity.getId(), qname);
            }
        }
        
        public Pair createValue(QName qname)
        {
            String uri = qname.getNamespaceURI();
            String localName = qname.getLocalName();
            // Create namespace
            Pair namespaceEntity = getOrCreateNamespace(uri);
            Long nsId = namespaceEntity.getFirst();
            // Create QName
            QNameEntity entity = createQNameEntity(nsId, localName);
            return new Pair(entity.getId(), qname);
        }
        @Override
        public int updateValue(Long id, QName qname)
        { 
            String uri = qname.getNamespaceURI();
            String localName = qname.getLocalName();
            QNameEntity entity = findQNameEntityById(id);
            if (entity == null)
            {
                // No chance of updating
                return 0;
            }
            // Create namespace
            Pair namespaceEntity = getOrCreateNamespace(uri);
            Long nsId = namespaceEntity.getFirst();
            // Create QName
            return updateQNameEntity(entity, nsId, localName);
        }
        public int deleteByKey(Long id)
        {
            QNameEntity entity = findQNameEntityById(id);
            if (entity == null)
            {
                // No chance of updating
                return 0;
            }
        		return deleteQNameEntity(entity);
        }
    }
    
    protected abstract QNameEntity findQNameEntityById(Long id);
    protected abstract QNameEntity findQNameEntityByNamespaceAndLocalName(Long nsId, String localName);
    protected abstract QNameEntity createQNameEntity(Long nsId, String localName);
    protected abstract int updateQNameEntity(QNameEntity entity, Long nsId, String localName);
    protected abstract int deleteQNameEntity(QNameEntity entity);
    
    //================================
    // Utility method implementations
    //================================
    public Set convertIdsToQNames(Set ids)
    {
        Set qnames = new HashSet(ids.size() * 2 + 1);
        for (Long id : ids)
        {
            QName qname = getQName(id).getSecond();                     // getQName(id) is never null
            qnames.add(qname);
        }
        return qnames;
    }
    
    public Map convertIdMapToQNameMap(Map idMap)
    {
        Map qnameMap = new HashMap(idMap.size() + 3);
        for (Map.Entry entry : idMap.entrySet())
        {
            QName qname = getQName(entry.getKey()).getSecond();         // getQName(id) is never null
            qnameMap.put(qname, entry.getValue());
        }
        return qnameMap;
    }
    /**
     * @return      Returns a set of IDs mapping to the QNames provided.  If create is false
     *              then there will not be corresponding entries for the QNames that don't exist.
     *              So there is no guarantee that the returned set will be ordered the same or even
     *              contain the same number of elements as the original unless create is true.
     */
    public Set convertQNamesToIds(Set qnames, boolean create)
    {
        Set qnameIds = new HashSet(qnames.size(), 1.0F);
        for (QName qname : qnames)
        {
            Long qnameEntityId = null;
            if (create)
            {
                qnameEntityId = getOrCreateQName(qname).getFirst();     // getOrCreateQName(qname) is never null
            }
            else
            {
                Pair qnamePair = getQName(qname);
                if (qnamePair == null)
                {
                    // No such qname and we are not creating one
                    continue;
                }
                else
                {
                    qnameEntityId = qnamePair.getFirst();
                }
            }
            if (qnameEntityId != null)
            {
                qnameIds.add(qnameEntityId);
            }
        }
        // Done
        return qnameIds;
    }
}