org.springframework.data.gemfire.repository.support.GemfireRepositoryFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.data.gemfire.repository.support.GemfireRepositoryFactory.java

Source

/*
 * Copyright 2012-2019 the original author or authors.
 *
 * 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
 *
 *      https://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.springframework.data.gemfire.repository.support;

import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Optional;

import org.apache.geode.cache.Region;
import org.springframework.data.gemfire.GemfireTemplate;
import org.springframework.data.gemfire.mapping.GemfirePersistentEntity;
import org.springframework.data.gemfire.mapping.GemfirePersistentProperty;
import org.springframework.data.gemfire.mapping.Regions;
import org.springframework.data.gemfire.repository.query.DefaultGemfireEntityInformation;
import org.springframework.data.gemfire.repository.query.GemfireEntityInformation;
import org.springframework.data.gemfire.repository.query.GemfireQueryMethod;
import org.springframework.data.gemfire.repository.query.PartTreeGemfireRepositoryQuery;
import org.springframework.data.gemfire.repository.query.StringBasedGemfireRepositoryQuery;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * {@link RepositoryFactorySupport} implementation creating repository proxies
 * for Gemfire.
 *
 * @author Oliver Gierke
 * @author David Turanski
 * @author John Blum
 */
public class GemfireRepositoryFactory extends RepositoryFactorySupport {

    private static final Class<org.springframework.data.gemfire.mapping.annotation.Region> REGION_ANNOTATION = org.springframework.data.gemfire.mapping.annotation.Region.class;

    static final String REGION_NOT_FOUND = "Region [%1$s] for Domain Type [%2$s] using Repository [%3$s] was not found;"
            + " You must configure a Region with name [%1$s] in the application context";

    static final String REGION_REPOSITORY_ID_TYPE_MISMATCH = "Region [%1$s] requires keys of type [%2$s], but Repository [%3$s] declared an id of type [%4$s]";

    static final String REPOSITORY_ENTITY_ID_TYPE_MISMATCH = "Repository [%1$s] declared an id of type [%2$s], but entity [%3$s] has an id of type [%4$s]";

    private final MappingContext<? extends GemfirePersistentEntity<?>, GemfirePersistentProperty> mappingContext;

    private final Regions regions;

    /**
     * Constructs a new instance of {@link GemfireRepositoryFactory} initialized with the given collection
     * of configured {@link Region Regions} and the {@link MappingContext}.
     *
     * @param regions {@link Iterable} collection of configured {@link Region Regions} used by this application;
     * must not be {@literal null}.
     * @param mappingContext {@link MappingContext} used to map entities to the underlying data store,
     * must not be {@literal null}.
     * @throws IllegalArgumentException if either {@link Regions} or the {@link MappingContext} is {@literal null}.
     * @see org.springframework.data.gemfire.mapping.GemfireMappingContext
     * @see org.springframework.data.gemfire.mapping.Regions
     * @see org.springframework.data.mapping.context.MappingContext
     */
    public GemfireRepositoryFactory(@NonNull Iterable<Region<?, ?>> regions,
            @NonNull MappingContext<? extends GemfirePersistentEntity<?>, GemfirePersistentProperty> mappingContext) {

        Assert.notNull(regions, "Regions are required");
        Assert.notNull(mappingContext, "MappingContext is required");

        this.regions = new Regions(regions, mappingContext);
        this.mappingContext = mappingContext;
    }

    /**
     * Returns a reference to the Pivotal GemFire {@link MappingContext} used to provide mapping meta-data
     * between {@link Class entity types} and the data store.
     *
     * @return a reference to the Pivotal GemFire {@link MappingContext}.
     * @see org.springframework.data.gemfire.mapping.GemfireMappingContext
     * @see org.springframework.data.mapping.context.MappingContext
     */
    protected MappingContext<? extends GemfirePersistentEntity<?>, GemfirePersistentProperty> getMappingContext() {
        return this.mappingContext;
    }

    /**
     * Returns a reference to the configured, application-defined {@link Region Regions}.
     *
     * @return a reference to the configured, application-defined {@link Region Regions}.
     * @see org.springframework.data.gemfire.mapping.Regions
     */
    protected Regions getRegions() {
        return this.regions;
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getEntityInformation(java.lang.Class)
     */
    @Override
    public <T, ID> GemfireEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
        return new DefaultGemfireEntityInformation<>(resolvePersistentEntity(domainClass));
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.repository.core.support.RepositoryFactorySupport
     *    #getRepositoryBaseClass(org.springframework.data.repository.core.RepositoryMetadata)
     */
    @Override
    protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
        return SimpleGemfireRepository.class;
    }

    /*
     * (non-Javadoc)
     * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getTargetRepository(org.springframework.data.repository.core.RepositoryInformation)
     */
    @Override
    protected Object getTargetRepository(RepositoryInformation repositoryInformation) {

        GemfireEntityInformation<?, Serializable> entityInformation = getEntityInformation(
                repositoryInformation.getDomainType());

        GemfireTemplate gemfireTemplate = newTemplate(repositoryInformation);

        return getTargetRepositoryViaReflection(repositoryInformation, gemfireTemplate, entityInformation);
    }

