I am testing a spring retry, but it seems the recover is not being called. Tried to get it work but it seems exhaustive. I passed to #Recover no argument, Throwable, Exception. Changed retry dependency version, and it seems it is included with aop for spring boot and removed it. Kept getting recover is not being call with the following exception messege.
Request processing failed; nested exception is
org.springframework.retry.ExhaustedRetryException: Cannot locate
recovery method; nested exception is java.lang.ArithmeticException: /
by zero] with root cause
Any help would be much appreciated
The code i have looks like below.
Configuration class
package hello;
import java.util.Arrays;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.annotation.Retryable;
#SpringBootApplication
#EnableRetry
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
return args -> {
System.out.println("Let's inspect the beans provided by `Spring Boot:");`
String[] beanNames = ctx.getBeanDefinitionNames();
Arrays.sort(beanNames);
for (String beanName : beanNames) {
System.out.println(beanName);
}
};
}
}
Rest Controller class;
package hello;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.retry.annotation.Backoff;
import org.springframework.web.bind.annotation.RequestMapping;
#RestController
public class HelloController {
#Autowired
private SomeService service;
#RequestMapping("/")
public String hello() {
String result = service.getInfo();
return result;
}
}
Service class is ;
package hello;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
#Service
public class SomeService {
#Retryable(value = ArithmeticException.class, maxAttempts = 3, `backoff = #Backoff(delay = 3000))`
public String getInfo() {
System.out.println("How many time will this be printed?");
return "Hello" + 4/0;
}
#Recover
public void helpHere(ArithmeticException cause) {
System.out.println(cause);
System.out.println("Recovery place!");
}
}
This is my dependencies list
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- tag::actuator[] -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- end::actuator[] -->
<!-- tag::tests[] -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- end::tests[] -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
With try-catch and variety of arguments
#Service
public class SomeService {
#Retryable(value = {ArithmeticException.class}, maxAttempts = 3, `backoff = #Backoff(delay = 3000))`
public String getInfo() {
try {
System.out.println("How many time will this be printed?");
return "Hello" + 4/0;
} catch(ArithmeticException ex) {
System.out.println("In the arthemetic Exception");
throw new ArithmeticException();
}
}
#Recover
public void helpHere(ArithmeticException cause) {
System.out.println(cause);
System.out.println("Recovery place! ArithmeticException");
}
#Recover
public void helpHere(Exception cause ) {
System.out.println(cause);
System.out.println("Recovery place! Exception");
}
#Recover
public void helpHere(Throwable cause) {
System.out.println(cause);
System.out.println("Recovery place! Exception");
}
#Recover
public void helpHere() {
System.out.println("Recovery place! Exception");
}
}
Screen shot of the console
I finally got the answer.
For a method annotated with #Recover to be invoked, it has to have the same method argument(plus the exception) and the same return type.
I tested it with different type of exception argument and methods are called if they have more specific exception type. If I have a method like this will be called than one with Exception argument. However, if I have multiple recover methods, only one with the more specific exception argument will be called.
#Recover
public String helpHere(ArithmeticException cause) {
Final code Example
package hello;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
#Service
public class SomeService {
#Retryable(maxAttempts = 3, backoff = #Backoff(delay = 3000))
public String getInfo() {
try {
System.out.println("How many time will this be printed?");
return "Hello" + 4/0;
} catch(Exception ex) {
System.out.println("In the arthemetic Exception");
throw new ArithmeticException();
}
}
#Recover
public String helpHere(ArithmeticException cause) {
System.out.println("Recovery place! ArithmeticException");
return "Hello";
}
#Recover
public String helpHere(Exception cause ) {
System.out.println("Recovery place! Exception");
return "Hello";
}
#Recover
public String helpHere() {
System.out.println("Recovery place! Exception");
return "Hello";
}
#Recover
public String helpHere(Throwable cause) {
System.out.println("Recovery place! Throwable");
return "Hello";
}
You should use try-catch to handle it. Here the example
#Retryable(value = ArithmeticException.class, maxAttempts = 5, backoff = #Backoff(delay = 3000))
public String getInfo() {
try {
System.out.println("How many time will this be printed?");
return "Hello" + 4 / 0;
} catch (ArithmeticException ex) {
// will be retried
throw ex;
}
}
throw ex; is a must as it is telling Spring to apply retry handling.
With #Recover we define a separate recovery method for ArithmeticException. This allows us to run special recovery code when a retryable method fails with ArithmeticException.
You may refer more on How to handle retry with Spring-Retry ?
Edit
Based on the latest exception,try provide version for spring-retry
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.2.1.RELEASE</version>
</dependency>
#Recover method should have the same return type and the Exception thrown as the original method for which you are writing the retry method.
#Recover public String connectionException(RetryException e)
throws RetryException{
return e.getMessage(); }
Related
I am trying to validate some fields of User with Validator. I am not able to catch the error in ItemProcessListener.
My validator is like so.
public class UserValidator implements Validator<User> {
#Override
public void validate(User user) throws ValidationException {
if (user.getName().length() > 3) {
throw new ValidationException("User name cannot have more than 3 alphabets");
}
}
}
I have Skiplistener like so..
public class UserValidationListener implements SkipListener<User, User> {
#Override
public void onSkipInRead(Throwable throwable) {
System.out.println(throwable.getMessage());
}
#Override
public void onSkipInWrite(User user, Throwable throwable) {
System.out.println(user.toString());
System.out.println(throwable.getMessage());
}
#Override
public void onSkipInProcess(User user, Throwable throwable) {
System.out.println(user.toString());
System.out.println(throwable.getMessage());
// No error message comes here
// 1. get the error message thrown by Validation exception
// 2. Save it db logic
}
}
In the batch job config I have created a bean to filter out the faulty user data.
#Bean
public ValidatingItemProcessor<User> validatingItemProcessor() {
ValidatingItemProcessor<User> itemProcessor = new ValidatingItemProcessor<>(new UserValidator());
itemProcessor.setFilter(true);
return itemProcessor;
}
And finally here is my batch job config.
Step step = stepBuilderFactory.get("ETL-file-load")
.<User, User>chunk(100)
.reader(itemReader)
.processor(validatingItemProcessor())
.writer(itemWriter)
.faultTolerant()
.skip(ValidationException.class)
.listener(userValidationListener())
.build();
return jobBuilderFactory.get("ETL-Load")
.incrementer(new RunIdIncrementer())
.start(step)
.build();
I have created a bean for listener like so:
#Bean
public UserValidationListener userValidationListener() {
return new UserValidationListener();
}
I am not able to catch the error message thrown by the UserValidator with ValidationException. How do I do that?
Here's an example that skips odd numbers and writes even numbers, hopefully this will help you
import org.springframework.batch.core.Job;
import org.springframework.batch.core.SkipListener;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.step.skip.SkipPolicy;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.batch.item.validator.ValidatingItemProcessor;
import org.springframework.batch.item.validator.ValidationException;
import org.springframework.batch.item.validator.Validator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
#Configuration
#EnableBatchProcessing
public class SimpleJobConfig {
#Bean
public Job simpleJob(JobBuilderFactory jobBuilderFactory) {
return jobBuilderFactory.get("SimpleJob")
.start(simpleJobStep(null))
.build();
}
#Bean
public Step simpleJobStep(StepBuilderFactory stepBuilderFactory) {
return stepBuilderFactory.get("simpleJobStep")
.<Integer, Integer>chunk(1)
.reader(integerReader())
.processor(validatingItemProcessor())
.writer(integerWriter())
.faultTolerant()
.skipPolicy(skipPolicy())
.listener(skipListener())
.build();
}
#Bean
public ItemReader<Integer> integerReader() {
return new ListItemReader<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
}
#Bean
public ValidatingItemProcessor<Integer> validatingItemProcessor() {
return new ValidatingItemProcessor<>(evenNumberValidator());
}
#Bean
public ItemWriter<Integer> integerWriter() {
return items -> items.forEach(item -> System.out.println("Writing " + item));
}
public Validator<Integer> evenNumberValidator() {
return value -> {
if (value % 2 != 0) {
throw new ValidationException("Odd number invalid");
}
};
}
public SkipPolicy skipPolicy() {
return (t, skipCount) -> t instanceof ValidationException;
}
public SkipListener<Integer, Integer> skipListener() {
return new SkipListener<Integer, Integer>() {
#Override
public void onSkipInRead(Throwable t) {}
#Override
public void onSkipInWrite(Integer item, Throwable t) {}
#Override
public void onSkipInProcess(Integer item, Throwable t) {
System.out.println("Skipped " + item + " - " + t.getMessage());
}
};
}
}
Output from job is
Skipped 1 - Odd number invalid
Writing 2
Skipped 3 - Odd number invalid
Writing 4
Skipped 5 - Odd number invalid
Writing 6
Skipped 7 - Odd number invalid
Writing 8
Skipped 9 - Odd number invalid
Writing 10
Items that fail validation will be skipped so you need to add a SkipListener and declare ValidationException as skippable in your step
Im trying to test method responsible for retreiving data from file.
I want to test if exception is thrown properly.
package contentfile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;
public class ContentFileRetrieverService implements ContentFileRetriever {
#Override
public String[] getContentFile(String pathName) {
Stream<String> contentFileStream;
try {
contentFileStream = Files.lines(Paths.get(pathName));
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
return contentFileStream.toArray(String[]::new);
}
}
My tests:
package contentfile;
import org.junit.jupiter.api.Test;
import static org.junit.Assert.*;
class ContentFileRetrieverServiceTest {
private ContentFileRetrieverService contentFileRetrieverService = new ContentFileRetrieverService();
#Test
void getContentFile() {
String pathFile = "src\\test\\java\\resources\\TestText.txt";
String[] testedContent = contentFileRetrieverService.getContentFile(pathFile);
String[] expected = {"Line1 a", "Line2 b c", "Line 3"};
assertArrayEquals(expected, testedContent);
}
#Test(expected = IllegalArgumentException.class)
void getContentFileWhenFileDoesNotExist() {
String pathFile = "unknown";
String[] testedContent = contentFileRetrieverService.getContentFile(pathFile);
}
}
pom,xml
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
It wont compile as it cannot resolve method excepted what I am making wrong?
PS:
Could you tell me if I test this method properly with these two methods?
You're mixing JUnit 4 and JUnit 5.
The expected element exists in the JUnit4 #Test
JUnit 5 offers more powerful assertThrows instead
i want to run #RunWith with few classes in Junit 5 and attached my dependencies that i did plus the code , but only one class is runing = someTest , what i missed over here ?
class someTest
import org.junit.jupiter.api.Test;
import org.junit.platform.suite.api.SelectClasses;
#RunWith(JUnitPlatform.class)
#SelectClasses( { Something.class, someTest.class } )
public class someTest {
#Test
public void sadsadas() {
System.out.println("these is the runWith");
}
}
class something
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
public class Something {
Logger logger = Logger.getLogger("");
FileHandler fh;
#BeforeEach
public void wallad() {
System.out.println("hello");
}
#AfterEach
public void asddf() {
System.out.println("pizza");
}
#Tag("test")
#Test
public void some() {
try {
fh = new
FileHandler("C:\\Users\\tester\\Desktop\\MyLogFile.txt");
logger.addHandler(fh);
SimpleFormatter formatter = new SimpleFormatter();
fh.setFormatter(formatter);
} catch (Exception e) {
e.printStackTrace();
}
logger = Logger.getLogger(Logger.class.getName());
logger.setLevel(Level.ALL);
ConsoleHandler handler = new ConsoleHandler();
handler.setLevel(Level.ALL);
logger.addHandler(handler);
logger.setUseParentHandlers(false);
System.out.println("test1");
logger.log(Level.WARNING, "warning");
System.out.println("test2");
logger.log(Level.INFO, "information");
System.out.println("test3");
logger.log(Level.SEVERE, "severe");
System.out.println("test4");
logger.log(Level.FINEST, "finest");
System.out.println("test5");
logger.log(Level.FINE, "fine");
System.out.println("test6");
logger.log(Level.FINER, "finer");
System.out.println("test7");
logger.log(Level.FINEST, "finest");
}
}
these is the dependencies that attach to maven
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.4.0-M1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.4.0-M1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-console-standalone</artifactId>
<version>1.4.0-M1</version>
</dependency>
what i missed in terms of the runWIth? or that in junit5 i need to write only:
#SelectClasses( { Something.class, someTest.class } )
i am runing Eclipse Jee 2018-09
Rename Something.class to SomethingTest.class it will work.
#SelectClasses({ SomethingTest.class, someTest.class })
Trying to setup my first REST API (using Jersey 2 and Gradle) and add some documentation to it by using swagger. But when adding swagger dependencies and following this swagger documentation, "Using a custom Application subclass" approach, it throws me this exception, when executing the main method from Eclipse:
Exception in thread "main" java.lang.NoClassDefFoundError: javax/servlet/ServletConfig
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
at java.lang.Class.getDeclaredMethods(Class.java:1975)
at org.glassfish.jersey.server.model.IntrospectionModeller$2.run(IntrospectionModeller.java:253)
at java.security.AccessController.doPrivileged(Native Method)
at org.glassfish.jersey.server.model.IntrospectionModeller.getAllDeclaredMethods(IntrospectionModeller.java:247)
at org.glassfish.jersey.server.model.IntrospectionModeller.checkForNonPublicMethodIssues(IntrospectionModeller.java:172)
at org.glassfish.jersey.server.model.IntrospectionModeller.doCreateResourceBuilder(IntrospectionModeller.java:119)
at org.glassfish.jersey.server.model.IntrospectionModeller.access$000(IntrospectionModeller.java:80)
at org.glassfish.jersey.server.model.IntrospectionModeller$1.call(IntrospectionModeller.java:112)
at org.glassfish.jersey.server.model.IntrospectionModeller$1.call(IntrospectionModeller.java:109)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
at org.glassfish.jersey.internal.Errors.processWithException(Errors.java:255)
at org.glassfish.jersey.server.model.IntrospectionModeller.createResourceBuilder(IntrospectionModeller.java:109)
at org.glassfish.jersey.server.model.Resource.from(Resource.java:797)
at org.glassfish.jersey.server.ApplicationHandler.initialize(ApplicationHandler.java:465)
at org.glassfish.jersey.server.ApplicationHandler.access$500(ApplicationHandler.java:184)
at org.glassfish.jersey.server.ApplicationHandler$3.call(ApplicationHandler.java:350)
at org.glassfish.jersey.server.ApplicationHandler$3.call(ApplicationHandler.java:347)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
at org.glassfish.jersey.internal.Errors.processWithException(Errors.java:255)
at org.glassfish.jersey.server.ApplicationHandler.<init>(ApplicationHandler.java:347)
at org.glassfish.jersey.server.ApplicationHandler.<init>(ApplicationHandler.java:299)
at org.glassfish.jersey.jdkhttp.JdkHttpHandlerContainer.<init>(JdkHttpHandlerContainer.java:98)
at org.glassfish.jersey.jdkhttp.JdkHttpServerFactory.createHttpServer(JdkHttpServerFactory.java:111)
at org.glassfish.jersey.jdkhttp.JdkHttpServerFactory.createHttpServer(JdkHttpServerFactory.java:93)
at example.MyApp.main(MyApp.java:21)
Caused by: java.lang.ClassNotFoundException: javax.servlet.ServletConfig
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 29 more
My code looks like this:
package example;
import static org.glassfish.jersey.jdkhttp.JdkHttpServerFactory.createHttpServer;
import java.net.URI;
import javax.ws.rs.core.UriBuilder;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;
import com.sun.net.httpserver.HttpServer;
import io.swagger.jaxrs.listing.ApiListingResource;
import io.swagger.jaxrs.listing.SwaggerSerializers;
public class MyApp extends ResourceConfig {
public static void main(String[] args) throws Throwable {
URI baseUri = UriBuilder.fromUri("http://localhost/").port(9999).build();
HttpServer server = createHttpServer(baseUri, new MyApp());
System.out.println("SERVICE started at: " + baseUri);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
server.stop(0);
}));
}
public MyApp() {
packages("example");
register(SwaggerSerializers.class); // <-- swagger specific
register(ApiListingResource.class); // <-- swagger specific
register(JacksonFeature.class);
}
}
My gradle dependencies
dependencies {
compile 'com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:+'
compile 'org.glassfish.jersey.containers:jersey-container-jdk-http:+'
compile 'org.glassfish.jersey.media:jersey-media-moxy:+'
compile 'org.glassfish.jersey.media:jersey-media-json-jackson:+'
compile 'io.swagger:swagger-jersey2-jaxrs:1.5.9'
}
Using jdk1.8.0_77 on Windows 7
However, if I comment out the swagger dependency and the swagger specifics in the code, then the actual REST service works as expected. How can I make swagger work without using a servlet container? The REST service can work without it
dependencies {
compile 'com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:+'
compile 'org.glassfish.jersey.containers:jersey-container-jdk-http:+'
compile 'org.glassfish.jersey.media:jersey-media-moxy:+'
compile 'org.glassfish.jersey.media:jersey-media-json-jackson:+'
// compile 'io.swagger:swagger-jersey2-jaxrs:1.5.9'
}
code:
package example;
import static org.glassfish.jersey.jdkhttp.JdkHttpServerFactory.createHttpServer;
import java.net.URI;
import javax.ws.rs.core.UriBuilder;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;
import com.sun.net.httpserver.HttpServer;
import io.swagger.jaxrs.listing.ApiListingResource;
import io.swagger.jaxrs.listing.SwaggerSerializers;
public class MyApp extends ResourceConfig {
public static void main(String[] args) throws Throwable {
URI baseUri = UriBuilder.fromUri("http://localhost/").port(9999).build();
HttpServer server = createHttpServer(baseUri, new MyApp());
System.out.println("SERVICE started at: " + baseUri);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
server.stop(0);
}));
}
public MyApp() {
packages("example");
// register(SwaggerSerializers.class); // <-- swagger specific
// register(ApiListingResource.class); // <-- swagger specific
register(JacksonFeature.class);
}
}
So it looks like the issue is derived from you running in a non-servlet environment. While Jersey supports it, swagger-core... not so much. This poses some issues with specific deployment, although they are less common.
The easiest solution would, obviously, be to use a servlet-container engine. Something lightweight like Jetty would work.
I know that is a litlle bit late, but I have the same issue and came up with a solution to run swagger on a non-servlet environment.
Hope to be helpful for the next devs.
pom.xml
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-http</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-binding</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa</artifactId>
<version>2.7.1</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.196</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-jersey2-jaxrs</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
</dependencies>
...
<properties>
<jersey.version>2.28</jersey.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
Main.java
package me.nunum.whereami;
import me.nunum.whereami.facade.ApiListingResource;
import me.nunum.whereami.framework.interceptor.PrincipalInterceptor;
import org.glassfish.grizzly.http.server.CLStaticHttpHandler;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.http.server.ServerConfiguration;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import java.io.IOException;
import java.net.URI;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Main class.
*/
public class Main {
// Base URI the Grizzly HTTP server will listen on
private static final String BASE_URI = "http://0.0.0.0:8080";
private static final Logger LOGGER = Logger.getLogger("Main");
/**
* Starts Grizzly HTTP server exposing JAX-RS resources defined in this application.
*
* #return Grizzly HTTP server.
*/
public static HttpServer startServer() {
// create a resource config that scans for JAX-RS resources and providers
// in me.nunum.whereami.facade package
final ResourceConfig rc = new ResourceConfig().packages("me.nunum.whereami.facade");
rc.setApplicationName("where");
rc.register(PrincipalInterceptor.class);
rc.register(ApiListingResource.class);
rc.register(io.swagger.jaxrs.listing.SwaggerSerializers.class);
// create and start a new instance of grizzly http server
// exposing the Jersey application at BASE_URI
return GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc);
}
/**
* Main method.
*
* #param args
* #throws IOException
*/
public static void main(String[] args) throws IOException {
final HttpServer server = startServer();
ClassLoader loader = Main.class.getClassLoader();
CLStaticHttpHandler docsHandler = new CLStaticHttpHandler(loader, "swagger-ui/dist/");
docsHandler.setFileCacheEnabled(false);
ServerConfiguration cfg = server.getServerConfiguration();
cfg.addHttpHandler(docsHandler, "/docs/");
Main.LOGGER.log(Level.INFO,"Jersey app started with WADL available at "
+ "{0} \nHit enter to stop it...", BASE_URI);
System.in.read();
server.shutdown();
}
}
Refactor io.swagger.jaxrs.listing.ApiListingResource class into a new class (created in my facade package) to work on a non-servlet environment.
package me.nunum.whereami.facade;
import io.swagger.annotations.ApiOperation;
import io.swagger.config.FilterFactory;
import io.swagger.config.Scanner;
import io.swagger.config.SwaggerConfig;
import io.swagger.core.filter.SpecFilter;
import io.swagger.core.filter.SwaggerSpecFilter;
import io.swagger.jaxrs.Reader;
import io.swagger.jaxrs.config.JaxrsScanner;
import io.swagger.jaxrs.config.ReaderConfig;
import io.swagger.jaxrs.listing.SwaggerSerializers;
import io.swagger.models.Swagger;
import io.swagger.util.Yaml;
import java.util.*;
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
#Path("/api/doc")
#Singleton
public class ApiListingResource {
static boolean initialized = false;
Logger LOGGER = LoggerFactory.getLogger(io.swagger.jaxrs.listing.ApiListingResource.class);
public Swagger mSwaggerConfig;
public ApiListingResource() {
mSwaggerConfig = new Swagger();
mSwaggerConfig.setBasePath("/");
}
public ApiListingResource(Swagger swagger){
this.mSwaggerConfig = swagger;
}
protected synchronized Swagger scan(Application app) {
Swagger swagger = null;
Scanner scanner = new Scanner() {
#Override
public Set<Class<?>> classes() {
return app.getClasses();
}
#Override
public boolean getPrettyPrint() {
return false;
}
#Override
public void setPrettyPrint(boolean b) {
}
};
this.LOGGER.debug("using scanner " + scanner);
SwaggerSerializers.setPrettyPrint(scanner.getPrettyPrint());
swagger = this.mSwaggerConfig;
new HashSet();
Set classes;
if (scanner instanceof JaxrsScanner) {
classes = null;
} else {
classes = scanner.classes();
}
if (classes != null) {
Reader reader = new Reader(swagger, new ReaderConfig() {
#Override
public boolean isScanAllResources() {
return false;
}
#Override
public Collection<String> getIgnoredRoutes() {
return new ArrayList<>();
}
});
swagger = reader.read(classes);
if (scanner instanceof SwaggerConfig) {
swagger = ((SwaggerConfig)scanner).configure(swagger);
} else {
SwaggerConfig configurator = new SwaggerConfig() {
#Override
public Swagger configure(Swagger swagger) {
return swagger;
}
#Override
public String getFilterClass() {
return "";
}
};
this.LOGGER.debug("configuring swagger with " + configurator);
configurator.configure(swagger);
}
}
initialized = true;
return swagger;
}
#GET
#Produces({"application/json"})
#Path("/swagger.json")
#ApiOperation(
value = "The swagger definition in JSON",
hidden = true
)
public Response getListingJson(#Context Application app, #Context HttpHeaders headers, #Context UriInfo uriInfo) {
Swagger swagger = this.mSwaggerConfig;
if (!initialized) {
this.mSwaggerConfig = this.scan(app);
}
if (swagger != null) {
SwaggerSpecFilter filterImpl = FilterFactory.getFilter();
if (filterImpl != null) {
SpecFilter f = new SpecFilter();
swagger = f.filter(swagger, filterImpl, this.getQueryParams(uriInfo.getQueryParameters()), this.getCookies(headers), this.getHeaders(headers));
}
return Response.ok().entity(swagger).build();
} else {
return Response.status(404).build();
}
}
#GET
#Produces({"application/yaml"})
#Path("/swagger.yaml")
#ApiOperation(
value = "The swagger definition in YAML",
hidden = true
)
public Response getListingYaml(#Context Application app, #Context HttpHeaders headers, #Context UriInfo uriInfo) {
Swagger swagger = this.mSwaggerConfig;
if (!initialized) {
this.mSwaggerConfig = this.scan(app);
}
try {
if (swagger != null) {
SwaggerSpecFilter filterImpl = FilterFactory.getFilter();
this.LOGGER.debug("using filter " + filterImpl);
if (filterImpl != null) {
SpecFilter f = new SpecFilter();
swagger = f.filter(swagger, filterImpl, this.getQueryParams(uriInfo.getQueryParameters()), this.getCookies(headers), this.getHeaders(headers));
}
String yaml = Yaml.mapper().writeValueAsString(swagger);
String[] parts = yaml.split("\n");
StringBuilder b = new StringBuilder();
String[] arr$ = parts;
int len$ = parts.length;
for(int i$ = 0; i$ < len$; ++i$) {
String part = arr$[i$];
int pos = part.indexOf("!<");
int endPos = part.indexOf(">");
b.append(part);
b.append("\n");
}
return Response.ok().entity(b.toString()).type("application/yaml").build();
}
} catch (Exception var16) {
var16.printStackTrace();
}
return Response.status(404).build();
}
protected Map<String, List<String>> getQueryParams(MultivaluedMap<String, String> params) {
Map<String, List<String>> output = new HashMap();
if (params != null) {
Iterator i$ = params.keySet().iterator();
while(i$.hasNext()) {
String key = (String)i$.next();
List<String> values = (List)params.get(key);
output.put(key, values);
}
}
return output;
}
protected Map<String, String> getCookies(HttpHeaders headers) {
Map<String, String> output = new HashMap();
if (headers != null) {
Iterator i$ = headers.getCookies().keySet().iterator();
while(i$.hasNext()) {
String key = (String)i$.next();
Cookie cookie = (Cookie)headers.getCookies().get(key);
output.put(key, cookie.getValue());
}
}
return output;
}
protected Map<String, List<String>> getHeaders(HttpHeaders headers) {
Map<String, List<String>> output = new HashMap();
if (headers != null) {
Iterator i$ = headers.getRequestHeaders().keySet().iterator();
while(i$.hasNext()) {
String key = (String)i$.next();
List<String> values = (List)headers.getRequestHeaders().get(key);
output.put(key, values);
}
}
return output;
}
}
Any questions, please ask.
I was facing the same issue, I solved it by following the same swagger documentation, The only difference is that I provided my own ApiListingResource implementation
package com.example;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.swagger.annotations.ApiOperation;
import io.swagger.jaxrs.config.BeanConfig;
import io.swagger.models.Swagger;
import io.swagger.util.Yaml;
import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
#Path("/docs")
#ApplicationScoped
public class ApiListingResource {
private final Swagger swagger;
public ApiListingResource() {
BeanConfig beanConfig = new BeanConfig();
beanConfig.setTitle("MY REST API");
beanConfig.setVersion("v1");
beanConfig.setBasePath("/api");
beanConfig.setResourcePackage("com.example.resource");
beanConfig.setScan(true);
this.swagger = beanConfig.getSwagger();
}
#GET
#Produces({"application/json"})
#Path("/swagger.json")
public Response getListingJson() {
return Response.ok(this.swagger).build();
}
#GET
#Produces({"application/yaml"})
#Path("/swagger.yaml")
public Response getListingYaml() throws JsonProcessingException {
String yaml = Yaml.mapper().writeValueAsString(this.swagger);
return Response.ok(yaml).build();
}
}
Then I registered the resource along with the SwaggerSerializers provider.
I'm currently trying to develop a new module for our existing web application. I'm trying to add LDAP functionality and have problems initializing the LDAP context as the SimpleNamingContextBuilder registers a context that is not working together with the LdapTemplate.
In our spring applicationContext.xml we have several JNDI lookups, so before running a test case I have to create mock JNDI-Resources using the SimpleNamingContextBuilder in the test cases constructor.
SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
builder.bind("someJNDIname",someObject); //e.g. for some datasource
builder.activate();
In our Spring application-context-test.xml we have the following ldapConfiguration:
<bean id="ldapContextSource" class="org.springframework.ldap.core.support.LdapContextSource">
<property name="url" value="ldap://ourserver:389" />
<property name="base" value="CN=Groups,CN=ourcompany,DC=com" />
<property name="userDn" value="CN=binduser" />
<property name="password" value="password" />
</bean>
<bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
<constructor-arg ref="ldapContextSource" />
</bean>
We run the testcase with:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:application-context-test.xml"})
public class TestClass {
public TestClass(){
.. //init the SimpleNamingContextBuilder
}
#Autowired
private LdapTemplate template;
#Test
public void someTestcase(){
ldapTemplate.search("", "(objectclass=user)" ,new LdapUserMapper());
}
}
As the SimpleNamingContextBuilder is already registering a simple InitialContext I get the following error:
org.springframework.ldap.NotContextException: DirContext object is required.; nested exception is javax.naming.NotContextException: DirContext object is required.
at org.springframework.ldap.support.LdapUtils.convertLdapException(LdapUtils.java:198)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:319)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:259)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:571)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:556)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:411)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:431)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:451)
at com.somecompany.TestClass.someTestcase(TestClass.java:30)
[...]
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: javax.naming.NotContextException: DirContext object is required.
at javax.naming.directory.InitialDirContext.castToDirContext(InitialDirContext.java:106)
at javax.naming.directory.InitialDirContext.getURLOrDefaultInitDirCtx(InitialDirContext.java:112)
at javax.naming.directory.InitialDirContext.search(InitialDirContext.java:245)
at org.springframework.ldap.core.LdapTemplate$4.executeSearch(LdapTemplate.java:253)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:293)
... 35 more
The error tells me that the LDAP requires a DirContext. How can I get the SimpleNamingContextBuilder to create and use such a DirContext.
If I don't register the SimpleNamingContextBuilder then creating the LDAPTemplate will work. However I will run into other problems as other parts of the application require the JNDI lookups.
I did not manage to get the SimpleNamingContextBuilder to create an instance of DirContext, but using a custom DirContextBuilder was the solution to get around this limitation.
The mocked SimpleNamingContext is mainly there to provide bound objects via e.g.
InitialContext.doLookup(String name)
methods - to let those ones work and provide proper support for e.g. LDAP DirContext instances, just omit the check for the "activated" context - you will bootstrap your code to apply activate() anyway, so this is no problem - at least not for the given JNDI + LDAP test case.
Missing this check, the code looks for the "java.naming.factory.initial" environment key and if the environment is empty (this is the case for InitialContext.doLookup(String name)) you get the mocked SimpleNamingContext with your bound objects.
If you use the LdapTemplate the environment is not empty and the key "java.naming.factory.initial" is set to "com.sun.jndi.ldap.LdapCtxFactory" or something similar which is at least (hopefully) a DirContext.
In this case you get a working DirContext instance back from the createInitialContextFactory call and the LdapTemplate lookup is successful.
So just create a class DirContextBuilder - take the code from SimpleNamingContextBuilder - like this:
public class DirContextBuilder implements InitialContextFactoryBuilder {
...
public InitialContextFactory createInitialContextFactory(Hashtable<?,?> environment) {
if (environment != null) {
...
}
Omit the check for activated == null and you are ready to test your bound JNDI objects and have a working LdapTemplate.
I faced the same issue. But overcome it with the below trick
#BeforeClass
public static void setUp(){
OracleDataSource ods = null;
try {
ods= new OracleDataSource();
} catch (SQLException e) {
e.printStackTrace();
}
ods.setURL("jdbc:oracle:thin:#(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=....;
ods.setUser(..);
ods.setPassword(..);
SimpleNamingContextBuilder builder = null;
try {
builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder();
builder.bind("some/name", ods);
} catch (NamingException e) {
e.printStackTrace();
}
}
#Before
public void beforeTest(){
SimpleNamingContextBuilder.getCurrentContextBuilder().deactivate();
}
#Test
public void yourTest(){
.....
}
This will bind your database with some/name and also let you to connect to the ldap correctly.
I also faced the same issue. I researched causes and internal behaviour in Java and SpringLdap why it happens. I came to the following decision.
I customized ContextSource bean creation in order to solve it. This method is crutch and requires modification config that checking test mode. But it works.
Below I present simple project demostrated it. For embeded LDAP server I used Apache Directory Server.
CommonConfig.java consisted this crutch:
package com.stackoverflow.question8325740.config;
import com.stackoverflow.question8325740.JndiExplorer;
import com.stackoverflow.question8325740.LdapSettings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.LdapOperations;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.AbstractContextSource;
import org.springframework.ldap.core.support.LdapContextSource;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.NoInitialContextException;
import javax.naming.directory.DirContext;
import javax.naming.ldap.Control;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.spi.InitialContextFactory;
import javax.naming.spi.NamingManager;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
#Configuration
public class CommonConfig {
private static class CustomLdapContextSource extends AbstractContextSource {
#Override
protected DirContext getDirContextInstance(Hashtable<String, Object> environment) throws NamingException {
return new CustomLdapContext(environment, null);
}
}
private static class CustomLdapContext extends InitialLdapContext {
public CustomLdapContext() throws NamingException {
}
public CustomLdapContext(Hashtable<?, ?> environment, Control[] connCtls) throws NamingException {
super(environment, connCtls);
}
#Override
protected Context getDefaultInitCtx() throws NamingException {
String className = "com.sun.jndi.ldap.LdapCtxFactory";
InitialContextFactory factory;
try {
factory = (InitialContextFactory) Class.forName(className).newInstance();
} catch (Exception e) {
NoInitialContextException ne =
new NoInitialContextException(
"Cannot instantiate class: " + className);
ne.setRootCause(e);
throw ne;
}
return factory.getInitialContext(myProps);
}
}
private static boolean checkTestMode() {
//checking test mode using reflection in order to not collapse in real execution
try {
Class clazz = Class.forName("org.springframework.mock.jndi.SimpleNamingContextBuilder");
Object result = clazz.getMethod("getCurrentContextBuilder").invoke(null);
return NamingManager.hasInitialContextFactoryBuilder() && result != null;
} catch (Exception e) {
return false;
}
}
#Bean
#Autowired
public ContextSource ldapContextSource(LdapSettings ldapSettings) {
AbstractContextSource contextSource;
if (checkTestMode()) {
contextSource = new CustomLdapContextSource();
} else {
contextSource = new LdapContextSource();
}
contextSource.setUrl(ldapSettings.getUrl());
contextSource.setUserDn(ldapSettings.getLogin());
contextSource.setPassword(ldapSettings.getPassword());
contextSource.setPooled(true);
contextSource.setAnonymousReadOnly(false);
Map<String, Object> baseEnvironmentProperties = new HashMap<String, Object>();
baseEnvironmentProperties.put(Context.SECURITY_AUTHENTICATION, "simple");
baseEnvironmentProperties.put(Context.REFERRAL, "follow");
contextSource.setBaseEnvironmentProperties(baseEnvironmentProperties);
return contextSource;
}
#Bean
#Autowired
public LdapOperations ldapTemplate(ContextSource ldapContextSource) {
return new LdapTemplate(ldapContextSource);
}
#Bean
public JndiExplorer jndiExplorer() {
return new JndiExplorer();
}
}
MainTest.java using JNDI and LdapOperations:
package com.stackoverflow.question8325740;
import com.stackoverflow.question8325740.config.CommonConfig;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapOperations;
import org.springframework.ldap.query.LdapQueryBuilder;
import org.springframework.ldap.test.LdapTestUtils;
import org.springframework.mock.jndi.SimpleNamingContextBuilder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttributes;
import java.util.List;
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {ApacheDsEmbededConfiguration.class, CommonConfig.class})
public class MainTest {
public static final String TEST_VALUE = "testValue";
private static SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
#BeforeAll
public static void setUp() throws Exception {
builder.bind(JndiExplorer.JNDI_TEST, TEST_VALUE);
builder.activate();
LdapTestUtils.startEmbeddedServer(ApacheDsEmbededConfiguration.PORT, ApacheDsEmbededConfiguration.DEFAULT_PARTITION_SUFFIX, "test");
Thread.sleep(1000);
}
#AfterAll
public static void shutdown() throws Exception {
LdapTestUtils.shutdownEmbeddedServer();
builder.deactivate();
}
#Autowired
private JndiExplorer jndiExplorer;
#Autowired
private LdapOperations ldapOperations;
#Test
public void testLdapTemplateWithSimpleJndi() {
Assertions.assertEquals(TEST_VALUE, jndiExplorer.getValue());
String cn = "testCN";
String sn = "testSN";
Attributes attrs = new BasicAttributes();
attrs.put("objectClass", "inetOrgPerson");
attrs.put("cn", cn);
attrs.put("sn", sn);
ldapOperations.bind("cn=" + cn + "," + ApacheDsEmbededConfiguration.DEFAULT_PARTITION_SUFFIX, null, attrs);
AttributesMapper<String> mapper = new AttributesMapper<String>() {
#Override
public String mapFromAttributes(Attributes attributes) throws NamingException {
return (String) attributes.get("sn").get();
}
};
List<String> sns = ldapOperations.search(LdapQueryBuilder.query().base(ApacheDsEmbededConfiguration.DEFAULT_PARTITION_SUFFIX).attributes("*").where("sn").is(sn), mapper);
Assertions.assertEquals(1, sns.size());
String resultSn = sns.get(0);
Assertions.assertEquals(sn, resultSn);
}
}
ApacheDsEmbededConfiguration.java:
package com.stackoverflow.question8325740;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class ApacheDsEmbededConfiguration {
//default password
static final String PASSWORD = "secret";
//default admin DN
static final String PRINCIPAL = "uid=admin,ou=system";
static final String DEFAULT_PARTITION_SUFFIX = "dc=stackoverflow,dc=com";
static final int PORT = 1888;
#Bean
public LdapSettings ldapSettings() {
LdapSettings settings = new LdapSettings();
settings.setUrl("ldap://localhost:" + PORT);
settings.setLogin(PRINCIPAL);
settings.setPassword(PASSWORD);
return settings;
}
}
Pojo LdapSettings.java:
package com.stackoverflow.question8325740;
public class LdapSettings {
private String url;
private String login;
private String password;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Bean using JNDI-variable JndiExplorer.java:
package com.stackoverflow.question8325740;
import javax.annotation.Resource;
public class JndiExplorer {
public static final String JNDI_TEST = "com/anything";
#Resource(mappedName = JNDI_TEST)
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
And pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.stackoverflow</groupId>
<artifactId>question-8325740</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>5.3.2</junit.version>
<spring.version>5.1.4.RELEASE</spring.version>
<spring.ldap.version>2.3.2.RELEASE</spring.ldap.version>
<apacheDirectoryService.version>1.5.5</apacheDirectoryService.version>
<apacheDirectoryService.shared.version>0.9.15</apacheDirectoryService.shared.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
<version>${spring.ldap.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-test</artifactId>
<version>${spring.ldap.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-core</artifactId>
<version>${apacheDirectoryService.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-core-entry</artifactId>
<version>${apacheDirectoryService.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-protocol-shared</artifactId>
<version>${apacheDirectoryService.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-protocol-ldap</artifactId>
<version>${apacheDirectoryService.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-server-jndi</artifactId>
<version>${apacheDirectoryService.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.directory.shared</groupId>
<artifactId>shared-ldap</artifactId>
<version>${apacheDirectoryService.shared.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
</plugin>
</plugins>
</build>
</project>