Java tutorial
/* * Copyright 2011-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.repository.cdi; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.inject.Alternative; import javax.enterprise.inject.Stereotype; import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.InjectionPoint; import javax.enterprise.inject.spi.PassivationCapable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.repository.config.CustomRepositoryImplementationDetector; import org.springframework.data.repository.config.RepositoryFragmentConfiguration; import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments; import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.data.repository.core.support.RepositoryFragment; import org.springframework.data.util.Optionals; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Base class for {@link Bean} wrappers. * * @author Dirk Mahler * @author Oliver Gierke * @author Mark Paluchs * @author Peter Rietzler * @author Jens Schauder * @author Christoph Strobl * @author Ariel Carrera */ public abstract class CdiRepositoryBean<T> implements Bean<T>, PassivationCapable { private static final Logger LOGGER = LoggerFactory.getLogger(CdiRepositoryBean.class); private static final CdiRepositoryConfiguration DEFAULT_CONFIGURATION = DefaultCdiRepositoryConfiguration.INSTANCE; private final Set<Annotation> qualifiers; private final Class<T> repositoryType; private final CdiRepositoryContext context; private final BeanManager beanManager; private final String passivationId; private transient @Nullable T repoInstance; /** * Creates a new {@link CdiRepositoryBean}. * * @param qualifiers must not be {@literal null}. * @param repositoryType has to be an interface must not be {@literal null}. * @param beanManager the CDI {@link BeanManager}, must not be {@literal null}. */ public CdiRepositoryBean(Set<Annotation> qualifiers, Class<T> repositoryType, BeanManager beanManager) { this(qualifiers, repositoryType, beanManager, new CdiRepositoryContext(CdiRepositoryBean.class.getClassLoader())); } /** * Creates a new {@link CdiRepositoryBean}. * * @param qualifiers must not be {@literal null}. * @param repositoryType has to be an interface must not be {@literal null}. * @param beanManager the CDI {@link BeanManager}, must not be {@literal null}. * @param detector detector for the custom repository implementations {@link CustomRepositoryImplementationDetector}. */ public CdiRepositoryBean(Set<Annotation> qualifiers, Class<T> repositoryType, BeanManager beanManager, Optional<CustomRepositoryImplementationDetector> detector) { Assert.notNull(qualifiers, "Qualifiers must not be null!"); Assert.notNull(beanManager, "BeanManager must not be null!"); Assert.notNull(repositoryType, "Repoitory type must not be null!"); Assert.isTrue(repositoryType.isInterface(), "RepositoryType must be an interface!"); this.qualifiers = qualifiers; this.repositoryType = repositoryType; this.beanManager = beanManager; this.context = new CdiRepositoryContext(getClass().getClassLoader(), detector.orElseThrow( () -> new IllegalArgumentException("CustomRepositoryImplementationDetector must be present!"))); this.passivationId = createPassivationId(qualifiers, repositoryType); } /** * Creates a new {@link CdiRepositoryBean}. * * @param qualifiers must not be {@literal null}. * @param repositoryType has to be an interface must not be {@literal null}. * @param beanManager the CDI {@link BeanManager}, must not be {@literal null}. * @param context CDI context encapsulating class loader, metadata scanning and fragment detection. * @since 2.1 */ public CdiRepositoryBean(Set<Annotation> qualifiers, Class<T> repositoryType, BeanManager beanManager, CdiRepositoryContext context) { Assert.notNull(qualifiers, "Qualifiers must not be null!"); Assert.notNull(beanManager, "BeanManager must not be null!"); Assert.notNull(repositoryType, "Repoitory type must not be null!"); Assert.isTrue(repositoryType.isInterface(), "RepositoryType must be an interface!"); this.qualifiers = qualifiers; this.repositoryType = repositoryType; this.beanManager = beanManager; this.context = context; this.passivationId = createPassivationId(qualifiers, repositoryType); } /** * Creates a unique identifier for the given repository type and the given annotations. * * @param qualifiers must not be {@literal null} or contain {@literal null} values. * @param repositoryType must not be {@literal null}. * @return */ private String createPassivationId(Set<Annotation> qualifiers, Class<?> repositoryType) { List<String> qualifierNames = new ArrayList<>(qualifiers.size()); for (Annotation qualifier : qualifiers) { qualifierNames.add(qualifier.annotationType().getName()); } Collections.sort(qualifierNames); return StringUtils.collectionToDelimitedString(qualifierNames, ":") + ":" + repositoryType.getName(); } /* * (non-Javadoc) * @see javax.enterprise.inject.spi.Bean#getTypes() */ @SuppressWarnings("rawtypes") public Set<Type> getTypes() { Set<Class> interfaces = new HashSet<>(); interfaces.add(repositoryType); interfaces.addAll(Arrays.asList(repositoryType.getInterfaces())); return new HashSet<>(interfaces); } /** * Returns an instance of an the given {@link Bean}. * * @param bean the {@link Bean} about to create an instance for. * @return the actual component instance. * @see Bean#getTypes() */ protected <S> S getDependencyInstance(Bean<S> bean) { return getDependencyInstance(bean, bean.getBeanClass()); } /** * Returns an instance of an the given {@link Bean} and allows to be specific about the type that is about to be * created. * * @param bean the {@link Bean} about to create an instance for. * @param type the expected type of the component instance created for that {@link Bean}. We need to hand this * parameter explicitly as the {@link Bean} might carry multiple types but the primary one might not be the * first, i.e. the one returned by {@link Bean#getBeanClass()}. * @return the actual component instance. * @see Bean#getTypes() */ @SuppressWarnings("unchecked") protected <S> S getDependencyInstance(Bean<S> bean, Class<?> type) { CreationalContext<S> creationalContext = beanManager.createCreationalContext(bean); return (S) beanManager.getReference(bean, type, creationalContext); } /** * Forces the initialization of bean target. */ public final void initialize() { create(beanManager.createCreationalContext(this)); } /* * (non-Javadoc) * @see javax.enterprise.context.spi.Contextual#create(javax.enterprise.context.spi.CreationalContext) */ public final T create(@SuppressWarnings("null") CreationalContext<T> creationalContext) { T repoInstance = this.repoInstance; if (repoInstance != null) { LOGGER.debug("Returning eagerly created CDI repository instance for {}.", repositoryType.getName()); return repoInstance; } LOGGER.debug("Creating CDI repository bean instance for {}.", repositoryType.getName()); repoInstance = create(creationalContext, repositoryType); this.repoInstance = repoInstance; return repoInstance; } /* * (non-Javadoc) * @see javax.enterprise.context.spi.Contextual#destroy(java.lang.Object, javax.enterprise.context.spi.CreationalContext) */ public void destroy(@SuppressWarnings("null") T instance, @SuppressWarnings("null") CreationalContext<T> creationalContext) { if (LOGGER.isDebugEnabled()) { LOGGER.debug(String.format("Destroying bean instance %s for repository type '%s'.", instance.toString(), repositoryType.getName())); } creationalContext.release(); } /* * (non-Javadoc) * @see javax.enterprise.inject.spi.Bean#getQualifiers() */ public Set<Annotation> getQualifiers() { return qualifiers; } /* * (non-Javadoc) * @see javax.enterprise.inject.spi.Bean#getName() */ public String getName() { return repositoryType.getName(); } /* * (non-Javadoc) * @see javax.enterprise.inject.spi.Bean#getStereotypes() */ public Set<Class<? extends Annotation>> getStereotypes() { return Arrays.stream(repositoryType.getAnnotations())// .map(Annotation::annotationType)// .filter(it -> it.isAnnotationPresent(Stereotype.class))// .collect(Collectors.toSet()); } /* * (non-Javadoc) * @see javax.enterprise.inject.spi.Bean#getBeanClass() */ public Class<?> getBeanClass() { return repositoryType; } /* * (non-Javadoc) * @see javax.enterprise.inject.spi.Bean#isAlternative() */ public boolean isAlternative() { return repositoryType.isAnnotationPresent(Alternative.class); } /* * (non-Javadoc) * @see javax.enterprise.inject.spi.Bean#isNullable() */ public boolean isNullable() { return false; } /* * (non-Javadoc) * @see javax.enterprise.inject.spi.Bean#getInjectionPoints() */ public Set<InjectionPoint> getInjectionPoints() { return Collections.emptySet(); } /* * (non-Javadoc) * @see javax.enterprise.inject.spi.Bean#getScope() */ public Class<? extends Annotation> getScope() { return ApplicationScoped.class; } /* * (non-Javadoc) * @see javax.enterprise.inject.spi.PassivationCapable#getId() */ public String getId() { return passivationId; } /** * Creates the actual component instance. * * @param creationalContext will never be {@literal null}. * @param repositoryType will never be {@literal null}. * @return */ protected T create(CreationalContext<T> creationalContext, Class<T> repositoryType) { CdiRepositoryConfiguration cdiRepositoryConfiguration = lookupConfiguration(beanManager, qualifiers); Optional<Bean<?>> customImplementationBean = getCustomImplementationBean(repositoryType, cdiRepositoryConfiguration); Optional<Object> customImplementation = customImplementationBean.map(this::getDependencyInstance); return create(creationalContext, repositoryType, customImplementation); } /** * Creates the actual component instance given a {@link RepositoryFactorySupport repository factory supplier} and the * repository {@link Class type}. This method is an utility for to create a repository. This method will obtain a * {@link RepositoryFactorySupport repository factory} and configure it with {@link CdiRepositoryConfiguration}. * * @param factorySupplier must not be {@literal null}. * @param repositoryType must not be {@literal null}. * @return * @since 2.1 */ protected T create(Supplier<? extends RepositoryFactorySupport> factorySupplier, Class<T> repositoryType) { CdiRepositoryConfiguration configuration = lookupConfiguration(beanManager, qualifiers); RepositoryFragments repositoryFragments = getRepositoryFragments(repositoryType, configuration); RepositoryFactorySupport factory = factorySupplier.get(); applyConfiguration(factory, configuration); return create(factory, repositoryType, repositoryFragments); } /** * Lookup repository fragments for a {@link Class repository interface}. * * @param repositoryType must not be {@literal null}. * @return the {@link RepositoryFragments}. * @since 2.1 */ protected RepositoryFragments getRepositoryFragments(Class<T> repositoryType) { Assert.notNull(repositoryType, "Repository type must not be null!"); CdiRepositoryConfiguration cdiRepositoryConfiguration = lookupConfiguration(beanManager, qualifiers); return getRepositoryFragments(repositoryType, cdiRepositoryConfiguration); } private RepositoryFragments getRepositoryFragments(Class<T> repositoryType, CdiRepositoryConfiguration cdiRepositoryConfiguration) { Optional<Bean<?>> customImplementationBean = getCustomImplementationBean(repositoryType, cdiRepositoryConfiguration); Optional<Object> customImplementation = customImplementationBean.map(this::getDependencyInstance); List<RepositoryFragment<?>> repositoryFragments = findRepositoryFragments(repositoryType, cdiRepositoryConfiguration); RepositoryFragments customImplementationFragment = customImplementation // .map(RepositoryFragments::just) // .orElseGet(RepositoryFragments::empty); return RepositoryFragments.from(repositoryFragments) // .append(customImplementationFragment); } @SuppressWarnings("unchecked") private List<RepositoryFragment<?>> findRepositoryFragments(Class<T> repositoryType, CdiRepositoryConfiguration cdiRepositoryConfiguration) { Stream<RepositoryFragmentConfiguration> fragmentConfigurations = context .getRepositoryFragments(cdiRepositoryConfiguration, repositoryType); return fragmentConfigurations.flatMap(it -> { Class<Object> interfaceClass = (Class<Object>) lookupFragmentInterface(repositoryType, it.getInterfaceName()); Class<?> implementationClass = context.loadClass(it.getClassName()); Optional<Bean<?>> bean = getBean(implementationClass, beanManager, qualifiers); return Optionals.toStream(bean.map(this::getDependencyInstance) // .map(implementation -> RepositoryFragment.implemented(interfaceClass, implementation))); // }).collect(Collectors.toList()); } private static Class<?> lookupFragmentInterface(Class<?> repositoryType, String interfaceName) { return Arrays.stream(repositoryType.getInterfaces()) // .filter(it -> it.getName().equals(interfaceName)) // .findFirst() // .orElseThrow(() -> new IllegalArgumentException(String.format("Did not find type %s in %s!", interfaceName, Arrays.asList(repositoryType.getInterfaces())))); } /** * Creates the actual component instance. * * @param creationalContext will never be {@literal null}. * @param repositoryType will never be {@literal null}. * @param customImplementation can be {@literal null}. * @return * @deprecated since 2.1, override {@link #create(CreationalContext, Class)} in which you create a repository factory * and call {@link #create(RepositoryFactorySupport, Class, RepositoryFragments)}. */ @Deprecated protected T create(CreationalContext<T> creationalContext, Class<T> repositoryType, Optional<Object> customImplementation) { throw new UnsupportedOperationException( "You have to implement create(CreationalContext<T>, Class<T>, Optional<Object>) " + "in order to use custom repository implementations"); } /** * Looks up an instance of a {@link CdiRepositoryConfiguration}. In case the instance cannot be found within the CDI * scope, a default configuration is used. * * @return an available CdiRepositoryConfiguration instance or a default configuration. */ protected CdiRepositoryConfiguration lookupConfiguration(BeanManager beanManager, Set<Annotation> qualifiers) { return beanManager.getBeans(CdiRepositoryConfiguration.class, getQualifiersArray(qualifiers)).stream() .findFirst()// .map(it -> (CdiRepositoryConfiguration) getDependencyInstance(it)) // .orElse(DEFAULT_CONFIGURATION); } /** * Try to lookup a custom implementation for a {@link org.springframework.data.repository.Repository}. Can only be * used when a {@code CustomRepositoryImplementationDetector} is provided. * * @param repositoryType * @param cdiRepositoryConfiguration * @return the custom implementation instance or null */ private Optional<Bean<?>> getCustomImplementationBean(Class<?> repositoryType, CdiRepositoryConfiguration cdiRepositoryConfiguration) { return context.getCustomImplementationClass(repositoryType, cdiRepositoryConfiguration)// .flatMap(type -> getBean(type, beanManager, qualifiers)); } /** * Applies the configuration from {@link CdiRepositoryConfiguration} to {@link RepositoryFactorySupport} by looking up * the actual configuration. * * @param repositoryFactory will never be {@literal null}. * @since 2.1 */ protected void applyConfiguration(RepositoryFactorySupport repositoryFactory) { applyConfiguration(repositoryFactory, lookupConfiguration(beanManager, qualifiers)); } /** * Applies the configuration from {@link CdiRepositoryConfiguration} to {@link RepositoryFactorySupport} by looking up * the actual configuration. * * @param repositoryFactory will never be {@literal null}. * @param configuration will never be {@literal null}. * @since 2.1 */ protected static void applyConfiguration(RepositoryFactorySupport repositoryFactory, CdiRepositoryConfiguration configuration) { configuration.getEvaluationContextProvider().ifPresent(repositoryFactory::setEvaluationContextProvider); configuration.getNamedQueries().ifPresent(repositoryFactory::setNamedQueries); configuration.getQueryLookupStrategy().ifPresent(repositoryFactory::setQueryLookupStrategyKey); configuration.getRepositoryBeanClass().ifPresent(repositoryFactory::setRepositoryBaseClass); configuration.getRepositoryProxyPostProcessors() .forEach(repositoryFactory::addRepositoryProxyPostProcessor); configuration.getQueryCreationListeners().forEach(repositoryFactory::addQueryCreationListener); } /** * Creates the actual repository instance. * * @param repositoryType will never be {@literal null}. * @param repositoryFragments will never be {@literal null}. * @return */ protected static <T> T create(RepositoryFactorySupport repositoryFactory, Class<T> repositoryType, RepositoryFragments repositoryFragments) { return repositoryFactory.getRepository(repositoryType, repositoryFragments); } private static Optional<Bean<?>> getBean(Class<?> beanType, BeanManager beanManager, Set<Annotation> qualifiers) { return beanManager.getBeans(beanType, getQualifiersArray(qualifiers)).stream().findFirst(); } private static Annotation[] getQualifiersArray(Set<Annotation> qualifiers) { return qualifiers.toArray(new Annotation[qualifiers.size()]); } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return String.format("CdiRepositoryBean: type='%s', qualifiers=%s", repositoryType.getName(), qualifiers.toString()); } enum DefaultCdiRepositoryConfiguration implements CdiRepositoryConfiguration { INSTANCE; } }