DATALDAP-5 - Add CDI extension for LDAP repositories.
We now provide a CDI extension to create LDAP repositories in a CDI container. LDAP repositories require a LdapOperations bean as underlying resource manager for LDAP access.
class LdapTemplateProducer {
@Produces
@Singleton
public LdapTemplate createLdapTemplate() {
LdapTemplate ldapTemplateMock = …
return ldapTemplateMock;
}
}
class RepositoryClient {
@Inject
PersonRepository repository;
}
This commit is contained in:
38
pom.xml
38
pom.xml
@@ -86,6 +86,44 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- CDI -->
|
||||
<!-- Dependency order required to build against CDI 1.0 and test with CDI 2.0 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.geronimo.specs</groupId>
|
||||
<artifactId>geronimo-jcdi_2.0_spec</artifactId>
|
||||
<version>1.0.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.interceptor</groupId>
|
||||
<artifactId>javax.interceptor-api</artifactId>
|
||||
<version>1.2.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.enterprise</groupId>
|
||||
<artifactId>cdi-api</artifactId>
|
||||
<version>${cdi}</version>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
<version>${javax-annotation-api}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.openwebbeans</groupId>
|
||||
<artifactId>openwebbeans-se</artifactId>
|
||||
<version>${webbeans}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<repositories>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
[[new-features]]
|
||||
= New & Noteworthy
|
||||
|
||||
[[new-features.2.1]]
|
||||
== What's new in Spring Data LDAP 2.1
|
||||
* CDI extension to create LDAP repositories within a CDI container.
|
||||
|
||||
[[new-features.2.0]]
|
||||
== What's new in Spring Data LDAP 2.0
|
||||
* Enhanced tooling support by using Spring Framework's `@NonNullApi` and `@Nullable` annotations.
|
||||
|
||||
@@ -229,3 +229,39 @@ Basic QueryDSL support is included in Spring LDAP. This support includes the fol
|
||||
* A Query implementation, `QueryDslLdapQuery`, for building and executing QueryDSL queries in code.
|
||||
* Spring Data repository support for QueryDSL predicates. `QueryDslPredicateExecutor` includes a number of additional methods with appropriate parameters; extend this interface along with `LdapRepository` to include this support in your repository.
|
||||
|
||||
[[ldap.repositories.misc]]
|
||||
== Miscellaneous
|
||||
|
||||
[[ldap.repositories.misc.cdi-integration]]
|
||||
=== CDI Integration
|
||||
|
||||
Instances of the repository interfaces are usually created by a container, which Spring is the most natural choice when working with Spring Data. As of version 2.1 Spring Data LDAP ships with a custom CDI extension that allows using the repository abstraction in CDI environments. The extension is part of the JAR so all you need to do to activate it is dropping the Spring Data LDAP JAR into your classpath. You can now set up the infrastructure by implementing a CDI Producer for the `LdapTemplate`:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
class LdapTemplateProducer {
|
||||
|
||||
@Produces
|
||||
@ApplicationScoped
|
||||
public LdapOperations createLdapTemplate() {
|
||||
|
||||
ContextSource contextSource = …
|
||||
return new LdapTemplate(contextSource);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The Spring Data LDAP CDI extension will pick up the `LdapTemplate` available as CDI bean and create a proxy for a Spring Data repository whenever a bean of a repository type is requested by the container. Thus obtaining an instance of a Spring Data repository is a matter of declaring an `@Inject`-ed property:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
class RepositoryClient {
|
||||
|
||||
@Inject
|
||||
PersonRepository repository;
|
||||
|
||||
public void businessMethod() {
|
||||
List<Person> people = repository.findAll();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* 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.springframework.data.ldap.repository.cdi;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.enterprise.context.spi.CreationalContext;
|
||||
import javax.enterprise.inject.spi.Bean;
|
||||
import javax.enterprise.inject.spi.BeanManager;
|
||||
|
||||
import org.springframework.data.ldap.repository.support.LdapRepositoryFactory;
|
||||
import org.springframework.data.repository.cdi.CdiRepositoryBean;
|
||||
import org.springframework.data.repository.config.CustomRepositoryImplementationDetector;
|
||||
import org.springframework.ldap.core.LdapOperations;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link CdiRepositoryBean} to create LDAP repository instances.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
public class LdapRepositoryBean<T> extends CdiRepositoryBean<T> {
|
||||
|
||||
private final Bean<LdapOperations> operations;
|
||||
|
||||
/**
|
||||
* Creates a new {@link LdapRepositoryBean}.
|
||||
*
|
||||
* @param operations must not be {@literal null}.
|
||||
* @param qualifiers must not be {@literal null}.
|
||||
* @param repositoryType must not be {@literal null}.
|
||||
* @param beanManager must not be {@literal null}.
|
||||
* @param detector detector for the custom {@link org.springframework.data.repository.Repository} implementations
|
||||
* {@link CustomRepositoryImplementationDetector}, can be {@link Optional#empty()}.
|
||||
*/
|
||||
LdapRepositoryBean(Bean<LdapOperations> operations, Set<Annotation> qualifiers, Class<T> repositoryType,
|
||||
BeanManager beanManager, Optional<CustomRepositoryImplementationDetector> detector) {
|
||||
|
||||
super(qualifiers, repositoryType, beanManager, detector);
|
||||
|
||||
Assert.notNull(operations, "LdapOperations bean must not be null!");
|
||||
this.operations = operations;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.cdi.CdiRepositoryBean#create(javax.enterprise.context.spi.CreationalContext, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
protected T create(CreationalContext<T> creationalContext, Class<T> repositoryType,
|
||||
Optional<Object> customImplementation) {
|
||||
|
||||
LdapOperations ldapOperations = getDependencyInstance(operations, LdapOperations.class);
|
||||
LdapRepositoryFactory factory = new LdapRepositoryFactory(ldapOperations);
|
||||
|
||||
return customImplementation.map(o -> factory.getRepository(repositoryType, o))
|
||||
.orElseGet(() -> factory.getRepository(repositoryType));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* 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.springframework.data.ldap.repository.cdi;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.enterprise.event.Observes;
|
||||
import javax.enterprise.inject.UnsatisfiedResolutionException;
|
||||
import javax.enterprise.inject.spi.AfterBeanDiscovery;
|
||||
import javax.enterprise.inject.spi.Bean;
|
||||
import javax.enterprise.inject.spi.BeanManager;
|
||||
import javax.enterprise.inject.spi.ProcessBean;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.repository.cdi.CdiRepositoryBean;
|
||||
import org.springframework.data.repository.cdi.CdiRepositoryExtensionSupport;
|
||||
import org.springframework.ldap.core.LdapOperations;
|
||||
|
||||
/**
|
||||
* CDI extension to export LDAP repositories.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
public class LdapRepositoryExtension extends CdiRepositoryExtensionSupport {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LdapRepositoryExtension.class);
|
||||
|
||||
private final Map<Set<Annotation>, Bean<LdapOperations>> ldapOperations = new HashMap<>();
|
||||
|
||||
public LdapRepositoryExtension() {
|
||||
LOG.info("Activating CDI extension for Spring Data LDAP repositories.");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
<X> void processBean(@Observes ProcessBean<X> processBean) {
|
||||
|
||||
Bean<X> bean = processBean.getBean();
|
||||
|
||||
for (Type type : bean.getTypes()) {
|
||||
if (type instanceof Class<?> && LdapOperations.class.isAssignableFrom((Class<?>) type)) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug(
|
||||
String.format("Discovered %s with qualifiers %s.", LdapOperations.class.getName(), bean.getQualifiers()));
|
||||
}
|
||||
|
||||
// Store the EntityManager bean using its qualifiers.
|
||||
ldapOperations.put(new HashSet<>(bean.getQualifiers()), (Bean<LdapOperations>) bean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) {
|
||||
|
||||
for (Entry<Class<?>, Set<Annotation>> entry : getRepositoryTypes()) {
|
||||
|
||||
Class<?> repositoryType = entry.getKey();
|
||||
Set<Annotation> qualifiers = entry.getValue();
|
||||
|
||||
// Create the bean representing the repository.
|
||||
CdiRepositoryBean<?> repositoryBean = createRepositoryBean(repositoryType, qualifiers, beanManager);
|
||||
|
||||
if (LOG.isInfoEnabled()) {
|
||||
LOG.info(String.format("Registering bean for %s with qualifiers %s.", repositoryType.getName(), qualifiers));
|
||||
}
|
||||
|
||||
// Register the bean to the container.
|
||||
registerBean(repositoryBean);
|
||||
afterBeanDiscovery.addBean(repositoryBean);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link CdiRepositoryBean} for the repository of the given type.
|
||||
*
|
||||
* @param <T> the type of the repository.
|
||||
* @param repositoryType the class representing the repository.
|
||||
* @param qualifiers the qualifiers to be applied to the bean.
|
||||
* @param beanManager the BeanManager instance.
|
||||
* @return the repository bean.
|
||||
*/
|
||||
private <T> CdiRepositoryBean<T> createRepositoryBean(Class<T> repositoryType, Set<Annotation> qualifiers,
|
||||
BeanManager beanManager) {
|
||||
|
||||
// Determine the LdapOperations bean which matches the qualifiers of the repository.
|
||||
Bean<LdapOperations> LdapOperations = this.ldapOperations.get(qualifiers);
|
||||
|
||||
if (LdapOperations == null) {
|
||||
throw new UnsatisfiedResolutionException(String.format("Unable to resolve a bean for '%s' with qualifiers %s.",
|
||||
LdapOperations.class.getName(), qualifiers));
|
||||
}
|
||||
|
||||
// Construct and return the repository bean.
|
||||
return new LdapRepositoryBean<>(LdapOperations, qualifiers, repositoryType, beanManager,
|
||||
Optional.of(getCustomImplementationDetector()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* CDI support for LDAP specific repository implementation.
|
||||
*/
|
||||
@org.springframework.lang.NonNullApi
|
||||
package org.springframework.data.ldap.repository.cdi;
|
||||
@@ -0,0 +1 @@
|
||||
org.springframework.data.ldap.repository.cdi.LdapRepositoryExtension
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* 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.springframework.data.ldap.repository.cdi;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.enterprise.inject.se.SeContainer;
|
||||
import javax.enterprise.inject.se.SeContainerInitializer;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.ldap.config.DummyEntity;
|
||||
import org.springframework.ldap.core.LdapTemplate;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link LdapRepositoryExtension}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class CdiExtensionIntegrationTests {
|
||||
|
||||
static SeContainer container;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp() {
|
||||
|
||||
container = SeContainerInitializer.newInstance() //
|
||||
.disableDiscovery() //
|
||||
.addPackages(CdiExtensionIntegrationTests.class) //
|
||||
.initialize();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
container.close();
|
||||
}
|
||||
|
||||
@Test // DATALDAP-5
|
||||
public void bootstrapsRepositoryCorrectly() {
|
||||
|
||||
RepositoryClient client = container.select(RepositoryClient.class).get();
|
||||
LdapTemplate ldapTemplateMock = client.getLdapTemplate();
|
||||
|
||||
DummyEntity entity = new DummyEntity();
|
||||
when(ldapTemplateMock.findAll(DummyEntity.class)).thenReturn(Collections.singletonList(entity));
|
||||
|
||||
SampleRepository repository = client.getSampleRepository();
|
||||
|
||||
assertThat(repository).isNotNull();
|
||||
|
||||
repository.deleteAll();
|
||||
|
||||
verify(client.getLdapTemplate()).delete(entity);
|
||||
}
|
||||
|
||||
@Test // DATALDAP-5
|
||||
public void returnOneFromCustomImpl() {
|
||||
|
||||
RepositoryClient repositoryConsumer = container.select(RepositoryClient.class).get();
|
||||
assertThat(repositoryConsumer.getSampleRepository().returnOne()).isEqualTo(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* 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.springframework.data.ldap.repository.cdi;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import javax.enterprise.inject.Produces;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.springframework.ldap.core.LdapTemplate;
|
||||
import org.springframework.ldap.odm.core.ObjectDirectoryMapper;
|
||||
|
||||
/**
|
||||
* Simple component exposing a {@link LdapTemplate} instance as CDI bean.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class LdapTemplateProducer {
|
||||
|
||||
@Produces
|
||||
@Singleton
|
||||
public LdapTemplate createLdapTemplate() {
|
||||
|
||||
LdapTemplate ldapTemplateMock = mock(LdapTemplate.class);
|
||||
ObjectDirectoryMapper odmMock = mock(ObjectDirectoryMapper.class);
|
||||
|
||||
when(ldapTemplateMock.getObjectDirectoryMapper()).thenReturn(odmMock);
|
||||
|
||||
return ldapTemplateMock;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* 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.springframework.data.ldap.repository.cdi;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.springframework.ldap.core.LdapTemplate;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Getter
|
||||
class RepositoryClient {
|
||||
|
||||
@Inject SampleRepository sampleRepository;
|
||||
@Inject LdapTemplate ldapTemplate;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* 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.springframework.data.ldap.repository.cdi;
|
||||
|
||||
import javax.naming.Name;
|
||||
|
||||
import org.springframework.data.ldap.config.DummyEntity;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface SampleRepository extends CrudRepository<DummyEntity, Name>, SampleRepositoryCustom {}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* 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.springframework.data.ldap.repository.cdi;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
interface SampleRepositoryCustom {
|
||||
|
||||
int returnOne();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2017 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
|
||||
*
|
||||
* 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.springframework.data.ldap.repository.cdi;
|
||||
|
||||
/**
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class SampleRepositoryImpl implements SampleRepositoryCustom {
|
||||
|
||||
@Override
|
||||
public int returnOne() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user