    /**
     * Constructs a new instance of {@link GemfireTemplate} initialized with the identified {@link Region}
     * used to back all persistent, data access operations defined by the {@link Repository}.
     *
     * @param repositoryMetadata {@link RepositoryMetadata} containing meta-data about the {@link Repository}.
     * @return a new instance of {@link GemfireTemplate} initialized with the identified {@link Region}.
     * @see #resolvePersistentEntity(Class)
     * @see #resolveRegion(RepositoryMetadata, GemfirePersistentEntity)
     * @see #validate(RepositoryMetadata, GemfirePersistentEntity, Region)
     * @see org.springframework.data.repository.core.RepositoryMetadata
     * @see org.springframework.data.gemfire.GemfireTemplate
     * @see org.apache.geode.cache.Region
     */
    protected GemfireTemplate newTemplate(RepositoryMetadata repositoryMetadata) {

        GemfirePersistentEntity<?> entity = resolvePersistentEntity(repositoryMetadata.getDomainType());

        return new GemfireTemplate(validate(repositoryMetadata, entity, resolveRegion(repositoryMetadata, entity)));
    }

    @Nullable
    @SuppressWarnings("unchecked")
    private <T> GemfirePersistentEntity<T> resolvePersistentEntity(Class<?> domainType) {
        return (GemfirePersistentEntity<T>) getMappingContext().getPersistentEntity(domainType);
    }

    private Region<?, ?> resolveRegion(RepositoryMetadata repositoryMetadata, GemfirePersistentEntity entity) {

        String resolvedRegionName = getRepositoryRegionName(repositoryMetadata)
                .orElseGet(() -> getEntityRegionName(repositoryMetadata, entity));

        return resolveRegion(repositoryMetadata, resolvedRegionName);
    }

    private Region<?, ?> validate(RepositoryMetadata repositoryMetadata, GemfirePersistentEntity<?> entity,
            Region<?, ?> region) {

        Assert.notNull(region, "Region is required");

        Class<?> repositoryIdType = repositoryMetadata.getIdType();

        Optional.ofNullable(region.getAttributes().getKeyConstraint())
                .ifPresent(regionKeyType -> Assert.isTrue(regionKeyType.isAssignableFrom(repositoryIdType),
                        () -> String.format(REGION_REPOSITORY_ID_TYPE_MISMATCH, region.getFullPath(),
                                regionKeyType.getName(), repositoryMetadata.getRepositoryInterface().getName(),
                                repositoryIdType.getName())));

        Optional.ofNullable(entity).map(GemfirePersistentEntity::getIdProperty)
                .ifPresent(entityIdProperty -> Assert.isTrue(
                        repositoryIdType.isAssignableFrom(entityIdProperty.getType()),
                        () -> String.format(REPOSITORY_ENTITY_ID_TYPE_MISMATCH,
                                repositoryMetadata.getRepositoryInterface().getName(), repositoryIdType.getName(),
                                entityIdProperty.getOwner().getName(), entityIdProperty.getTypeName())));

        return region;
    }

    String getEntityRegionName(@NonNull RepositoryMetadata repositoryMetadata,
            @Nullable GemfirePersistentEntity entity) {

        Optional<GemfirePersistentEntity> optionalEntity = Optional.ofNullable(entity);

        return optionalEntity.map(GemfirePersistentEntity::getRegionName).filter(StringUtils::hasText)
                .orElseGet(() -> optionalEntity.map(GemfirePersistentEntity::getType).map(Class::getSimpleName)
                        .orElseGet(() -> repositoryMetadata.getDomainType().getSimpleName()));
    }

    Optional<String> getRepositoryRegionName(@NonNull RepositoryMetadata repositoryMetadata) {

        return Optional.ofNullable(repositoryMetadata).map(RepositoryMetadata::getRepositoryInterface)
                .filter(repositoryInterface -> repositoryInterface.isAnnotationPresent(REGION_ANNOTATION))
                .map(repositoryInterface -> repositoryInterface.getAnnotation(REGION_ANNOTATION))
                .map(regionAnnotation -> regionAnnotation.value()).filter(StringUtils::hasText);
    }

    Region<?, ?> resolveRegion(@NonNull RepositoryMetadata repositoryMetadata, String regionNamePath) {

        return Optional.ofNullable(getRegions().getRegion(regionNamePath))
                .orElseThrow(() -> newIllegalStateException(REGION_NOT_FOUND, regionNamePath,
                        repositoryMetadata.getDomainType().getName(),
                        repositoryMetadata.getRepositoryInterface().getName()));
    }

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.data.repository.core.support.RepositoryFactorySupport
     *    #getQueryLookupStrategy(Key, EvaluationContextProvider)
     */
    @Override
    protected Optional<QueryLookupStrategy> getQueryLookupStrategy(Key key,
            QueryMethodEvaluationContextProvider evaluationContextProvider) {

        return Optional.of((Method method, RepositoryMetadata repositoryMetadata,
                ProjectionFactory projectionFactory, NamedQueries namedQueries) -> {

            GemfireQueryMethod queryMethod = newQueryMethod(method, repositoryMetadata, projectionFactory,
                    evaluationContextProvider);

            GemfireTemplate template = newTemplate(repositoryMetadata);

            if (queryMethod.hasAnnotatedQuery()) {
                return new StringBasedGemfireRepositoryQuery(queryMethod, template).asUserDefinedQuery();
            }

            String namedQueryName = queryMethod.getNamedQueryName();

            if (namedQueries.hasQuery(namedQueryName)) {
                return new StringBasedGemfireRepositoryQuery(namedQueries.getQuery(namedQueryName), queryMethod,
                        template).asUserDefinedQuery();
            }

            return new PartTreeGemfireRepositoryQuery(queryMethod, template);
        });
    }

    @SuppressWarnings({ "unchecked", "unused" })
    protected <T extends QueryMethod> T newQueryMethod(Method method, RepositoryMetadata repositoryMetadata,
            ProjectionFactory projectionFactory, QueryMethodEvaluationContextProvider evaluationContextProvider) {

        return (T) new GemfireQueryMethod(method, repositoryMetadata, projectionFactory, getMappingContext());
    }
}