org.grails.datastore.mapping.redis.engine.RedisPropertyValueIndexer.java Source code

Java tutorial

Introduction

Here is the source code for org.grails.datastore.mapping.redis.engine.RedisPropertyValueIndexer.java

Source

/* Copyright (C) 2010 SpringSource
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.grails.datastore.mapping.redis.engine;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Set;

import org.springframework.dao.DataAccessException;
import org.grails.datastore.mapping.core.SessionImplementor;
import org.grails.datastore.mapping.engine.PropertyValueIndexer;
import org.grails.datastore.mapping.model.MappingContext;
import org.grails.datastore.mapping.model.PersistentEntity;
import org.grails.datastore.mapping.model.PersistentProperty;
import org.grails.datastore.mapping.redis.collection.RedisSet;
import org.grails.datastore.mapping.redis.query.RedisQueryUtils;
import org.grails.datastore.mapping.redis.util.RedisTemplate;

import redis.clients.jedis.exceptions.JedisDataException;

/**
 * Indexes property values for querying later
 *
 * @author Graeme Rocher
 * @since 1.0
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
public class RedisPropertyValueIndexer implements PropertyValueIndexer<Long> {

    private RedisTemplate template;
    private PersistentProperty property;
    private RedisEntityPersister entityPersister;
    private MappingContext mappingContext;

    public RedisPropertyValueIndexer(MappingContext context, RedisEntityPersister redisEntityPersister,
            PersistentProperty property) {
        this.template = redisEntityPersister.getRedisTemplate();
        this.entityPersister = redisEntityPersister;
        this.property = property;
        this.mappingContext = context;
    }

    public void deindex(Object value, Long primaryKey) {
        if (value == null) {
            return;
        }

        final String primaryIndex = createRedisKey(value);
        template.srem(primaryIndex, primaryKey);
    }

    public void index(final Object value, final Long primaryKey) {
        if (value == null) {
            return;
        }

        String propSortKey = entityPersister.getPropertySortKey(property);
        clearCachedIndices(entityPersister.getPropertySortKeyPattern());
        final String primaryIndex = createRedisKey(value);
        try {
            template.sadd(primaryIndex, primaryKey);
        } catch (JedisDataException e) {
            // TODO horrible hack workaround for a Jedis 2.0.0 bug; the command works but the
            //      client doesn't handle the response correctly, so it's safe to ignore the
            //      exception if we're in a transaction
            if (e.getMessage().startsWith("Please close pipeline or multi block before calling this method")) {
                if (!template.isInMulti()) {
                    throw e;
                }
            } else {
                throw e;
            }
        }

        // for numbers and dates we also create a list index in order to support range queries

        if (value instanceof Number) {
            template.zadd(propSortKey, ((Number) value).doubleValue(), primaryKey);
        } else if (value instanceof Date) {
            template.zadd(propSortKey, ((Date) value).getTime(), primaryKey);
        }
    }

    private void clearCachedIndices(final String propSortKey) {
        final SessionImplementor session = (SessionImplementor) entityPersister.getSession();
        final String keyPattern = propSortKey + "~*";
        session.addPostFlushOperation(new KeyPatternRunnable(keyPattern) {
            public void run() {
                deleteKeys(template.keys(keyPattern));
            }
        });
    }

    private abstract class KeyPatternRunnable implements Runnable {
        private String keyPattern;

        public KeyPatternRunnable(String keyPattern) {
            this.keyPattern = keyPattern;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof KeyPatternRunnable) {
                return keyPattern.equals(((KeyPatternRunnable) obj).keyPattern);
            }
            return super.equals(obj);
        }

        @Override
        public int hashCode() {
            return keyPattern.hashCode();
        }
    }

    private void deleteKeys(Set<String> toDelete) {
        if (toDelete != null && !toDelete.isEmpty()) {
            template.del(toDelete.toArray(new String[toDelete.size()]));
        }
    }

    private String createRedisKey(Object value) {
        return getIndexRoot() + urlEncode(value);
    }

    @SuppressWarnings("serial")
    private String urlEncode(Object value) {
        try {
            return URLEncoder.encode(value.toString(), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new DataAccessException("Cannot encoding Redis key: " + e.getMessage(), e) {
            };
        }
    }

    private String getIndexRoot() {
        return getRootEntityName() + ":" + property.getName() + ":";
    }

    private String getRootEntityName() {

        final PersistentEntity owner = property.getOwner();
        if (owner.isRoot()) {
            return owner.getName();
        }
        return owner.getRootEntity().getName();
    }

    public List<Long> query(final Object value) {
        return query(value, 0, -1);
    }

    public List<Long> query(final Object value, final int offset, final int max) {
        RedisSet set = new RedisSet(template, createRedisKey(value));
        Collection<String> results;
        if (offset > 0 || max > 0) {
            results = set.members(offset, max);
        } else {
            results = set.members();
        }
        return RedisQueryUtils.transformRedisResults(mappingContext.getConversionService(), results);
    }

    public String getIndexName(Object value) {
        return createRedisKey(value);
    }

    public String getIndexPattern(String pattern) {
        return getIndexRoot() + urlEncode(pattern.replaceAll("%", "*"));
    }
}