View Javadoc
1   /*
2    * Copyright 2021-2025 Brian Thomas Matthews
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.buralotech.oss.ldapunit;
18  
19  import com.unboundid.ldap.listener.InMemoryDirectoryServer;
20  import com.unboundid.ldap.sdk.LDAPException;
21  import com.unboundid.ldif.LDIFException;
22  import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
23  import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
24  import org.junit.jupiter.api.extension.ExtensionContext;
25  import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
26  import org.junit.jupiter.api.extension.ExtensionContext.Store;
27  import org.junit.jupiter.api.extension.ParameterContext;
28  import org.junit.jupiter.api.extension.ParameterResolutionException;
29  import org.junit.jupiter.api.extension.ParameterResolver;
30  
31  import java.io.IOException;
32  
33  /**
34   * JUnit 5 (Jupiter) extension that will start an embedded directory server before the test method execution and
35   * stop the embedded directory server when the test method completes.
36   *
37   * @author <a href="mailto:bmatthews68@gmail.com">Brian Matthews</a>
38   * @since 2.0.0
39   */
40  public class DirectoryServerExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback,
41          ParameterResolver {
42  
43      /**
44       * The name of the property used to cache the reference to the embedded directory server.
45       */
46      private static final String SERVER = "server";
47  
48      /**
49       * This callback is invoked before the test method is executed and is responsible for starting the embedded
50       * directory server.
51       *
52       * @param extensionContext – the extension context for the Executable about to be invoked; never {@code null}.
53       */
54      @Override
55      public void beforeTestExecution(final ExtensionContext extensionContext) {
56          final DirectoryServerConfiguration annotation = getAnnotation(extensionContext);
57          if (annotation != null) {
58              try {
59                  final InMemoryDirectoryServer server = DirectoryServerUtils.startServer(annotation);
60                  getStore(extensionContext).put(SERVER, server);
61              } catch (final LDIFException | LDAPException | IOException e) {
62                  throw new AssertionError("Failed to launch embedded Directory Server", e);
63              }
64          }
65      }
66  
67      /**
68       * This callback is invoked after the test method is executed and is responsible for stopping the embedded
69       * directory server.
70       *
71       * @param extensionContext – the extension context for the Executable about to be invoked; never {@code null}.
72       */
73      @Override
74      public void afterTestExecution(final ExtensionContext extensionContext) {
75          final Store store = getStore(extensionContext);
76          final InMemoryDirectoryServer server = store.get(SERVER, InMemoryDirectoryServer.class);
77          if (server != null) {
78              DirectoryServerUtils.stopServer(server);
79          }
80      }
81  
82      /**
83       * Get teh context storage for the method invocation.
84       *
85       * @param extensionContext – the extension context for the Executable about to be invoked; never {@code null}.
86       * @return The context store.
87       */
88      private Store getStore(final ExtensionContext extensionContext) {
89          final Namespace namespace = Namespace.create(
90                  DirectoryServerExtension.class,
91                  extensionContext.getRequiredTestMethod());
92          return extensionContext.getStore(namespace);
93      }
94  
95      /**
96       * Locate the annotation that specifies the configuration for the embedded directory server. The annotation is
97       * sought on the test method declaration before falling back to check the test class.
98       *
99       * @param extensionContext – the extension context for the Executable about to be invoked; never {@code null}.
100      * @return The {@code DirectoryServerConfiguration} annotation if found. Otherwise, {@code null}.
101      */
102     private DirectoryServerConfiguration getAnnotation(final ExtensionContext extensionContext) {
103         final DirectoryServerConfiguration annotation = extensionContext.getRequiredTestMethod()
104                 .getAnnotation(DirectoryServerConfiguration.class);
105         if (annotation == null) {
106             return extensionContext.getRequiredTestClass().getAnnotation(DirectoryServerConfiguration.class);
107         } else {
108             return annotation;
109         }
110     }
111 
112     /**
113      * Check the parameter type is {@link DirectoryTester}.
114      *
115      * @param parameterContext The context for the parameter for which an argument should be resolved;
116      *                         never {@code null}.
117      * @param extensionContext The extension context for the Executable about to be invoked; never {@code null}.
118      * @return {@code true} if the parameter type is {@link DirectoryTester}. Otherwise, {@code false}.
119      */
120     @Override
121     public boolean supportsParameter(final ParameterContext parameterContext,
122                                      final ExtensionContext extensionContext)
123             throws ParameterResolutionException {
124         return DirectoryTester.class.equals(parameterContext.getParameter().getType());
125     }
126 
127     /**
128      * Resolve {@link DirectoryTester} parameters.
129      *
130      * @param parameterContext The context for the parameter for which an argument should be resolved;
131      *                         never {@code null}.
132      * @param extensionContext –The extension context for the Executable about to be invoked; never {@code null}.
133      * @return The resolved parameter.
134      * @throws ParameterResolutionException If a connection to the directory server could not be established.
135      */
136     @Override
137     public Object resolveParameter(final ParameterContext parameterContext,
138                                    final ExtensionContext extensionContext)
139             throws ParameterResolutionException {
140         final InMemoryDirectoryServer server = getStore(extensionContext).get(SERVER, InMemoryDirectoryServer.class);
141         if (server != null) {
142             try {
143                 return new DirectoryTester(server.getConnection());
144             } catch (final LDAPException e) {
145                 throw new ParameterResolutionException("Cannot connect to directory server", e);
146             }
147         }
148         return null;
149     }
150 }