diff --git a/ci/smoke-tests.yml b/ci/smoke-tests.yml index 0c51777f..32d1d6ee 100644 --- a/ci/smoke-tests.yml +++ b/ci/smoke-tests.yml @@ -39,6 +39,7 @@ smoke_tests: - jdbc-mariadb - jdbc-mysql - jdbc-postgresql + - ldap-odm - liquibase - logging-log4j2 - logging-logback diff --git a/ldap-odm/build.gradle b/ldap-odm/build.gradle new file mode 100644 index 00000000..bca9f15e --- /dev/null +++ b/ldap-odm/build.gradle @@ -0,0 +1,27 @@ +plugins { + id 'java' + id 'org.springframework.boot' + id 'org.springframework.aot.smoke-test' + id 'org.graalvm.buildtools.native' +} + +apply plugin: "org.springframework.boot.aot" + +dependencies { + implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)) + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.ldap:spring-ldap-core") + implementation("com.unboundid:unboundid-ldapsdk") + + testImplementation("org.springframework.boot:spring-boot-starter-test") + + aotTestImplementation(project(":aot-smoke-test-support")) +} + +aotSmokeTest { + webApplication = true +} + +test { + useJUnitPlatform() +} diff --git a/ldap-odm/src/aotTest/java/example/ldap/odm/LdapOdmApplicationTests.java b/ldap-odm/src/aotTest/java/example/ldap/odm/LdapOdmApplicationTests.java new file mode 100644 index 00000000..9f9b00ed --- /dev/null +++ b/ldap-odm/src/aotTest/java/example/ldap/odm/LdapOdmApplicationTests.java @@ -0,0 +1,71 @@ +package example.ldap.odm; + +import org.junit.jupiter.api.Test; + +import org.springframework.aot.smoketest.support.junit.AotSmokeTest; +import org.springframework.test.web.reactive.server.WebTestClient; + +@AotSmokeTest +public class LdapOdmApplicationTests { + + @Test + void shouldFindAllPeople(WebTestClient client) { + client.get().uri("/person").exchange().expectBody().json(""" + [ + { + "company": "company1", + "country": "Sweden", + "description": "Sweden, Company1, Some Person", + "fullName": "Some Person", + "lastName": "Person", + "phone": "+46 555-123456" + }, + { + "company": "company1", + "country": "Sweden", + "description": "Sweden, Company1, Some Person2", + "fullName": "Some Person2", + "lastName": "Person2", + "phone": "+46 555-654321" + } + ] + """); + } + + @Test + void shouldFindPerson(WebTestClient client) { + // @formatter:off + client.get().uri((builder) -> builder.path("/person") + .queryParam("country", "Sweden") + .queryParam("company", "company1") + .queryParam("fullName", "Some Person2").build() + ).exchange().expectBody().json(""" + { + "company": "company1", + "country": "Sweden", + "description": "Sweden, Company1, Some Person2", + "fullName": "Some Person2", + "lastName": "Person2", + "phone": "+46 555-654321" + } + """); + // @formatter:on + } + + @Test + void shouldAddNewPerson(WebTestClient client) { + client.post().uri("/person").exchange().expectStatus().isOk(); + } + + @Test + void shouldDeletePerson(WebTestClient client) { + // @formatter:off + client.delete().uri((builder) -> builder.path("/person") + .queryParam("country", "Sweden") + .queryParam("company", "company1") + .queryParam("fullName", "Some Person2").build() + ).exchange().expectStatus().isOk(); + // @formatter:on + } + +} diff --git a/ldap-odm/src/main/java/example/ldap/odm/LdapOdmApplication.java b/ldap-odm/src/main/java/example/ldap/odm/LdapOdmApplication.java new file mode 100644 index 00000000..986018bc --- /dev/null +++ b/ldap-odm/src/main/java/example/ldap/odm/LdapOdmApplication.java @@ -0,0 +1,29 @@ +package example.ldap.odm; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ImportRuntimeHints; + +@SpringBootApplication +@ImportRuntimeHints(LdapOdmApplication.Hints.class) +public class LdapOdmApplication { + + public static void main(String[] args) throws InterruptedException { + SpringApplication.run(LdapOdmApplication.class, args); + Thread.currentThread().join(); // To be able to measure memory consumption + } + + static class Hints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + // TODO Should this be in Spring Boot? + // https://github.com/spring-projects/spring-boot/issues/32084 + hints.resources().registerPattern(".*.ldif"); + } + + } + +} diff --git a/ldap-odm/src/main/java/example/ldap/odm/OdmPersonDaoImpl.java b/ldap-odm/src/main/java/example/ldap/odm/OdmPersonDaoImpl.java new file mode 100644 index 00000000..ffad78df --- /dev/null +++ b/ldap-odm/src/main/java/example/ldap/odm/OdmPersonDaoImpl.java @@ -0,0 +1,46 @@ +package example.ldap.odm; + +import java.util.List; + +import javax.naming.ldap.LdapName; + +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.support.LdapNameBuilder; +import org.springframework.stereotype.Repository; + +@Repository +public class OdmPersonDaoImpl implements PersonDao { + + private final LdapTemplate ldapTemplate; + + public OdmPersonDaoImpl(LdapTemplate ldapTemplate) { + this.ldapTemplate = ldapTemplate; + } + + @Override + public void create(Person person) { + this.ldapTemplate.create(person); + } + + @Override + public void delete(Person person) { + this.ldapTemplate.delete(person); + } + + @Override + public List findAll() { + return this.ldapTemplate.findAll(Person.class); + } + + @Override + public Person findByPrimaryKey(String country, String company, String fullName) { + LdapName dn = buildDn(country, company, fullName); + return this.ldapTemplate.findByDn(dn, Person.class); + } + + private LdapName buildDn(String country, String company, String fullname) { + return LdapNameBuilder.newInstance().add("dc", "io").add("dc", "spring").add("c", country).add("ou", company) + .add("cn", fullname).build(); + } + +} diff --git a/ldap-odm/src/main/java/example/ldap/odm/Person.java b/ldap-odm/src/main/java/example/ldap/odm/Person.java new file mode 100644 index 00000000..13f85432 --- /dev/null +++ b/ldap-odm/src/main/java/example/ldap/odm/Person.java @@ -0,0 +1,125 @@ +package example.ldap.odm; + +import java.util.Objects; + +import javax.naming.Name; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import org.springframework.ldap.odm.annotations.Attribute; +import org.springframework.ldap.odm.annotations.DnAttribute; +import org.springframework.ldap.odm.annotations.Entry; +import org.springframework.ldap.odm.annotations.Id; +import org.springframework.ldap.odm.annotations.Transient; + +@Entry(objectClasses = { "inetOrgPerson", "organizationalPerson", "person", "top" }) +public final class Person { + + @Id + @JsonIgnore + private Name dn; + + @Attribute(name = "cn") + @DnAttribute(value = "cn") + private String fullName; + + @Attribute(name = "sn") + private String lastName; + + @Attribute(name = "description") + private String description; + + @Transient + @DnAttribute(value = "c") + private String country; + + @Transient + @DnAttribute(value = "ou") + private String company; + + @Attribute(name = "telephoneNumber") + private String phone; + + public Name getDn() { + return this.dn; + } + + public void setDn(Name dn) { + this.dn = dn; + } + + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getFullName() { + return this.fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public String getLastName() { + return this.lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getCompany() { + return this.company; + } + + public void setCompany(String company) { + this.company = company; + } + + public String getCountry() { + return this.country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getPhone() { + return this.phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Person person = (Person) o; + return Objects.equals(this.dn, person.dn) && Objects.equals(this.fullName, person.fullName) + && Objects.equals(this.lastName, person.lastName) + && Objects.equals(this.description, person.description) && Objects.equals(this.country, person.country) + && Objects.equals(this.company, person.company) && Objects.equals(this.phone, person.phone); + } + + @Override + public int hashCode() { + return Objects.hash(this.dn, this.fullName, this.lastName, this.description, this.country, this.company, + this.phone); + } + + @Override + public String toString() { + return "Person{" + "dn=" + this.dn + ", fullName='" + this.fullName + '\'' + ", lastName='" + this.lastName + + '\'' + ", description='" + this.description + '\'' + ", country='" + this.country + '\'' + + ", company='" + this.company + '\'' + ", phone='" + this.phone + '\'' + '}'; + } + +} diff --git a/ldap-odm/src/main/java/example/ldap/odm/PersonController.java b/ldap-odm/src/main/java/example/ldap/odm/PersonController.java new file mode 100644 index 00000000..fb1c4ebe --- /dev/null +++ b/ldap-odm/src/main/java/example/ldap/odm/PersonController.java @@ -0,0 +1,62 @@ +package example.ldap.odm; + +import java.util.List; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; + +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/person") +public class PersonController { + + private final PersonDao personDao; + + public PersonController(PersonDao personDao) { + this.personDao = personDao; + } + + @GetMapping + public List findAll() { + return this.personDao.findAll(); + } + + @GetMapping(params = { "country", "company", "fullName" }) + public Person showPerson(@RequestParam String country, @RequestParam String company, + @RequestParam String fullName) { + return this.personDao.findByPrimaryKey(country, company, fullName); + } + + @PostMapping + public Person addPerson() throws InvalidNameException { + Person person = getPerson(); + this.personDao.create(person); + return person; + } + + @DeleteMapping(params = { "country", "company", "fullName" }) + public void removePerson(@RequestParam String country, @RequestParam String company, + @RequestParam String fullName) { + Person person = this.personDao.findByPrimaryKey(country, company, fullName); + this.personDao.delete(person); + } + + private Person getPerson() throws InvalidNameException { + Person person = new Person(); + person.setDn(new LdapName("cn=John Doe,ou=company1,c=Sweden,dc=spring,dc=io")); + person.setFullName("John Doe"); + person.setLastName("Doe"); + person.setCompany("company1"); + person.setCountry("Sweden"); + person.setDescription("Test user"); + person.setPhone("+46 555-123123"); + return person; + } + +} diff --git a/ldap-odm/src/main/java/example/ldap/odm/PersonDao.java b/ldap-odm/src/main/java/example/ldap/odm/PersonDao.java new file mode 100644 index 00000000..6b17782c --- /dev/null +++ b/ldap-odm/src/main/java/example/ldap/odm/PersonDao.java @@ -0,0 +1,15 @@ +package example.ldap.odm; + +import java.util.List; + +public interface PersonDao { + + void create(Person person); + + void delete(Person person); + + List findAll(); + + Person findByPrimaryKey(String country, String company, String fullname); + +} diff --git a/ldap-odm/src/main/resources/application.yml b/ldap-odm/src/main/resources/application.yml new file mode 100644 index 00000000..ac2e712c --- /dev/null +++ b/ldap-odm/src/main/resources/application.yml @@ -0,0 +1,8 @@ +spring: + ldap: + embedded: + base-dn: dc=io + credential: + username: uid=admin,ou=system + password: secret + ldif: classpath:setup_data.ldif diff --git a/ldap-odm/src/main/resources/setup_data.ldif b/ldap-odm/src/main/resources/setup_data.ldif new file mode 100644 index 00000000..3f36a929 --- /dev/null +++ b/ldap-odm/src/main/resources/setup_data.ldif @@ -0,0 +1,45 @@ +dn: dc=io +objectclass: top +objectclass: domain +objectclass: extensibleObject + +dn: dc=spring,dc=io +objectclass: top +objectclass: domain +objectclass: extensibleObject + +dn: c=Sweden,dc=spring,dc=io +objectclass: top +objectclass: country +c: Sweden +description: The country of Sweden + +dn: ou=company1,c=Sweden,dc=spring,dc=io +objectclass: top +objectclass: organizationalUnit +ou: company1 +description: First company in Sweden + +dn: cn=Some Person,ou=company1,c=Sweden,dc=spring,dc=io +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +uid: some.person +userPassword: password +cn: Some Person +sn: Person +description: Sweden, Company1, Some Person +telephoneNumber: +46 555-123456 + +dn: cn=Some Person2,ou=company1,c=Sweden,dc=spring,dc=io +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +uid: some.person2 +userPassword: password +cn: Some Person2 +sn: Person2 +description: Sweden, Company1, Some Person2 +telephoneNumber: +46 555-654321 diff --git a/ldap-odm/src/test/java/example/ldap/odm/LdapOdmApplicationTests.java b/ldap-odm/src/test/java/example/ldap/odm/LdapOdmApplicationTests.java new file mode 100644 index 00000000..1469806c --- /dev/null +++ b/ldap-odm/src/test/java/example/ldap/odm/LdapOdmApplicationTests.java @@ -0,0 +1,15 @@ +package example.ldap.odm; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class LdapOdmApplicationTests { + + @Test + void contextLoads() { + + } + +} diff --git a/settings.gradle b/settings.gradle index 89e5d1fa..d78f7bdd 100644 --- a/settings.gradle +++ b/settings.gradle @@ -67,6 +67,7 @@ include "jdbc-h2" include "jdbc-mariadb" include "jdbc-mysql" include "jdbc-postgresql" +include "ldap-odm" include "liquibase" include "logging-log4j2" include "logging-logback" @@ -102,3 +103,4 @@ include "webmvc-tomcat" include "webmvc-tomcat-tls" include "websocket" include "websocket-stomp" +