I'm using the Jaxb2Marshaller from org.springframework.oxm.jaxb.Jaxb2Marshaller in my Spring Batch application to marshall XML with annotated classes. The implementation of the Marshaller is:
#Bean
public Jaxb2Marshaller productMarshaller() {
Map<String, Object> props = new HashMap<String, Object>();
props.put("com.sun.xml.bind.marshaller.CharacterEscapeHandler", new XmlCharacterEscapeHandler());
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(new Class[] {Product.class, TechSpecs.class});
marshaller.setMarshallerProperties(props);
return marshaller;
}
The Marshaller is used inside a StaxEventItemWriter that is implemented as following:
#Bean(name = "writer")
#StepScope
public StaxEventItemWriter<Product> writer (
#Value("#{jobParameters['path']}") String path,
#Value("#{stepExecutionContext['currentFile']}") String fileName
) {
Map<String, String> rootElementAttributes = new HashMap<String, String>();
rootElementAttributes.put("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
FileSystemResource file = new FileSystemResource(path + fileName);
return new StaxEventItemWriterBuilder<Product>()
.name("writer")
.version("1.0")
.encoding("UTF-8")
.standalone(false)
.rootTagName("Products")
.rootElementAttributes(rootElementAttributes)
.headerCallback(headerCallback(null, null))
.footerCallback(footerCallback())
.marshaller(productMarshaller())
.resource(file)
.build();
}
Now the problem is that when I run the code, I get an IndexOutOfBoundsException. I found out that the exception is thrown because my Product object has a String attribute that may contain a &. The & is not allowed in XML and has to be escaped.
Why is the Jaxb2Marshaller not auto escaping the & character? As far as I understand the Marshaller should take care of escaping characters.
I tried to escape the character my self in the item processor with the StringEscapeUtils, e.g. product.setFullName(StringEscapeUtils.escapeXml10(dbExport.getFullName()));, but this didn't help. Also the String will be changed from & to &, which also contains a &.
I also tried to use my own implementation of a CharacterEscapeHandler, but the marshaller.setMarshallerProperties() does not have any visible effect on the Marshaller. Do I have to set the properties for the Marshaller differently?
public class XmlCharacterEscapeHandler implements CharacterEscapeHandler {
#Override
public void escape(char[] ch, int start, int length, boolean isAttVal, Writer out) throws IOException {
StringWriter buffer = new StringWriter();
for(int i = start; i < start + length; i++) {
buffer.write(ch[i]);
}
String escapedString = StringEscapeUtils.escapeXml10(buffer.toString());
out.write(escapedString);
}
}
EDIT
Unfortunately I could not resolve my issue until now. Therefore, I switched from Jaxb2Marshaller to XStreamMarshaller. Here I get a similar issue. As far as I can tell the underlying XStream should use a PrettyPrintWriter that will auto convert & to & as described here: https://stackoverflow.com/a/48141964/4191735 This is not happening. For me there is always an problem with &. Why does the escaping not work? Also escaping the String itself and force converting it to UTF-8 does not help.
Minimal Complete Example
Main:
package com.mwe;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
#SpringBootApplication
#ComponentScan("com.mwe")
public class Main {
public static void main(String [] args) {
System.exit(SpringApplication.exit(SpringApplication.run(Main.class, args)));
}
}
BatchConfig:
package com.mwe;
import org.springframework.batch.core.Job;
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.configuration.annotation.StepScope;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.batch.item.xml.StaxEventItemWriter;
import org.springframework.batch.item.xml.builder.StaxEventItemWriterBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;
import org.springframework.oxm.xstream.XStreamMarshaller;
#Configuration
#EnableBatchProcessing
public class BatchConfig {
#Autowired
public JobBuilderFactory jobBuilderFactory;
#Autowired
public StepBuilderFactory stepBuilderFactory;
#Bean
#StepScope
public FlatFileItemReader<Product> reader() {
FlatFileItemReader<Product> reader = new FlatFileItemReader<Product>();
reader.setResource(new FileSystemResource("test.csv"));
DefaultLineMapper<Product> lineMapper = new DefaultLineMapper<>();
lineMapper.setFieldSetMapper(new CustomFieldMapper());
DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
tokenizer.setDelimiter("|");
tokenizer.setNames(new String[] {"ID", "NAME"});
lineMapper.setLineTokenizer(tokenizer);
reader.setLineMapper(lineMapper);
reader.setLinesToSkip(1);
return reader;
}
#Bean
public ItemProcessor<Product, Xml> processor() {
return new Processor();
}
#Bean
#StepScope
public StaxEventItemWriter<Xml> writer () {
return new StaxEventItemWriterBuilder<Xml>()
.name("writer")
.version("1.0")
.encoding("UTF-8")
.standalone(false)
.rootTagName("products")
.marshaller(getMarshaller())
.resource(new FileSystemResource("test.xml"))
.build();
}
#Bean
public Job job() {
return this.jobBuilderFactory.get("job")
.start(step1())
.build();
}
#Bean
public Step step1() {
return (stepBuilderFactory.get("step1")
.<Product, Xml>chunk(2)
.reader(reader())
.processor(processor())
.writer(writer())
.build());
}
#Bean
public XStreamMarshaller getMarshaller() {
XStreamMarshaller marshaller = new XStreamMarshaller();
marshaller.setEncoding("UTF-8");
return marshaller;
}
}
CustomFieldMapper
package com.mwe;
import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.file.transform.FieldSet;
public class CustomFieldMapper implements FieldSetMapper<Product> {
public Product mapFieldSet(FieldSet fs) {
Product product = new Product();
product.setId(fs.readString("ID"));
product.setName(fs.readString("NAME"));
return product;
}
}
ItemProcessor:
package com.mwe;
import org.springframework.batch.item.ItemProcessor;
public class Processor implements ItemProcessor<Product, Xml> {
#Override
public Xml process(final Product product) {
Xml xml = new Xml();
xml.setId(Integer.parseInt(product.getId()));
xml.setName(product.getName());
return xml;
}
}
Product:
package com.mwe;
public class Product {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Xml:
package com.mwe;
public class Xml {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Application Properties:
# Spring config
spring.main.allow-bean-definition-overriding=true
spring.main.banner-mode=off
spring.batch.initialize-schema=never
# Logging data source
spring.datasource.logging.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.logging.maximum-pool-size=10
spring.datasource.logging.hikar.minimum-idle=1
spring.datasource.logging.hikari.data-source-properties.useUnicode=true
spring.datasource.logging.hikari.data-source-properties.characterEncoding=UTF-8
spring.datasource.logging.hibernate.dialect=org.hibernate.dialect.MariaDBDialect
spring.datasource.logging.hibernate.ddl-auto=none
spring.datasource.url=jdbc:mariadb://localhost:3306/logging?UseUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
Pom:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.mwe</groupId>
<artifactId>mwe</artifactId>
<version>1</version>
<name>mwe</name>
<description>Minimal working example</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.9</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.15</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
test.csv:
"ID"|"NAME"
1|"Product 1"
2|"Product 1 & Addition"
You are hitting this issue: https://github.com/spring-projects/spring-batch/issues/3745.
This issue will be fixed in v4.3.2/v4.2.6 which are planned to be released on March 18, 2021. Please check the milestone page on GitHub in case the release date changes.
Related
I follow tutorial at https://www.youtube.com/watch?v=AjpSdbdvIHw
My code
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.anil.hz</groupId>
<artifactId>H</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
<version>3.11</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
File Server.java
package com.anil.server;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
public class Server {
public static void main(String[] args) {
HazelcastInstance hzInstance = Hazelcast.newHazelcastInstance();
IMap<Long, String> map = hzInstance.getMap("testMap");
map.put(1L, "one");
map.put(2L, "two");
}
}
File Student.java
package com.anil.server;
public class Student {
private Long id;
private String sName;
private String city;
public Student(Long id, String sName, String city) {
this.id = id;
this.sName = sName;
this.city = city;
}
}
File Server.java
package com.anil.server;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
public class Server {
public static void main(String[] args) {
HazelcastInstance hzInstance = Hazelcast.newHazelcastInstance();
IMap<Long, String> map = hzInstance.getMap("testMap");
map.put(1L, "one");
map.put(2L, "two");
}
}
File HZConfig.java
package com.anil.server;
import com.hazelcast.config.Config;
import com.hazelcast.config.JoinConfig;
import com.hazelcast.config.NetworkConfig;
public class HZConfig {
public static Config getConfig(){
Config config = new Config();
NetworkConfig network = config.getNetworkConfig();
network.setPort(5710).setPortCount(20);
network.setPortAutoIncrement(true);
JoinConfig join = network.getJoin();
join.getMulticastConfig().setEnabled(false);
join.getTcpIpConfig().addMember("localhost").setEnabled(true);
config.getManagementCenterConfig().setEnabled(true);
config.getManagementCenterConfig().setUrl("http://localhost:8081/mancenter");
return config;
}
}
File Client.java
package com.anil.server;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
public class Client {
public static void main(String[] args) {
HazelcastInstance hzClient = null;
try {
hzClient = HazelcastClient.newHazelcastClient();
IMap<Long, String> testMap = hzClient.getMap("testMap");
System.out.println(testMap.get(1L));
} finally {
hzClient.shutdown();
}
}
}
I catch error at
What are new API for old thing HazelcastInstance hzClient = HazelcastClient.newHazelcastClient(); ?
You are using the following dependency
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
<version>3.11</version>
</dependency>
This does not contain the client. The tutorial changes that dependency to hazelcast-all at 3:45, which has the client included. You can also include the client separately using hazelcast-client artifactId.
However 3.11 is a really old version, not supported anymore, you should use the latest release - 5.1 currently. In this release, there is no hazelcast-all jar anymore and the client is included in the main jar. Note that there are some breaking changes between 3.x and 4.x/5.x, but it's mostly just package names.
Base on answer from František Hartman, I add more detail
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.anil.hz</groupId>
<artifactId>H</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
<version>5.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
File Client.java
package com.anil.server;
import com.hazelcast.client.HazelcastClient;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.map.IMap;
public class Client {
public static void main(String[] args) {
HazelcastInstance hzClient = null;
try {
hzClient = HazelcastClient.newHazelcastClient();
IMap<Long, String> testMap = hzClient.getMap("testMap");
System.out.println(testMap.get(1L));
} finally {
hzClient.shutdown();
}
}
}
File Server.java
package com.anil.server;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.map.IMap;
public class Server {
public static void main(String[] args) {
HazelcastInstance hzInstance = Hazelcast.newHazelcastInstance();
IMap<Long, String> map = hzInstance.getMap("testMap");
map.put(1L, "one");
map.put(2L, "two");
}
}
File HZConfig.java
package com.anil.server;
import com.hazelcast.config.Config;
import com.hazelcast.config.JoinConfig;
import com.hazelcast.config.NetworkConfig;
public class HZConfig {
public static Config getConfig(){
Config config = new Config();
NetworkConfig network = config.getNetworkConfig();
network.setPort(5710).setPortCount(20);
network.setPortAutoIncrement(true);
JoinConfig join = network.getJoin();
join.getMulticastConfig().setEnabled(false);
join.getTcpIpConfig().addMember("localhost").setEnabled(true);
config.getManagementCenterConfig().setConsoleEnabled(true);
config.getManagementCenterConfig().setDataAccessEnabled(true);
config.getManagementCenterConfig().setScriptingEnabled(true);
// config.getManagementCenterConfig().setUrl("http://localhost:8081/mancenter"); // ?
return config;
}
}
I have a Springboot project organized in different modules.
This is the main 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>org.cmw</groupId>
<artifactId>bo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>bo</name>
<url>https://www.test.com</url>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<java.version>11</java.version>
<mapstruct.version>1.4.2.Final</mapstruct.version>
<org.lombok.version>1.18.18</org.lombok.version>
</properties>
<modules>
<module>bo_libs</module>
<module>bo_basis_lib</module>
<module>bo_basis</module>
<module>bo_api_lib</module>
<module>bo_api</module>
<module>bo_ui</module>
</modules>
<dependencies>
...
</dependencies>
</project>
I'm trying to have each module connect to a different database.
For the bo_basis module I have created this configuration class
package com.cmw.bo.basis.config;
import com.cmw.bo.basis.repositories.SampleDataPopulator;
import com.cmw.bo.common.config.DataAccessConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.hibernate.boot.SchemaAutoTooling;
import org.hibernate.dialect.MySQL8Dialect;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
#Configuration
#Profile("development")
#EnableJpaAuditing
#EnableJpaRepositories(basePackages = {
"com.cmw.bo.basis.repositories"
})
#EnableTransactionManagement
#PropertySource("classpath:application.properties")
public class DevelopmentDataAccessConfig extends DataAccessConfig {
#Value(value = "${jdbc.url}")
private String jdbcUrl;
#Bean
public DataSource dataSource() {
final HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(String.format("%s/bo_basis", jdbcUrl));
dataSource.setUsername("root");
dataSource.setPassword("root");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setMaximumPoolSize(10);
dataSource.addDataSourceProperty("cachePrepStmts", true);
dataSource.addDataSourceProperty("prepStmtCacheSize", 250);
dataSource.addDataSourceProperty("prepStmtCacheSqlLimit", 2048);
dataSource.addDataSourceProperty("useServerPrepStmts", true);
dataSource.addDataSourceProperty("useLocalSessionState", true);
dataSource.addDataSourceProperty("useLocalTransactionState", true);
dataSource.addDataSourceProperty("rewriteBatchedStatements", true);
dataSource.addDataSourceProperty("cacheResultSetMetadata", true);
dataSource.addDataSourceProperty("cacheServerConfiguration", true);
dataSource.addDataSourceProperty("elideSetAutoCommits", true);
dataSource.addDataSourceProperty("maintainTimeStats", false);
dataSource.addDataSourceProperty("useSSL", false);
dataSource.addDataSourceProperty("characterEncoding", "utf8");
dataSource.addDataSourceProperty("useUnicode", "true");
dataSource.addDataSourceProperty("serverTimezone", "UTC");
return dataSource;
}
#Bean
public SampleDataPopulator sampleDataPopulator() {
return new SampleDataPopulator();
}
#Override
protected boolean getShowSql() {
return false;
}
#Override
protected SchemaAutoTooling getSchemaAutoTooling() {
return SchemaAutoTooling.VALIDATE;
}
#Override
protected String[] getPackagesToScan() {
return new String[]{
"com.cmw.bo.basis.entities",
"com.cmw.bo.common.models.entities"
};
}
#Override
protected String getDatabasePlatform() {
return MySQL8Dialect.class.getCanonicalName();
}
#Override
protected String getName() {
return "basisJpaManager";
}
}
The DataAccessConfig class:
#Configuration
public abstract class DataAccessConfig {
#Bean
#DependsOn({"flyway"})
public EntityManagerFactory entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(false);
vendorAdapter.setShowSql(getShowSql());
vendorAdapter.setDatabasePlatform(getDatabasePlatform());
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setValidationMode(ValidationMode.CALLBACK);
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan(getPackagesToScan());
factory.setDataSource(dataSource());
Map<String, Object> properties = new HashMap<>();
properties.put(AvailableSettings.HBM2DDL_AUTO, getSchemaAutoTooling().name().toLowerCase());
// To avoid: Entity manager factory name (default) is already registered.
// If entity manager will be clustered or passivated, specify a unique value for property 'hibernate.ejb.entitymanager_factory_name'
properties.put("hibernate.ejb.entitymanager_factory_name", getName());
factory.setJpaPropertyMap(properties);
factory.afterPropertiesSet();
return factory.getObject();
}
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory());
return txManager;
}
#Bean
#Description("Database migration")
public Flyway flyway() {
Flyway flyway = Flyway.configure()
.dataSource(dataSource())
.validateOnMigrate(true)
.baselineOnMigrate(true)
.outOfOrder(false).load();
flyway.migrate();
return flyway;
}
protected abstract boolean getShowSql();
protected abstract SchemaAutoTooling getSchemaAutoTooling();
protected abstract String[] getPackagesToScan();
protected abstract String getDatabasePlatform();
protected abstract String getName();
public abstract DataSource dataSource();
}
For the bo_ui module the configuration class is the same (apart from the jdbc connection url of course)
The problem is that (obviously) if I keep the configuration classes the same name I get a ConflictingBeanDefinitionException. If I rename the classes differently, one of the two connections is ignored.
How would you solve this problem that was not taken into consideration in the design phase?
Thank you
You can apply as a way to dynamically determine the actual DataSource based on the current context.
I think is similar to what you ask: Spring's AbstractRoutingDatasource
good coding! ¯_(ツ)_/¯
My project was working fine earlier, but when I open this in STS and then opened it in IntelliJ, it started giving me this error. I was searching for a solution but didn't got any. Anyone, please help me.
Also, I have already installed the Lombok plugin in my IntelliJ.
Error picture
My 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com</groupId>
<artifactId>training-management-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>training-management-server</name>
<description>Backend for Training management</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.validation/validation-api -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>7.0.2.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.0.206</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.18.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.project-lombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
UserController
package com.trainingmanagementserver.api;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.trainingmanagementserver.entity.*;
import com.trainingmanagementserver.exception.ApiRequestException;
import com.trainingmanagementserver.message.ResponseFile;
import com.trainingmanagementserver.message.ResponseMessage;
import com.trainingmanagementserver.repository.TraineesTrainerRepository;
import com.trainingmanagementserver.repository.UserCredentialsRepository;
import com.trainingmanagementserver.service.AssignmentDetailsService;
import com.trainingmanagementserver.service.FileDBService;
import com.trainingmanagementserver.service.UserCredentialsService;
import com.trainingmanagementserver.service.UserDetailService;
import com.trainingmanagementserver.utility.Utility;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.net.URI;
import java.util.*;
import java.util.stream.Collectors;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
import static org.springframework.http.HttpStatus.FORBIDDEN;
#RestController
#RequestMapping("/api")
#RequiredArgsConstructor
#Slf4j
//#Validated
#CrossOrigin("http://localhost:8080")
public class UserController {
private final UserCredentialsService userCredentialsService;
private final UserDetailService userDetailService;
private final UserCredentialsRepository userCredentialsRepository;
private final TraineesTrainerRepository traineesTrainerRepository;
private final AssignmentDetailsService assignmentDetailsService;
private final FileDBService fileDBService;
#PostMapping("/auth/signup")
public ResponseEntity<UserCredentialsEntity> saveUser(#RequestBody UserCredentialsEntity userCredentialsEntity) throws ApiRequestException{
URI uri = URI.create(ServletUriComponentsBuilder.fromCurrentContextPath().path("/api/auth/signup").toUriString());
var username = userCredentialsRepository.findByUsername(userCredentialsEntity.getUsername());
var email = userCredentialsRepository.findByEmail(userCredentialsEntity.getEmail());
if(username == null && email == null) {
return ResponseEntity.created(uri).body(userCredentialsService.userCredentialsSave(userCredentialsEntity));
} else if(username != null && email != null) {
throw new ApiRequestException("Username and Email already exist");
}
else if(username != null){
throw new ApiRequestException("Username already exist");
}
throw new ApiRequestException("Email already exist");
}
#PostMapping("/trainee/addBulk")
public ResponseEntity<List<UserCredentialsEntity>> addBulkTrainees(#RequestBody List<UserCredentialsEntity> bulkUserCredentialsEntity, HttpServletRequest request) throws ApiRequestException{
URI uri = URI.create(ServletUriComponentsBuilder.fromCurrentContextPath().path("/api/role").toUriString());
String username = (new Utility()).getUsernameFromToken(request);
int trainerId = userCredentialsRepository.findByUsername(username).getId();
bulkUserCredentialsEntity.forEach(trainee -> {
var checkUsername = userCredentialsRepository.findByUsername(trainee.getUsername());
var checkEmail = userCredentialsRepository.findByEmail(trainee.getEmail());
if(checkUsername != null || checkEmail != null) {
throw new ApiRequestException("Invalid Entry");
}
});
var savedTrainees = userCredentialsService.addBulkTrainees(bulkUserCredentialsEntity);
savedTrainees.forEach(trainees -> {
int traineeId = trainees.getId();
TraineesTrainer traineesTrainer = new TraineesTrainer();
traineesTrainer.setTraineeId(traineeId);
traineesTrainer.setTrainerId(trainerId);
traineesTrainerRepository.save(traineesTrainer);
userCredentialsService.addRoleToUser("ROLE_TRAINEE", trainees.getUsername());
});
return ResponseEntity.created(uri).body(savedTrainees);
}
#GetMapping("/trainee/all")
public ResponseEntity<List<UserMerged>> getAllTrainees(HttpServletRequest request) {
String username = (new Utility().getUsernameFromToken(request));
if(username.isEmpty()) throw new ApiRequestException("Username not present");
var trainerId = userCredentialsRepository.findByUsername(username).getId();
return ResponseEntity.ok().body(userCredentialsService.showTraineesEnrolled(trainerId));
}
#PostMapping("/role")
public ResponseEntity<Role> saveRole(#RequestBody Role role) {
URI uri = URI.create(ServletUriComponentsBuilder.fromCurrentContextPath().path("/api/role").toUriString());
return ResponseEntity.created(uri).body(userCredentialsService.roleSave(role));
}
#GetMapping("/register/check")
public ResponseEntity<?> checkRegister(HttpServletRequest request) throws ApiRequestException{
try {
String username = (new Utility()).getUsernameFromToken(request);
int userId = userCredentialsRepository.findByUsername(username).getId();
Map<String, Boolean> response = new HashMap<>();
if (userDetailService.fetchUserDetails(userId).isPresent()) {
response.put("isPresent", true);
} else {
response.put("isPresent", false);
}
return ResponseEntity.ok().body(response);
} catch (ApiRequestException exception) {
throw new ApiRequestException("Token is mismatch");
}
}
#PostMapping("/role/addToUser")
public ResponseEntity<?> addRoleToUser(#RequestBody RoleToUserForm roleForm) throws ApiRequestException {
try {
userCredentialsService.addRoleToUser(roleForm.getRoleName(), roleForm.getUsername());
return ResponseEntity.ok().build();
} catch (Exception e) {
throw new ApiRequestException("Not able to add role to the user");
}
}
#PostMapping("/me")
public ResponseEntity<?> saveUser(#Valid #RequestBody UserDetailsEntity userDetailsEntity, HttpServletRequest request) {
// System.out.println("Printing result "+result);
// if(result.hasErrors()) {
// Map<String, BindingResult> errors = new HashMap<>();
// errors.put("error", result);
// return ResponseEntity.badRequest().body(errors);
// }
URI uri = URI.create(ServletUriComponentsBuilder.fromCurrentContextPath().path("/api/user/detail").toUriString());
String username = (new Utility()).getUsernameFromToken(request);
int userId = userCredentialsRepository.findByUsername(username).getId();
userDetailsEntity.setId(userId);
return ResponseEntity.created(uri).body(userDetailService.save(userDetailsEntity));
}
#GetMapping("/me")
public ResponseEntity<UserMerged> fetchUserDetails(HttpServletRequest request) throws ApiRequestException {
try {
String username = (new Utility()).getUsernameFromToken(request);
int userId = userCredentialsRepository.findByUsername(username).getId();
if (userDetailService.fetchUserDetails(userId).isPresent()) {
UserDetailsEntity userDetailsEntity = userDetailService.fetchUserDetails(userId).get();
UserCredentialsEntity userCredentialsEntity = userCredentialsRepository.findByUsername(username);
UserMerged userMerged = new UserMerged();
userMerged.setUser_detail(userDetailsEntity);
userMerged.setUser_credential(userCredentialsEntity);
return ResponseEntity.ok().body(userMerged);
}
} catch (ApiRequestException exception) {
throw new ApiRequestException("Token mismatch");
}
return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE);
}
#GetMapping("/token/refresh")
public void refreshToken(HttpServletRequest request, HttpServletResponse response) throws IOException {
String authorizationHeader = request.getHeader(AUTHORIZATION);
log.info(authorizationHeader);
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
try {
String refresh_token = authorizationHeader.substring("Bearer ".length());
log.info(refresh_token);
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes());
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT decodedJWT = verifier.verify(refresh_token);
String username = decodedJWT.getSubject();
UserCredentialsEntity userCredentialsEntity = userCredentialsService.getUser(username);
String access_token = JWT.create()
.withSubject(userCredentialsEntity.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + 10 * 60 * 1000))
.withIssuer(request.getRequestURL().toString())
.withClaim("roles", userCredentialsEntity.getRoles().stream().map(Role::getName).collect(Collectors.toList()))
.sign(algorithm);
Map<String, String> tokens = new HashMap<>();
tokens.put("access_token", access_token);
tokens.put("refresh_token", refresh_token);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getOutputStream(), tokens);
} catch (Exception exception) {
response.setHeader("error", exception.getMessage());
response.setStatus(FORBIDDEN.value());
Map<String, String> error = new HashMap<>();
error.put("error", exception.getMessage());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getOutputStream(), error);
}
} else {
throw new RuntimeException("Refresh token required!");
}
}
#GetMapping("/user/all")
public List<UserCredentialsEntity> fetchUsers() {
return userCredentialsService.fetchUsersCredentials();
}
#DeleteMapping("/user/{id}")
public ResponseEntity<String> deleteUser(#PathVariable("id") int userId) {
userCredentialsService.deleteUser(userId);
return ResponseEntity.ok("User is deleted Successfully!!");
}
#PostMapping("/assignment/upload")
public ResponseEntity<ResponseMessage> uploadFile(#RequestParam(value = "file", required = false) MultipartFile file,#RequestParam("title") String title, #RequestParam("description") String description, #RequestParam("total_credit") int total_credit, #RequestParam("due_date") String due_date, HttpServletRequest request) {
String senderUsername = (new Utility()).getUsernameFromToken(request);
int trainerId = userCredentialsRepository.findByUsername(senderUsername).getId();
var savedAssignment = assignmentDetailsService.save(new AssignmentDetail(trainerId, title, description, total_credit, due_date, true));
String message = "Assignment Uploaded Successfully";
if(file != null) {
try {
fileDBService.store(file, savedAssignment.getId());
} catch(IOException exception) {
message = "Could not upload the file " + file.getOriginalFilename();
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(new ResponseMessage(message));
}
}
return ResponseEntity.ok(new ResponseMessage(message));
}
#GetMapping("/files/{id}")
public ResponseEntity<ResponseFile> getFile(#PathVariable("id") int id) {
var file = fileDBService.getFileByFileId(id);
String fileDownloadUri = ServletUriComponentsBuilder
.fromCurrentContextPath()
.path("/files/")
.path(Integer.toString(file.getId()))
.toUriString();
ResponseFile responseFile = new ResponseFile(file.getName(),fileDownloadUri, file.getType(), file.getData().length);
return ResponseEntity.ok().body(responseFile);
}
}
#Data
class RoleToUserForm {
private String username;
private String roleName;
}
Thank You.
For IntelliJ Idea, when using Lombok you have to enable annotation processing.
Preferences (Ctrl + Alt + S)
Build, Execution, Deployment
Compiler
Annotation Processors
Enable annotation processing
If the problem still persist:
Add version 1.18.16.
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
<scope>provided</scope>
</dependency>
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>