commit 9bbf0389e28184a5c2194216ae667ea552e278fc Author: Mark Paluch Date: Mon Nov 21 15:53:45 2016 +0100 Initial import. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3b34d16 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +.DS_Store +*.iml +*.ipr +*.iws +*.orig +target +.springBeans +.sonar4clipse +*.sonar4clipseExternals +.ant-targets-build.xml +.settings/ +.project +.classpath +src/ant/.ant-targets-upload-dist.xml +atlassian-ide-plugin.xml +/.gradle/ +/.idea/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..3dc28eb --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +language: java + +jdk: + - oraclejdk8 + +env: + matrix: + - PROFILE=ci + +sudo: false + +cache: + directories: + - $HOME/.m2 + +install: true + +script: "mvn clean dependency:list test -P${PROFILE} -Dsort" diff --git a/CODE_OF_CONDUCT.adoc b/CODE_OF_CONDUCT.adoc new file mode 100644 index 0000000..f64fb1b --- /dev/null +++ b/CODE_OF_CONDUCT.adoc @@ -0,0 +1,27 @@ += Contributor Code of Conduct + +As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, such as physical or electronic addresses, + without explicit permission +* Other unethical or unprofessional conduct + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer at spring-code-of-conduct@pivotal.io. +All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. +Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident. + +This Code of Conduct is adapted from the http://contributor-covenant.org[Contributor Covenant], version 1.3.0, available at http://contributor-covenant.org/version/1/3/0/[contributor-covenant.org/version/1/3/0/]. \ No newline at end of file diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc new file mode 100644 index 0000000..f007591 --- /dev/null +++ b/CONTRIBUTING.adoc @@ -0,0 +1,3 @@ += Spring Data contribution guidelines + +You find the contribution guidelines for Spring Data projects https://github.com/spring-projects/spring-data-build/blob/master/CONTRIBUTING.adoc[here]. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f57a3b --- /dev/null +++ b/README.md @@ -0,0 +1,116 @@ +[![Spring Data LDAP](https://spring.io/badges/spring-data-ldap/ga.svg)](http://projects.spring.io/spring-data-ldap#quick-start) +[![Spring Data LDAP](https://spring.io/badges/spring-data-ldap/snapshot.svg)](http://projects.spring.io/spring-data-ldap#quick-start) + +# Spring Data LDAP + +The primary goal of the [Spring Data](http://projects.spring.io/spring-data) project is to make it easier to build Spring-powered applications that use new data access technologies such as non-relational databases, map-reduce frameworks, and cloud based data services. + +The Spring Data LDAP project aims to provide familiar and consistent repository abstractions for [Spring LDAP](https://github.com/spring-projects/spring-ldap). + +## Getting Help + +For a comprehensive treatment of all the Spring Data LDAP features, please refer to: + +* the [User Guide](http://docs.spring.io/spring-data/ldap/docs/current/reference/html/) +* the [JavaDocs](http://docs.spring.io/spring-data/ldap/docs/current/api/) have extensive comments in them as well. +* the home page of [Spring Data LDAP](http://projects.spring.io/spring-data-ldap) contains links to articles and other resources. +* for more detailed questions, use [Spring Data LDAP on Stackoverflow](http://stackoverflow.com/questions/tagged/spring-data-ldap). + +If you are new to Spring as well as to Spring Data, look for information about [Spring projects](http://projects.spring.io/). + + +## Quick Start + +### Maven configuration + +Add the Maven dependency: + +```xml + + org.springframework.data + spring-data-ldap + ${version}.RELEASE + +``` + +If you'd rather like the latest snapshots of the upcoming major version, use our Maven snapshot repository and declare the appropriate dependency version. + +```xml + + org.springframework.data + spring-data-ldap + ${version}.BUILD-SNAPSHOT + + + + spring-libs-snapshot + Spring Snapshot Repository + http://repo.spring.io/libs-snapshot + +``` + +### Spring Data repositories + +To simplify the creation of data repositories Spring Data LDAP provides a generic repository programming model. It will automatically create a repository proxy for you that adds implementations of finder methods you specify on an interface. + +For example, given a `Person` class with first and last name properties, a `PersonRepository` interface that can query for `Person` by last name and when the first name matches a like expression is shown below: + +```java +public interface PersonRepository extends CrudRepository { + + List findByLastname(String lastname); + + List findByFirstnameLike(String firstname); +} +``` + +The queries issued on execution will be derived from the method name. Extending `CrudRepository` causes CRUD methods being pulled into the interface so that you can easily save and find single entities and collections of them. + +You can have Spring automatically create a proxy for the interface by using the following JavaConfig: + +```java +@Configuration +@EnableLdapRepositories +class ApplicationConfig { + +} +``` + +This will find the repository interface and register a proxy object in the container. You can use it as shown below: + +```java +@Service +public class MyService { + + private final PersonRepository repository; + + @Autowired + public MyService(PersonRepository repository) { + this.repository = repository; + } + + public void doWork() { + + repository.deleteAll(); + + Person person = new Person(); + person.setFirstname("Rob"); + person.setLastname("Winch"); + person = repository.save(person); + + List lastNameResults = repository.findByLastname("Winch"); + List firstNameResults = repository.findByFirstnameLike("Ro*"); + } +} +``` + +## Contributing to Spring Data + +Here are some ways for you to get involved in the community: + +* Get involved with the Spring community on Stackoverflow and help out on the [spring-data-ldap](http://stackoverflow.com/questions/tagged/spring-data-ldap) tag by responding to questions and joining the debate. +* Create [JIRA](https://jira.springframework.org/browse/DATALDAP) tickets for bugs and new features and comment and vote on the ones that you are interested in. +* Github is for social coding: if you want to write code, we encourage contributions through pull requests from [forks of this repository](http://help.github.com/forking/). If you want to contribute code this way, please reference a JIRA ticket as well covering the specific issue you are addressing. +* Watch for upcoming articles on Spring by [subscribing](http://spring.io/blog) to spring.io. + +Before we accept a non-trivial patch or pull request we will need you to sign the [contributor's agreement](https://support.springsource.com/spring_committer_signup). Signing the contributor's agreement does not grant anyone commit rights to the main repository, but it does mean that we can accept your contributions, and you will get an author credit if we do. Active contributors might be asked to join the core team, and given the ability to merge pull requests. diff --git a/etc/formatting.xml b/etc/formatting.xml new file mode 100644 index 0000000..b5515c1 --- /dev/null +++ b/etc/formatting.xml @@ -0,0 +1,291 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..931259a --- /dev/null +++ b/pom.xml @@ -0,0 +1,168 @@ + + + + 4.0.0 + + org.springframework.data + spring-data-ldap + 1.0.0.BUILD-SNAPSHOT + + Spring Data LDAP + Spring Data integration for LDAP + https://github.com/spring-projects/spring-data-ldap + + + org.springframework.data.build + spring-data-parent + 1.9.0.BUILD-SNAPSHOT + + + + 2.2.0.BUILD-SNAPSHOT + 1.13.0.BUILD-SNAPSHOT + 3.5.2 + false + + + + + rwinch + Rob Winch + rwinch at pivotal.io + Pivotal + http://www.pivotal.io + + Project Lead + + -6 + + + mpaluch + Mark Paluch + mpaluch at pivotal.io + Pivotal + http://www.pivotal.io + + Developer + + +1 + + + + + + + org.springframework + spring-context + + + + org.springframework + spring-web + + + + org.springframework.ldap + spring-ldap-core + ${spring-ldap} + + + + ${project.groupId} + spring-data-commons + ${springdata.commons} + + + + com.querydsl + querydsl-mongodb + ${querydsl} + true + + + + com.querydsl + querydsl-apt + ${querydsl} + provided + + + + org.springframework + spring-test + ${spring} + test + + + + org.assertj + assertj-core + ${assertj} + test + + + + + + + spring-libs-snapshot + https://repo.spring.io/libs-snapshot + + + + + + spring-plugins-release + https://repo.spring.io/plugins-release + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + org.codehaus.mojo + wagon-maven-plugin + + + org.asciidoctor + asciidoctor-maven-plugin + + + ${project.version} + ${project.name} + ${project.version} + ${aspectj} + ${querydsl} + ${spring} + ${releasetrain} + ${spring-ldap} + true + 3 + true + + + + + + + + + release + + + + org.jfrog.buildinfo + artifactory-maven-plugin + false + + + + + + + diff --git a/src/main/asciidoc/index.adoc b/src/main/asciidoc/index.adoc new file mode 100644 index 0000000..fe19102 --- /dev/null +++ b/src/main/asciidoc/index.adoc @@ -0,0 +1,40 @@ += Spring Data LDAP - Reference Documentation +Mattias Hellborg Arthursson; Ulrik Sandberg; Eric Dalquist; Keith Barlow; Rob Winch; Mark Paluch +:revnumber: {version} +:revdate: {localdate} +:toc: +:toc-placement!: +:spring-data-commons-docs: ../../../../spring-data-commons/src/main/asciidoc + +(C) 2008-2016 The original authors. + +NOTE: _Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically._ + +toc::[] + +include::preface.adoc[] + +:leveloffset: +1 +include::new-features.adoc[] +include::{spring-data-commons-docs}/dependencies.adoc[] +include::{spring-data-commons-docs}/repositories.adoc[] +:leveloffset: -1 + +[[reference]] += Reference Documentation + +:leveloffset: +1 +include::reference/introduction.adoc[] +include::reference/ldap-repositories.adoc[] +:leveloffset: -1 + +[[appendix]] += Appendix + +:numbered!: +:leveloffset: +1 +include::{spring-data-commons-docs}/repository-namespace-reference.adoc[] +include::{spring-data-commons-docs}/repository-populator-namespace-reference.adoc[] +include::{spring-data-commons-docs}/repository-query-keywords-reference.adoc[] +include::{spring-data-commons-docs}/repository-query-return-types-reference.adoc[] +:leveloffset: -1 diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc new file mode 100644 index 0000000..eb06c46 --- /dev/null +++ b/src/main/asciidoc/new-features.adoc @@ -0,0 +1,6 @@ +[[new-features]] += New & Noteworthy + +[[new-features.1.0]] +== What's new in Spring Data LDAP 1.0 +* Migration of Spring LDAP's repository suport into Spring Data LDAP. diff --git a/src/main/asciidoc/preface.adoc b/src/main/asciidoc/preface.adoc new file mode 100644 index 0000000..4b0c6ab --- /dev/null +++ b/src/main/asciidoc/preface.adoc @@ -0,0 +1,43 @@ +[[preface]] += Preface + +Spring Data LDAP makes it easier to build Spring-based applications that use the Lightweight Directory Access Protocol with Spring LDAP. + +This document is the reference guide for Spring Data - Document Support. It explains Document module concepts and semantics and the syntax for various store namespaces. + + +[[get-started:first-steps:spring]] +== Knowing Spring +Spring Data uses Spring framework's http://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/spring-core.html[core] functionality, such as the http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/beans.html[IoC] container, http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/validation.html#core-convert[type conversion system], http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/expressions.html[expression language], http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/jmx.html[JMX integration], and portable http://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/html/dao.html#dao-exceptions[DAO exception hierarchy]. While it is not important to know the Spring APIs, understanding the concepts behind them is. At a minimum, the idea behind IoC should be familiar for whatever IoC container you choose to use. + + +To learn more about Spring, you can refer to the comprehensive (and sometimes disarming) documentation that explains in detail the Spring Framework. There are a lot of articles, blog entries and books on the matter - take a look at the Spring framework http://spring.io/docs[home page ] for more information. + +[[requirements]] +== Requirements + +Spring Data LDAP 1.x binaries requires JDK level 6.0 and above, http://spring.io/docs[Spring Framework] {springVersion} and above, and http://projects.spring.io/spring-ldap[Spring LDAP] {springLdapVersion} and above. + +== Additional Help Resources + +Learning a new framework is not always straight forward. In this section, we try to provide what we think is an easy to follow guide for starting with Spring Data LDAP module. However, if you encounter issues or you are just looking for an advice, feel free to use one of the links below: + +[[get-started:help]] +=== Support + +There are a few support options available: + +[[get-started:help:community]] +==== Community Forum + +Spring Data on Stackoverflow http://stackoverflow.com/questions/tagged/spring-data[Stackoverflow] is a tag for all Spring Data (not just Document) users to share information and help each other. Note that registration is needed *only* for posting. + +[[get-started:help:professional]] +==== Professional Support + +Professional, from-the-source support, with guaranteed response time, is available from http://pivotal.io/[Pivotal Sofware, Inc.], the company behind Spring Data and Spring. + +[[get-started:up-to-date]] +=== Following Development + +For information on the Spring Data LDAP source code repository, nightly builds and snapshot artifacts please see the http://projects.spring.io/spring-data-ldap/[Spring Data LDAP homepage]. You can help make Spring Data best serve the needs of the Spring community by interacting with developers through the Community on http://stackoverflow.com/questions/tagged/spring-data[Stackoverflow]. To follow developer activity look for the mailing list information on the Spring Data LDAP homepage. If you encounter a bug or want to suggest an improvement, please create a ticket on the Spring Data issue https://jira.spring.io/browse/DATALDAP[tracker]. To stay up to date with the latest news and announcements in the Spring eco system, subscribe to the Spring Community http://spring.io[Portal]. Lastly, you can follow the Spring http://spring.io/blog[blog ]or the project team on Twitter (http://twitter.com/SpringData[SpringData]). diff --git a/src/main/asciidoc/reference/introduction.adoc b/src/main/asciidoc/reference/introduction.adoc new file mode 100644 index 0000000..2d1da13 --- /dev/null +++ b/src/main/asciidoc/reference/introduction.adoc @@ -0,0 +1,9 @@ +[[introduction]] += Introduction + +== Document Structure + +This part of the reference documentation explains the core functionality offered by Spring Data LDAP. + +<> introduces the repository support for LDAP. + diff --git a/src/main/asciidoc/reference/ldap-repositories.adoc b/src/main/asciidoc/reference/ldap-repositories.adoc new file mode 100644 index 0000000..ecb7b07 --- /dev/null +++ b/src/main/asciidoc/reference/ldap-repositories.adoc @@ -0,0 +1,200 @@ +[[ldap.repositories]] += LDAP repositories + +[[ldap.repo-intro]] +== Introduction + +This chapter will point out the specialties for repository support for MongoDB. This builds on the core repository support explained in <>. So make sure you've got a sound understanding of the basic concepts explained there. + +* Spring LDAP repositories can be enabled using an `` tag in your XML configuration or using an `@EnableLdapRepositories` annotation on a configuration class. +* To include support for `LdapQuery` parameters in automatically generated repositories, have your interface extend `LdapRepository` rather than `CrudRepository`. +* All Spring LDAP repositories must work with entities annotated with the ODM annotations, as described in http://docs.spring.io/spring-ldap/docs/{springLdapVersion}.RELEASE/reference/#odm[Object-Directory Mapping]. +* Since all ODM managed classes must have a Distinguished Name as ID, all Spring LDAP repositories must have the ID type parameter set to `javax.naming.Name`. + Indeed, the built-in `LdapRepository` only takes one type parameter; the managed entity class, defaulting ID to `javax.naming.Name`. +* Due to specifics of the LDAP protocol, paging and sorting is not supported for Spring LDAP repositories. + +NOTE: Make sure to use ODM annotations like `org.springframework.ldap.odm.annotations.Id`. Using Spring Data's annotation does not work as Spring LDAP uses its own mapping layer. + +[[ldap.repo-usage]] +== Usage + +To access domain entities stored in a LDAP-compliant directory you can leverage our sophisticated repository support that eases implementing those quite significantly. To do so, simply create an interface for your repository: + +.Sample Person entity +==== +[source,java] +---- +@Entry(objectClasses = { "person", "top" }, base="ou=someOu") +public class Person { + + @Id + private Name dn; + + @Attribute(name="cn") + @DnAttribute(value="cn", index=1) + private String fullName; + + @Attribute(name="firstName") + private String firstName; + + // No @Attribute annotation means this will be bound to the LDAP attribute + // with the same value + private String firstName; + + @DnAttribute(value="ou", index=0) + @Transient + private String company; + + @Transient + private String someUnmappedField; + // ...more attributes below +} +---- +==== + +We have a quite simple domain object here. Note that it has a property named `dn` of type `Name`. + +.Basic repository interface to persist Person entities +==== +[source] +---- +public interface PersonRepository extends CrudRepository { + + // additional custom finder methods go here +} +---- +==== + +Right now this interface simply serves typing purposes but we will add additional methods to it later. In your Spring configuration simply add + + +.JavaConfig for repositories +==== +[source,java] +---- +@Configuration +@EnableLdapRepositories +class ApplicationConfig { + + @Bean + ContextSource contextSource() { + + LdapContextSource ldapContextSource = new LdapContextSource(); + ldapContextSource.setUrl("ldap://127.0.0.1:389"); + + return ldapContextSource; + } + + @Bean + LdapTemplate ldapTemplate(ContextSource contextSource) { + return new LdapTemplate(contextSource); + } +} +---- +==== + +As our domain repository extends `CrudRepository` it provides you with CRUD operations as well as methods for access to the entities. Working with the repository instance is just a matter of dependency injecting it into a client. + +.Paging access to Person entities +==== +[source,java] +---- +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class PersonRepositoryTests { + + @Autowired PersonRepository repository; + + @Test + public void readAll() { + + List persons = repository.findAll(); + assertThat(persons.isEmpty(), is(false)); + } +} +---- +==== + +The sample creates an application context with Spring's unit test support which will perform annotation based dependency injection into test cases. Inside the test method we simply use the repository to query the datastore. + +[[ldap.repositories.queries]] +== Query methods + +Most of the data access operations you usually trigger on a repository result a query being executed against the MongoDB databases. Defining such a query is just a matter of declaring a method on the repository interface + +.PersonRepository with query methods +==== +[source,java] +---- +public interface PersonRepository extends PagingAndSortingRepository { + + List findByLastname(String lastname); <1> + + List findByLastnameFirstname(String lastname, String firstname); <2> +} +---- +<1> The method shows a query for all people with the given lastname. The query will be derived parsing the method name for constraints which can be concatenated with `And` and `Or`. Thus the method name will result in a query expression of `(&(objectclass=person)(lastname=lastname))`. +<1> The method shows a query for all people with the given lastname and firstname. The query will be derived parsing the method name.Thus the method name will result in a query expression of `(&(objectclass=person)(lastname=lastname)(firstname=firstname))`. +==== + +[cols="1,2,3", options="header"] +.Supported keywords for query methods +|=== +| Keyword +| Sample +| Logical result + +| `LessThanEqual` +| `findByAgeLessThanEqual(int age)` +| `(attribute<=age)` + +| `GreaterThanEqual` +| `findByAgeGreaterThanEqual(int age)` +| `(attribute>=age)` + +| `IsNotNull`, `NotNull` +| `findByFirstnameNotNull()` +| `(firstname=*)` + +| `IsNull`, `Null` +| `findByFirstnameNull()` +| `(!(firstname=*))` + +| `Like` +| `findByFirstnameLike(String name)` +| `(firstname=name)` + +| `NotLike`, `IsNotLike` +| `findByFirstnameNotLike(String name)` +| `(!(firstname=name*))` + +| `StartingWith` +| `findByStartingWith(String name)` +| `(firstname=name*)` + +| `EndingWith` +| `findByFirstnameLike(String name)` +| `(firstname=*name)` + +| `Containing` +| `findByFirstnameLike(String name)` +| `(firstname=\*name*)` + +| `(No keyword)` +| `findByFirstname(String name)` +| `(Firstname=name)` + +| `Not` +| `findByFirstnameNot(String name)` +| `(!(Firstname=name))` + +|=== + + +=== QueryDSL support +Basic QueryDSL support is included in Spring LDAP. This support includes the following: + +* An Annotation Processor, `LdapAnnotationProcessor`, for generating QueryDSL classes based on Spring LDAP ODM annotations. See http://docs.spring.io/spring-ldap/docs/{springLdapVersion}.RELEASE/reference/#odm[Object-Directory Mapping] for more information on the ODM annotations. +* A Query implementation, `QueryDslLdapQuery`, for building and executing QueryDSL queries in code. +* Spring Data repository support for QueryDSL predicates. `QueryDslPredicateExecutor` includes a number of additional methods with appropriate parameters; extend this interface along with `LdapRepository` to include this support in your repository. + diff --git a/src/main/java/org/springframework/data/ldap/repository/LdapRepository.java b/src/main/java/org/springframework/data/ldap/repository/LdapRepository.java new file mode 100644 index 0000000..e09b375 --- /dev/null +++ b/src/main/java/org/springframework/data/ldap/repository/LdapRepository.java @@ -0,0 +1,47 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository; + +import javax.naming.Name; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.ldap.query.LdapQuery; + +/** + * Ldap specific extensions to CrudRepository. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public interface LdapRepository extends CrudRepository { + + /** + * Find one entry matching the specified query. + * + * @param ldapQuery the query specification. + * @return the found entry or null if no matching entry was found. + * @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one entry matches the query. + */ + T findOne(LdapQuery ldapQuery); + + /** + * Find all entries matching the specified query. + * + * @param ldapQuery the query specification. + * @return the entries matching the query. + */ + Iterable findAll(LdapQuery ldapQuery); +} diff --git a/src/main/java/org/springframework/data/ldap/repository/Query.java b/src/main/java/org/springframework/data/ldap/repository/Query.java new file mode 100644 index 0000000..d26d3cb --- /dev/null +++ b/src/main/java/org/springframework/data/ldap/repository/Query.java @@ -0,0 +1,75 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.ldap.query.SearchScope; + +/** + * Annotation for use in {@link org.springframework.data.ldap.repository.LdapRepository} declarations to create + * automatic query methods based on statically defined queries. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Query { + + /** + * Search base, to be used as input to + * {@link org.springframework.ldap.query.LdapQueryBuilder#base(javax.naming.Name)}. + * + * @return the search base, default is {@link org.springframework.ldap.support.LdapUtils#emptyLdapName()} + */ + String base() default ""; + + /** + * The filter format string, to be used as input to + * {@link org.springframework.ldap.query.LdapQueryBuilder#filter(String, Object...)}. + * + * @return search filter, must be specified. + */ + String value() default ""; + + /** + * Search scope, to be used as input to + * {@link org.springframework.ldap.query.LdapQueryBuilder#searchScope(org.springframework.ldap.query.SearchScope)}. + * + * @return the search scope. + */ + SearchScope searchScope() default SearchScope.SUBTREE; + + /** + * Time limit, to be used as input to {@link org.springframework.ldap.query.LdapQueryBuilder#timeLimit(int)}. + * + * @return the time limit. + */ + int timeLimit() default 0; + + /** + * Count limit, to be used as input to {@link org.springframework.ldap.query.LdapQueryBuilder#countLimit(int)}. + * + * @return the count limit. + */ + int countLimit() default 0; +} diff --git a/src/main/java/org/springframework/data/ldap/repository/config/EnableLdapRepositories.java b/src/main/java/org/springframework/data/ldap/repository/config/EnableLdapRepositories.java new file mode 100644 index 0000000..603f256 --- /dev/null +++ b/src/main/java/org/springframework/data/ldap/repository/config/EnableLdapRepositories.java @@ -0,0 +1,113 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.config; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Import; +import org.springframework.data.repository.query.QueryLookupStrategy.Key; +import org.springframework.data.ldap.repository.support.LdapRepositoryFactoryBean; + +/** + * Annotation to activate Ldap repositories. If no base package is configured through either {@link #value()}, + * {@link #basePackages()} or {@link #basePackageClasses()} it will trigger scanning of the package of annotated class. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@Import(LdapRepositoriesRegistrar.class) +public @interface EnableLdapRepositories { + + /** + * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.: + * {@code @EnableLdapRepositories("org.my.pkg")} instead of {@code @EnableLdapRepositories(basePackages="org.my.pkg")}. + */ + String[] value() default {}; + + /** + * Base packages to scan for annotated components. {@link #value()} is an alias for (and mutually exclusive with) this + * attribute. Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names. + */ + String[] basePackages() default {}; + + /** + * Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for annotated components. The + * package of each class specified will be scanned. Consider creating a special no-op marker class or interface in + * each package that serves no purpose other than being referenced by this attribute. + */ + Class[] basePackageClasses() default {}; + + /** + * Specifies which types are eligible for component scanning. Further narrows the set of candidate components from + * everything in {@link #basePackages()} to everything in the base packages that matches the given filter or filters. + */ + Filter[] includeFilters() default {}; + + /** + * Specifies which types are not eligible for component scanning. + */ + Filter[] excludeFilters() default {}; + + /** + * Returns the postfix to be used when looking up custom repository implementations. Defaults to {@literal Impl}. So + * for a repository named {@code PersonRepository} the corresponding implementation class will be looked up scanning + * for {@code PersonRepositoryImpl}. + * + * @return + */ + String repositoryImplementationPostfix() default ""; + + /** + * Configures the location of where to find the Spring Data named queries properties file. Will default to + * {@code META-INFO/mongo-named-queries.properties}. + * + * @return + */ + String namedQueriesLocation() default ""; + + /** + * Returns the key of the {@link org.springframework.data.repository.query.QueryLookupStrategy} to be used for lookup queries for query methods. Defaults to + * {@link org.springframework.data.repository.query.QueryLookupStrategy.Key#CREATE_IF_NOT_FOUND}. + * + * @return + */ + Key queryLookupStrategy() default Key.CREATE_IF_NOT_FOUND; + + /** + * Returns the {@link org.springframework.beans.factory.FactoryBean} class to be used for each repository instance. Defaults to + * {@link org.springframework.data.ldap.repository.support.LdapRepositoryFactoryBean}. + * + * @return + */ + Class repositoryFactoryBeanClass() default LdapRepositoryFactoryBean.class; + + /** + * Configures the name of the {@link org.springframework.ldap.core.LdapTemplate} bean to be used with the repositories detected. + * + * @return + */ + String ldapTemplateRef() default "ldapTemplate"; +} diff --git a/src/main/java/org/springframework/data/ldap/repository/config/LdapRepositoriesRegistrar.java b/src/main/java/org/springframework/data/ldap/repository/config/LdapRepositoriesRegistrar.java new file mode 100644 index 0000000..3fbe761 --- /dev/null +++ b/src/main/java/org/springframework/data/ldap/repository/config/LdapRepositoriesRegistrar.java @@ -0,0 +1,40 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.config; + +import java.lang.annotation.Annotation; + +import org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport; +import org.springframework.data.repository.config.RepositoryConfigurationExtension; + +/** + * LDAP-specific {@link org.springframework.context.annotation.ImportBeanDefinitionRegistrar}. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +class LdapRepositoriesRegistrar extends RepositoryBeanDefinitionRegistrarSupport { + + @Override + protected Class getAnnotation() { + return EnableLdapRepositories.class; + } + + @Override + protected RepositoryConfigurationExtension getExtension() { + return new LdapRepositoryConfigurationExtension(); + } +} diff --git a/src/main/java/org/springframework/data/ldap/repository/config/LdapRepositoryConfigurationExtension.java b/src/main/java/org/springframework/data/ldap/repository/config/LdapRepositoryConfigurationExtension.java new file mode 100644 index 0000000..3c1f88e --- /dev/null +++ b/src/main/java/org/springframework/data/ldap/repository/config/LdapRepositoryConfigurationExtension.java @@ -0,0 +1,108 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.config; + +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Collections; + +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.core.annotation.AnnotationAttributes; +import org.springframework.data.ldap.repository.LdapRepository; +import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource; +import org.springframework.data.repository.config.RepositoryConfigurationExtension; +import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport; +import org.springframework.data.repository.config.XmlRepositoryConfigurationSource; +import org.springframework.ldap.odm.annotations.Entry; +import org.springframework.data.ldap.repository.support.LdapRepositoryFactoryBean; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +/** + * {@link RepositoryConfigurationExtension} for LDAP. + * + * @author Mattias Hellborg Arthursson + * @author Mark Paluch + * @since 2.0 + */ +public class LdapRepositoryConfigurationExtension extends RepositoryConfigurationExtensionSupport { + + private static final String ATT_LDAP_TEMPLATE_REF = "ldap-template-ref"; + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getModuleName() + */ + @Override + public String getModuleName() { + return "LDAP"; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getModulePrefix() + */ + @Override + protected String getModulePrefix() { + return "ldap"; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationExtension#getRepositoryFactoryClassName() + */ + public String getRepositoryFactoryClassName() { + return LdapRepositoryFactoryBean.class.getName(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getIdentifyingAnnotations() + */ + @Override + protected Collection> getIdentifyingAnnotations() { + return Collections.> singleton(Entry.class); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getIdentifyingTypes() + */ + @Override + protected Collection> getIdentifyingTypes() { + return Collections.> singleton(LdapRepository.class); + } + + @Override + public void postProcess(BeanDefinitionBuilder builder, XmlRepositoryConfigurationSource config) { + + Element element = config.getElement(); + String ldapTemplateRef = element.getAttribute(ATT_LDAP_TEMPLATE_REF); + if (!StringUtils.hasText(ldapTemplateRef)) { + ldapTemplateRef = "ldapTemplate"; + } + + builder.addPropertyReference("ldapOperations", ldapTemplateRef); + } + + @Override + public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource config) { + + AnnotationAttributes attributes = config.getAttributes(); + + builder.addPropertyReference("ldapOperations", attributes.getString("ldapTemplateRef")); + } +} diff --git a/src/main/java/org/springframework/data/ldap/repository/query/AbstractLdapRepositoryQuery.java b/src/main/java/org/springframework/data/ldap/repository/query/AbstractLdapRepositoryQuery.java new file mode 100644 index 0000000..22298c0 --- /dev/null +++ b/src/main/java/org/springframework/data/ldap/repository/query/AbstractLdapRepositoryQuery.java @@ -0,0 +1,66 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.query; + +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.query.LdapQuery; + +/** + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +abstract class AbstractLdapRepositoryQuery implements RepositoryQuery { + + private final LdapQueryMethod queryMethod; + private final Class clazz; + private final LdapOperations ldapOperations; + + public AbstractLdapRepositoryQuery(LdapQueryMethod queryMethod, Class clazz, LdapOperations ldapOperations) { + this.queryMethod = queryMethod; + this.clazz = clazz; + this.ldapOperations = ldapOperations; + } + + @Override + public final Object execute(Object[] parameters) { + + LdapQuery query = createQuery(parameters); + + if (queryMethod.isCollectionQuery()) { + return ldapOperations.find(query, clazz); + } else { + try { + return ldapOperations.findOne(query, clazz); + } catch (EmptyResultDataAccessException e) { + return null; + } + } + } + + protected abstract LdapQuery createQuery(Object[] parameters); + + Class getClazz() { + return clazz; + } + + @Override + public final QueryMethod getQueryMethod() { + return queryMethod; + } +} diff --git a/src/main/java/org/springframework/data/ldap/repository/query/AnnotatedLdapRepositoryQuery.java b/src/main/java/org/springframework/data/ldap/repository/query/AnnotatedLdapRepositoryQuery.java new file mode 100644 index 0000000..4a98154 --- /dev/null +++ b/src/main/java/org/springframework/data/ldap/repository/query/AnnotatedLdapRepositoryQuery.java @@ -0,0 +1,61 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.query; + +import static org.springframework.ldap.query.LdapQueryBuilder.*; + +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.query.LdapQuery; +import org.springframework.data.ldap.repository.Query; +import org.springframework.util.Assert; + +/** + * Handles queries for repository methods annotated with {@link org.springframework.data.ldap.repository.Query}. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public class AnnotatedLdapRepositoryQuery extends AbstractLdapRepositoryQuery { + + private final Query queryAnnotation; + + /** + * Construct a new instance. + * + * @param queryMethod the QueryMethod. + * @param clazz the managed class. + * @param ldapOperations the LdapOperations instance to use. + */ + public AnnotatedLdapRepositoryQuery(LdapQueryMethod queryMethod, Class clazz, LdapOperations ldapOperations) { + + super(queryMethod, clazz, ldapOperations); + + queryAnnotation = queryMethod.getQueryAnnotation(); + + Assert.notNull(queryMethod, "Annotation must be present"); + Assert.hasLength(queryAnnotation.value(), "Query filter must be specified"); + } + + @Override + protected LdapQuery createQuery(Object[] parameters) { + + return query().base(queryAnnotation.base()) // + .searchScope(queryAnnotation.searchScope()) // + .countLimit(queryAnnotation.countLimit()) // + .timeLimit(queryAnnotation.timeLimit()) // + .filter(queryAnnotation.value(), parameters); + } +} diff --git a/src/main/java/org/springframework/data/ldap/repository/query/LdapQueryCreator.java b/src/main/java/org/springframework/data/ldap/repository/query/LdapQueryCreator.java new file mode 100644 index 0000000..1eac3b8 --- /dev/null +++ b/src/main/java/org/springframework/data/ldap/repository/query/LdapQueryCreator.java @@ -0,0 +1,128 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.query; + +import static org.springframework.ldap.query.LdapQueryBuilder.*; + +import java.util.Iterator; + +import org.springframework.data.domain.Sort; +import org.springframework.data.mapping.PropertyPath; +import org.springframework.data.repository.query.Parameters; +import org.springframework.data.repository.query.ParametersParameterAccessor; +import org.springframework.data.repository.query.parser.AbstractQueryCreator; +import org.springframework.data.repository.query.parser.Part; +import org.springframework.data.repository.query.parser.PartTree; +import org.springframework.ldap.odm.annotations.Entry; +import org.springframework.ldap.odm.core.ObjectDirectoryMapper; +import org.springframework.ldap.query.ConditionCriteria; +import org.springframework.ldap.query.ContainerCriteria; +import org.springframework.ldap.query.LdapQuery; + +/** + * Creator of dynamic queries based on method names. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public class LdapQueryCreator extends AbstractQueryCreator { + + private final Class clazz; + private final ObjectDirectoryMapper mapper; + + /** + * Construct a new instance. + */ + public LdapQueryCreator(PartTree tree, Parameters parameters, Class clazz, ObjectDirectoryMapper mapper, + Object[] values) { + + super(tree, new ParametersParameterAccessor(parameters, values)); + + this.clazz = clazz; + this.mapper = mapper; + } + + @Override + protected ContainerCriteria create(Part part, Iterator iterator) { + + String base = clazz.getAnnotation(Entry.class).base(); + ConditionCriteria criteria = query().base(base).where(getAttribute(part)); + + return appendCondition(part, iterator, criteria); + } + + private ContainerCriteria appendCondition(Part part, Iterator iterator, ConditionCriteria criteria) { + + Part.Type type = part.getType(); + + String value = null; + if (iterator.hasNext()) { + value = iterator.next().toString(); + } + switch (type) { + case NEGATING_SIMPLE_PROPERTY: + return criteria.not().is(value); + case SIMPLE_PROPERTY: + return criteria.is(value); + case STARTING_WITH: + return criteria.like(value + "*"); + case ENDING_WITH: + return criteria.like("*" + value); + case CONTAINING: + return criteria.like("*" + value + "*"); + case LIKE: + return criteria.like(value); + case NOT_LIKE: + return criteria.not().like(value); + case GREATER_THAN_EQUAL: + return criteria.gte(value); + case LESS_THAN_EQUAL: + return criteria.lte(value); + case IS_NOT_NULL: + return criteria.isPresent(); + case IS_NULL: + return criteria.not().isPresent(); + } + + throw new IllegalArgumentException(String.format("%s queries are not supported for LDAP repositories", type)); + } + + private String getAttribute(Part part) { + PropertyPath path = part.getProperty(); + if (path.hasNext()) { + throw new IllegalArgumentException("Nested properties are not supported"); + } + + return mapper.attributeFor(clazz, path.getSegment()); + } + + @Override + protected ContainerCriteria and(Part part, ContainerCriteria base, Iterator iterator) { + ConditionCriteria criteria = base.and(getAttribute(part)); + + return appendCondition(part, iterator, criteria); + } + + @Override + protected ContainerCriteria or(ContainerCriteria base, ContainerCriteria criteria) { + return base.or(criteria); + } + + @Override + protected LdapQuery complete(ContainerCriteria criteria, Sort sort) { + return criteria; + } +} diff --git a/src/main/java/org/springframework/data/ldap/repository/query/LdapQueryMethod.java b/src/main/java/org/springframework/data/ldap/repository/query/LdapQueryMethod.java new file mode 100644 index 0000000..cfc0aa3 --- /dev/null +++ b/src/main/java/org/springframework/data/ldap/repository/query/LdapQueryMethod.java @@ -0,0 +1,69 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.query; + +import java.lang.reflect.Method; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.ldap.repository.Query; + +/** + * QueryMethod for Ldap Queries. + * + * @author Mattias Hellborg Arthursson + * @author Eddu Melendez + * @since 2.0 + */ +public class LdapQueryMethod extends QueryMethod { + + private final Method method; + + /** + * Creates a new LdapQueryMethod from the given parameters. + * + * @param method must not be {@literal null} + * @param metadata must not be {@literal null} + */ + public LdapQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory) { + + super(method, metadata, factory); + + this.method = method; + } + + /** + * Check whether the target method is annotated with {@link org.springframework.data.ldap.repository.Query}. + * + * @return true if the target method is annotated with {@link org.springframework.data.ldap.repository.Query}, + * false otherwise. + */ + public boolean hasQueryAnnotation() { + return getQueryAnnotation() != null; + } + + /** + * Get the {@link org.springframework.data.ldap.repository.Query} annotation of the target method (if any). + * + * @return the {@link org.springframework.data.ldap.repository.Query} annotation of the target method if present, or + * null otherwise. + */ + Query getQueryAnnotation() { + return AnnotationUtils.getAnnotation(method, Query.class); + } +} diff --git a/src/main/java/org/springframework/data/ldap/repository/query/PartTreeLdapRepositoryQuery.java b/src/main/java/org/springframework/data/ldap/repository/query/PartTreeLdapRepositoryQuery.java new file mode 100644 index 0000000..d442a6e --- /dev/null +++ b/src/main/java/org/springframework/data/ldap/repository/query/PartTreeLdapRepositoryQuery.java @@ -0,0 +1,49 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.query; + +import org.springframework.data.repository.query.Parameters; +import org.springframework.data.repository.query.parser.PartTree; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.odm.core.ObjectDirectoryMapper; +import org.springframework.ldap.query.LdapQuery; + +/** + * @author Mattias Hellborg Arthursson + */ +public class PartTreeLdapRepositoryQuery extends AbstractLdapRepositoryQuery { + + private final PartTree partTree; + private final Parameters parameters; + private final ObjectDirectoryMapper objectDirectoryMapper; + + public PartTreeLdapRepositoryQuery(LdapQueryMethod queryMethod, Class clazz, LdapOperations ldapOperations) { + + super(queryMethod, clazz, ldapOperations); + + partTree = new PartTree(queryMethod.getName(), clazz); + parameters = queryMethod.getParameters(); + objectDirectoryMapper = ldapOperations.getObjectDirectoryMapper(); + } + + @Override + protected LdapQuery createQuery(Object[] actualParameters) { + + org.springframework.data.ldap.repository.query.LdapQueryCreator queryCreator = new LdapQueryCreator(partTree, + this.parameters, getClazz(), objectDirectoryMapper, actualParameters); + return queryCreator.createQuery(); + } +} diff --git a/src/main/java/org/springframework/data/ldap/repository/support/DefaultLdapAnnotationProcessorConfiguration.java b/src/main/java/org/springframework/data/ldap/repository/support/DefaultLdapAnnotationProcessorConfiguration.java new file mode 100644 index 0000000..dd325c7 --- /dev/null +++ b/src/main/java/org/springframework/data/ldap/repository/support/DefaultLdapAnnotationProcessorConfiguration.java @@ -0,0 +1,59 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.support; + +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.Map; + +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.VariableElement; + +import org.springframework.ldap.odm.annotations.Id; + +import com.querydsl.apt.DefaultConfiguration; + +/** + * @author Mattias Hellborg Arthursson + * @author Eddu Melendez + * @since 2.0 + */ +class DefaultLdapAnnotationProcessorConfiguration extends DefaultConfiguration { + + public DefaultLdapAnnotationProcessorConfiguration( + RoundEnvironment roundEnv, + Map options, + Collection keywords, + Class entitiesAnn, + Class entityAnn, + Class superTypeAnn, + Class embeddableAnn, + Class embeddedAnn, + Class skipAnn) { + + super(roundEnv, options, keywords, entitiesAnn, entityAnn, superTypeAnn, embeddableAnn, embeddedAnn, skipAnn); + } + + @Override + public boolean isBlockedField(VariableElement field) { + return super.isBlockedField(field) || field.getAnnotation(Id.class) != null; + } + + @Override + public boolean isValidField(VariableElement field) { + return super.isValidField(field) && field.getAnnotation(Id.class) == null; + } +} diff --git a/src/main/java/org/springframework/data/ldap/repository/support/LdapAnnotationProcessor.java b/src/main/java/org/springframework/data/ldap/repository/support/LdapAnnotationProcessor.java new file mode 100644 index 0000000..0851397 --- /dev/null +++ b/src/main/java/org/springframework/data/ldap/repository/support/LdapAnnotationProcessor.java @@ -0,0 +1,57 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.support; + +import java.util.Collections; + +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.tools.Diagnostic; + +import org.springframework.ldap.odm.annotations.Entry; +import org.springframework.ldap.odm.annotations.Transient; + +import com.querydsl.apt.AbstractQuerydslProcessor; +import com.querydsl.apt.Configuration; +import com.querydsl.apt.DefaultConfiguration; +import com.querydsl.core.annotations.QueryEntities; + +/** + * QueryDSL Annotation Processor to generate QueryDSL classes for entity classes annotated with {@link Entry}. + * + * @author Mattias Hellborg Arthursson + * @author Eddu Melendez + * @since 2.0 + */ +@SupportedAnnotationTypes("org.springframework.ldap.odm.annotations.*") +@SupportedSourceVersion(SourceVersion.RELEASE_6) +public class LdapAnnotationProcessor extends AbstractQuerydslProcessor { + + @Override + protected Configuration createConfiguration(RoundEnvironment roundEnv) { + processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Running " + getClass().getSimpleName()); + + DefaultConfiguration configuration = new DefaultLdapAnnotationProcessorConfiguration(roundEnv, + processingEnv.getOptions(), Collections. emptySet(), QueryEntities.class, Entry.class, null, null, null, + Transient.class); + configuration.setUseFields(true); + configuration.setUseGetters(false); + + return configuration; + } +} diff --git a/src/main/java/org/springframework/data/ldap/repository/support/LdapRepositoryFactory.java b/src/main/java/org/springframework/data/ldap/repository/support/LdapRepositoryFactory.java new file mode 100644 index 0000000..c100e8e --- /dev/null +++ b/src/main/java/org/springframework/data/ldap/repository/support/LdapRepositoryFactory.java @@ -0,0 +1,114 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.support; + +import static org.springframework.data.querydsl.QueryDslUtils.*; + +import java.io.Serializable; +import java.lang.reflect.Method; + +import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.querydsl.QueryDslPredicateExecutor; +import org.springframework.data.repository.core.EntityInformation; +import org.springframework.data.repository.core.NamedQueries; +import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.data.repository.query.EvaluationContextProvider; +import org.springframework.data.repository.query.QueryLookupStrategy; +import org.springframework.data.repository.query.QueryLookupStrategy.Key; +import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.data.ldap.repository.query.AnnotatedLdapRepositoryQuery; +import org.springframework.data.ldap.repository.query.LdapQueryMethod; +import org.springframework.data.ldap.repository.query.PartTreeLdapRepositoryQuery; +import org.springframework.data.ldap.repository.support.SimpleLdapRepository; + +/** + * Factory to create {@link org.springframework.data.ldap.repository.LdapRepository} instances. + * + * @author Mattias Hellborg Arthursson + * @author Eddu Melendez + * @since 2.0 + */ +public class LdapRepositoryFactory extends RepositoryFactorySupport { + + private final LdapOperations ldapOperations; + + public LdapRepositoryFactory(LdapOperations ldapOperations) { + this.ldapOperations = ldapOperations; + } + + @Override + public EntityInformation getEntityInformation(Class domainClass) { + return null; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected Object getTargetRepository(RepositoryMetadata metadata) { + + if (!isQueryDslRepository(metadata.getRepositoryInterface())) { + return new org.springframework.data.ldap.repository.support.SimpleLdapRepository(ldapOperations, + ldapOperations.getObjectDirectoryMapper(), metadata.getDomainType()); + } + + return new QueryDslLdapRepository(ldapOperations, ldapOperations.getObjectDirectoryMapper(), + metadata.getDomainType()); + } + + protected Object getTargetRepository(RepositoryInformation metadata) { + return getTargetRepository((RepositoryMetadata) metadata); + } + + private static boolean isQueryDslRepository(Class repositoryInterface) { + return QUERY_DSL_PRESENT && QueryDslPredicateExecutor.class.isAssignableFrom(repositoryInterface); + } + + @Override + protected Class getRepositoryBaseClass(RepositoryMetadata metadata) { + if (!isQueryDslRepository(metadata.getRepositoryInterface())) { + return SimpleLdapRepository.class; + } else { + return QueryDslLdapRepository.class; + } + } + + @Override + protected QueryLookupStrategy getQueryLookupStrategy(QueryLookupStrategy.Key key) { + return new LdapQueryLookupStrategy(); + } + + @Override + protected QueryLookupStrategy getQueryLookupStrategy(Key key, EvaluationContextProvider evaluationContextProvider) { + return new LdapQueryLookupStrategy(); + } + + private final class LdapQueryLookupStrategy implements QueryLookupStrategy { + + @Override + public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory, + NamedQueries namedQueries) { + LdapQueryMethod queryMethod = new LdapQueryMethod(method, metadata, factory); + Class domainType = metadata.getDomainType(); + + if (queryMethod.hasQueryAnnotation()) { + return new AnnotatedLdapRepositoryQuery(queryMethod, domainType, ldapOperations); + } else { + return new PartTreeLdapRepositoryQuery(queryMethod, domainType, ldapOperations); + } + } + } +} diff --git a/src/main/java/org/springframework/data/ldap/repository/support/LdapRepositoryFactoryBean.java b/src/main/java/org/springframework/data/ldap/repository/support/LdapRepositoryFactoryBean.java new file mode 100644 index 0000000..5988342 --- /dev/null +++ b/src/main/java/org/springframework/data/ldap/repository/support/LdapRepositoryFactoryBean.java @@ -0,0 +1,53 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.support; + +import javax.naming.Name; + +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.data.ldap.repository.support.LdapRepositoryFactory; +import org.springframework.util.Assert; + +/** + * {@link org.springframework.beans.factory.FactoryBean} to create + * {@link org.springframework.data.ldap.repository.LdapRepository} instances. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public class LdapRepositoryFactoryBean, S> + extends RepositoryFactoryBeanSupport { + + private LdapOperations ldapOperations; + + public void setLdapOperations(LdapOperations ldapOperations) { + this.ldapOperations = ldapOperations; + } + + @Override + protected RepositoryFactorySupport createRepositoryFactory() { + return new LdapRepositoryFactory(ldapOperations); + } + + @Override + public void afterPropertiesSet() { + super.afterPropertiesSet(); + Assert.notNull(ldapOperations, "LdapOperations must be set"); + } +} diff --git a/src/main/java/org/springframework/data/ldap/repository/support/LdapSerializer.java b/src/main/java/org/springframework/data/ldap/repository/support/LdapSerializer.java new file mode 100644 index 0000000..9a49043 --- /dev/null +++ b/src/main/java/org/springframework/data/ldap/repository/support/LdapSerializer.java @@ -0,0 +1,124 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.support; + +import org.springframework.ldap.filter.AndFilter; +import org.springframework.ldap.filter.EqualsFilter; +import org.springframework.ldap.filter.Filter; +import org.springframework.ldap.filter.GreaterThanOrEqualsFilter; +import org.springframework.ldap.filter.LessThanOrEqualsFilter; +import org.springframework.ldap.filter.LikeFilter; +import org.springframework.ldap.filter.NotFilter; +import org.springframework.ldap.filter.OrFilter; +import org.springframework.ldap.filter.PresentFilter; +import org.springframework.ldap.odm.core.ObjectDirectoryMapper; + +import com.querydsl.core.types.*; + +/** + * Helper class for generating LDAP filters from QueryDSL Expressions. + * + * @author Mattias Hellborg Arthursson + * @author Eddu Melendez + * @since 2.0 + */ +class LdapSerializer implements Visitor { + + private final ObjectDirectoryMapper odm; + private final Class clazz; + + public LdapSerializer(ObjectDirectoryMapper odm, Class clazz) { + + this.odm = odm; + this.clazz = clazz; + } + + public Filter handle(Expression expression) { + return (Filter) expression.accept(this, null); + } + + @Override + public Object visit(Constant expr, Void context) { + return expr.getConstant().toString(); + } + + @Override + public Object visit(FactoryExpression expr, Void context) { + throw new UnsupportedOperationException(); + } + + @Override + public Object visit(Operation expr, Void context) { + + Operator operator = expr.getOperator(); + + if (operator == Ops.EQ) { + return new EqualsFilter(attribute(expr), value(expr)); + } else if (operator == Ops.AND) { + return new AndFilter().and(handle(expr.getArg(0))).and(handle(expr.getArg(1))); + } else if (operator == Ops.OR) { + return new OrFilter().or(handle(expr.getArg(0))).or(handle(expr.getArg(1))); + } else if (operator == Ops.NOT) { + return new NotFilter(handle(expr.getArg(0))); + } else if (operator == Ops.LIKE) { + return new LikeFilter(attribute(expr), value(expr)); + } else if (operator == Ops.STARTS_WITH || operator == Ops.STARTS_WITH_IC) { + return new LikeFilter(attribute(expr), value(expr) + "*"); + } else if (operator == Ops.ENDS_WITH || operator == Ops.ENDS_WITH_IC) { + return new LikeFilter(attribute(expr), "*" + value(expr)); + } else if (operator == Ops.STRING_CONTAINS || operator == Ops.STRING_CONTAINS_IC) { + return new LikeFilter(attribute(expr), "*" + value(expr) + "*"); + } else if (operator == Ops.IS_NOT_NULL) { + return new PresentFilter(attribute(expr)); + } else if (operator == Ops.IS_NULL) { + return new NotFilter(new PresentFilter(attribute(expr))); + } else if (operator == Ops.GOE) { + return new GreaterThanOrEqualsFilter(attribute(expr), value(expr)); + } else if (operator == Ops.LOE) { + return new LessThanOrEqualsFilter(attribute(expr), value(expr)); + } + + throw new UnsupportedOperationException("Unsupported operator " + operator.toString()); + } + + private String value(Operation expr) { + return (String) expr.getArg(1).accept(this, null); + } + + private String attribute(Operation expr) { + return odm.attributeFor(clazz, (String) expr.getArg(0).accept(this, null)); + } + + @Override + public Object visit(ParamExpression expr, Void context) { + throw new UnsupportedOperationException(); + } + + @Override + public Object visit(Path expr, Void context) { + return expr.getMetadata().getName(); + } + + @Override + public Object visit(SubQueryExpression expr, Void context) { + throw new UnsupportedOperationException(); + } + + @Override + public Object visit(TemplateExpression expr, Void context) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/org/springframework/data/ldap/repository/support/QueryDslLdapQuery.java b/src/main/java/org/springframework/data/ldap/repository/support/QueryDslLdapQuery.java new file mode 100644 index 0000000..f22555c --- /dev/null +++ b/src/main/java/org/springframework/data/ldap/repository/support/QueryDslLdapQuery.java @@ -0,0 +1,78 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.support; + +import static org.springframework.ldap.query.LdapQueryBuilder.*; + +import java.util.List; + +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.query.LdapQuery; + +import com.querydsl.core.DefaultQueryMetadata; +import com.querydsl.core.FilteredClause; +import com.querydsl.core.support.QueryMixin; +import com.querydsl.core.types.EntityPath; +import com.querydsl.core.types.Predicate; + +/** + * Spring LDAP specific {@link FilteredClause} implementation. + * + * @author Mattias Hellborg Arthursson + * @author Eddu Melendez + * @since 2.0 + */ +public class QueryDslLdapQuery implements FilteredClause> { + + private final LdapOperations ldapOperations; + private final Class clazz; + + private QueryMixin> queryMixin = new QueryMixin>(this, + new DefaultQueryMetadata().noValidate()); + + private final LdapSerializer filterGenerator; + + @SuppressWarnings("unchecked") + public QueryDslLdapQuery(LdapOperations ldapOperations, EntityPath entityPath) { + this(ldapOperations, (Class) entityPath.getType()); + } + + public QueryDslLdapQuery(LdapOperations ldapOperations, Class clazz) { + + this.ldapOperations = ldapOperations; + this.clazz = clazz; + this.filterGenerator = new LdapSerializer(ldapOperations.getObjectDirectoryMapper(), clazz); + } + + @Override + public QueryDslLdapQuery where(Predicate... o) { + return queryMixin.where(o); + } + + @SuppressWarnings("unchecked") + public List list() { + return (List) ldapOperations.find(buildQuery(), clazz); + } + + public K uniqueResult() { + return ldapOperations.findOne(buildQuery(), clazz); + } + + LdapQuery buildQuery() { + return query().filter(filterGenerator.handle(queryMixin.getMetadata().getWhere())); + } + +} diff --git a/src/main/java/org/springframework/data/ldap/repository/support/QueryDslLdapRepository.java b/src/main/java/org/springframework/data/ldap/repository/support/QueryDslLdapRepository.java new file mode 100644 index 0000000..fd3a8b1 --- /dev/null +++ b/src/main/java/org/springframework/data/ldap/repository/support/QueryDslLdapRepository.java @@ -0,0 +1,85 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.support; + +import java.util.List; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.querydsl.QueryDslPredicateExecutor; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.odm.core.ObjectDirectoryMapper; +import org.springframework.data.ldap.repository.support.QueryDslLdapQuery; +import org.springframework.data.ldap.repository.support.SimpleLdapRepository; + +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.Predicate; + +/** + * Base repository implementation for QueryDSL support. + * + * @author Mattias Hellborg Arthursson + * @author Eddu Melendez + * @since 2.0 + */ +public class QueryDslLdapRepository extends SimpleLdapRepository implements QueryDslPredicateExecutor { + + public QueryDslLdapRepository(LdapOperations ldapOperations, ObjectDirectoryMapper odm, Class clazz) { + super(ldapOperations, odm, clazz); + } + + @Override + public T findOne(Predicate predicate) { + return queryFor(predicate).uniqueResult(); + } + + @Override + public List findAll(Predicate predicate) { + return queryFor(predicate).list(); + } + + @Override + public long count(Predicate predicate) { + return findAll(predicate).size(); + } + + public boolean exists(Predicate predicate) { + return count(predicate) > 0; + } + + public Iterable findAll(Predicate predicate, Sort sort) { + throw new UnsupportedOperationException(); + } + + private QueryDslLdapQuery queryFor(Predicate predicate) { + return new QueryDslLdapQuery(getLdapOperations(), getClazz()).where(predicate); + } + + public Iterable findAll(OrderSpecifier... orders) { + throw new UnsupportedOperationException(); + } + + @Override + public Iterable findAll(Predicate predicate, OrderSpecifier... orders) { + throw new UnsupportedOperationException(); + } + + @Override + public Page findAll(Predicate predicate, Pageable pageable) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/org/springframework/data/ldap/repository/support/SimpleLdapRepository.java b/src/main/java/org/springframework/data/ldap/repository/support/SimpleLdapRepository.java new file mode 100644 index 0000000..152e19d --- /dev/null +++ b/src/main/java/org/springframework/data/ldap/repository/support/SimpleLdapRepository.java @@ -0,0 +1,242 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.support; + +import static org.springframework.ldap.query.LdapQueryBuilder.*; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import javax.naming.Name; + +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.data.domain.Persistable; +import org.springframework.ldap.NameNotFoundException; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.core.support.CountNameClassPairCallbackHandler; +import org.springframework.ldap.filter.Filter; +import org.springframework.ldap.odm.core.ObjectDirectoryMapper; +import org.springframework.ldap.query.LdapQuery; +import org.springframework.data.ldap.repository.LdapRepository; +import org.springframework.util.Assert; + +/** + * Base repository implementation for LDAP. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ +public class SimpleLdapRepository implements LdapRepository { + + private static final String OBJECTCLASS_ATTRIBUTE = "objectclass"; + private final LdapOperations ldapOperations; + private final ObjectDirectoryMapper odm; + private final Class clazz; + + public SimpleLdapRepository(LdapOperations ldapOperations, ObjectDirectoryMapper odm, Class clazz) { + + this.ldapOperations = ldapOperations; + this.odm = odm; + this.clazz = clazz; + } + + protected LdapOperations getLdapOperations() { + return ldapOperations; + } + + protected Class getClazz() { + return clazz; + } + + @Override + public long count() { + + Filter filter = odm.filterFor(clazz, null); + CountNameClassPairCallbackHandler callback = new CountNameClassPairCallbackHandler(); + LdapQuery query = query().attributes(OBJECTCLASS_ATTRIBUTE).filter(filter); + ldapOperations.search(query, callback); + + return callback.getNoOfRows(); + } + + private boolean isNew(S entity, Name id) { + + if (entity instanceof Persistable) { + Persistable persistable = (Persistable) entity; + return persistable.isNew(); + } else { + return id == null; + } + } + + @Override + public S save(S entity) { + + Assert.notNull(entity, "Entity must not be null"); + + Name declaredId = odm.getId(entity); + + if (isNew(entity, declaredId)) { + ldapOperations.create(entity); + } else { + ldapOperations.update(entity); + } + + return entity; + } + + @Override + public Iterable save(Iterable entities) { + + return new TransformingIterable(entities, new Function() { + @Override + public S transform(S entry) { + return save(entry); + } + }); + } + + @Override + public T findOne(Name name) { + + Assert.notNull(name, "Id must not be null"); + try { + return ldapOperations.findByDn(name, clazz); + } catch (NameNotFoundException e) { + return null; + } + } + + @Override + public List findAll(LdapQuery ldapQuery) { + + Assert.notNull(ldapQuery, "LdapQuery must not be null"); + return ldapOperations.find(ldapQuery, clazz); + } + + @Override + public T findOne(LdapQuery ldapQuery) { + Assert.notNull(ldapQuery, "LdapQuery must not be null"); + try { + return ldapOperations.findOne(ldapQuery, clazz); + } catch (EmptyResultDataAccessException e) { + return null; + } + } + + @Override + public boolean exists(Name name) { + + Assert.notNull(name, "Id must not be null"); + + return findOne(name) != null; + } + + @Override + public List findAll() { + return ldapOperations.findAll(clazz); + } + + @Override + public List findAll(final Iterable names) { + + Iterable found = new TransformingIterable(names, new Function() { + @Override + public T transform(Name name) { + return findOne(name); + } + }); + + LinkedList list = new LinkedList(); + for (T entry : found) { + if (entry != null) { + list.add(entry); + } + } + + return list; + } + + @Override + public void delete(Name name) { + + Assert.notNull(name, "Id must not be null"); + + ldapOperations.unbind(name); + } + + @Override + public void delete(T entity) { + + Assert.notNull(entity, "Entity must not be null"); + + ldapOperations.delete(entity); + } + + @Override + public void delete(Iterable entities) { + + for (T entity : entities) { + delete(entity); + } + } + + @Override + public void deleteAll() { + delete(findAll()); + } + + private static final class TransformingIterable implements Iterable { + + private final Iterable target; + private final Function function; + + private TransformingIterable(Iterable target, Function function) { + this.target = target; + this.function = function; + } + + @Override + public Iterator iterator() { + + final Iterator targetIterator = target.iterator(); + return new Iterator() { + + @Override + public boolean hasNext() { + return targetIterator.hasNext(); + } + + @Override + public T next() { + return function.transform(targetIterator.next()); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Remove is not supported for this iterator"); + } + }; + } + } + + private interface Function { + + T transform(F entry); + + } +} diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt new file mode 100644 index 0000000..6fefc77 --- /dev/null +++ b/src/main/resources/changelog.txt @@ -0,0 +1,10 @@ +Spring Data LDAP Changelog +============================= + +Changes in version 1.0.0.M1 LDAP (2016-11-11) +------------------------------------------------ + +Repository +* Introducing generic repository implementation for LDAP +* Automatic implementation of interface query method names on repositories. + diff --git a/src/main/resources/license.txt b/src/main/resources/license.txt new file mode 100644 index 0000000..7584e2d --- /dev/null +++ b/src/main/resources/license.txt @@ -0,0 +1,216 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +======================================================================= + +To the extent any open source subcomponents are licensed under the EPL and/or other +similar licenses that require the source code and/or modifications to +source code to be made available (as would be noted above), you may obtain a +copy of the source code corresponding to the binaries for such open source +components and modifications thereto, if any, (the "Source Files"), by +downloading the Source Files from http://www.springsource.org/download, +or by sending a request, with your name and address to: VMware, Inc., 3401 Hillview +Avenue, Palo Alto, CA 94304, United States of America or email info@vmware.com. All +such requests should clearly specify: OPEN SOURCE FILES REQUEST, Attention General +Counsel. VMware shall mail a copy of the Source Files to you on a CD or equivalent +physical medium. This offer to obtain a copy of the Source Files is valid for three +years from the date you acquired this Software product. \ No newline at end of file diff --git a/src/main/resources/notice.txt b/src/main/resources/notice.txt new file mode 100644 index 0000000..5746246 --- /dev/null +++ b/src/main/resources/notice.txt @@ -0,0 +1,10 @@ +Spring Data LDAP 1.0 M1 +Copyright (c) [2010-2015] Pivotal Software, Inc. + +This product is licensed to you under the Apache License, Version 2.0 (the "License"). +You may not use this product except in compliance with the License. + +This product may include a number of subcomponents with +separate copyright notices and license terms. Your use of the source +code for the these subcomponents is subject to the terms and +conditions of the subcomponent's license, as noted in the LICENSE file. diff --git a/src/test/java/org/springframework/data/ldap/config/DummyEntity.java b/src/test/java/org/springframework/data/ldap/config/DummyEntity.java new file mode 100644 index 0000000..96fc742 --- /dev/null +++ b/src/test/java/org/springframework/data/ldap/config/DummyEntity.java @@ -0,0 +1,22 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.config; + +/** + * @author Mattias Hellborg Arthursson + */ +public class DummyEntity { +} diff --git a/src/test/java/org/springframework/data/ldap/config/DummyLdapRepository.java b/src/test/java/org/springframework/data/ldap/config/DummyLdapRepository.java new file mode 100644 index 0000000..969e753 --- /dev/null +++ b/src/test/java/org/springframework/data/ldap/config/DummyLdapRepository.java @@ -0,0 +1,27 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.ldap.config; + +import javax.naming.Name; + +import org.springframework.data.repository.CrudRepository; + +/** + * @author Mattias Hellborg Arthursson + */ +public interface DummyLdapRepository extends CrudRepository { +} diff --git a/src/test/java/org/springframework/data/ldap/repository/SimpleLdapRepositoryTest.java b/src/test/java/org/springframework/data/ldap/repository/SimpleLdapRepositoryTest.java new file mode 100644 index 0000000..ebfb6c9 --- /dev/null +++ b/src/test/java/org/springframework/data/ldap/repository/SimpleLdapRepositoryTest.java @@ -0,0 +1,212 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.ldap.repository; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.*; + +import java.util.Arrays; +import java.util.Iterator; + +import javax.naming.Name; +import javax.naming.ldap.LdapName; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.data.domain.Persistable; +import org.springframework.ldap.NameNotFoundException; +import org.springframework.ldap.core.LdapOperations; +import org.springframework.ldap.core.support.CountNameClassPairCallbackHandler; +import org.springframework.ldap.filter.Filter; +import org.springframework.ldap.odm.core.ObjectDirectoryMapper; +import org.springframework.ldap.query.LdapQuery; +import org.springframework.data.ldap.repository.support.SimpleLdapRepository; +import org.springframework.ldap.support.LdapUtils; + +/** + * @author Mattias Hellborg Arthursson + */ +public class SimpleLdapRepositoryTest { + + private LdapOperations ldapOperationsMock; + private ObjectDirectoryMapper odmMock; + private SimpleLdapRepository tested; + + @Before + public void prepareTestedInstance() { + ldapOperationsMock = mock(LdapOperations.class); + odmMock = mock(ObjectDirectoryMapper.class); + tested = new SimpleLdapRepository(ldapOperationsMock, odmMock, Object.class); + } + + @Test + public void testCount() { + Filter filterMock = mock(Filter.class); + when(odmMock.filterFor(Object.class, null)).thenReturn(filterMock); + ArgumentCaptor ldapQuery = ArgumentCaptor.forClass(LdapQuery.class); + doNothing().when(ldapOperationsMock).search(ldapQuery.capture(), any(CountNameClassPairCallbackHandler.class)); + + long count = tested.count(); + + assertThat(count).isEqualTo(0); + LdapQuery query = ldapQuery.getValue(); + assertThat(query.filter()).isEqualTo(filterMock); + assertThat(query.attributes()).isEqualTo(new String[]{"objectclass"}); + } + + @Test + public void testSaveNonPersistableWithIdSet() { + Object expectedEntity = new Object(); + + when(odmMock.getId(expectedEntity)).thenReturn(LdapUtils.emptyLdapName()); + when(odmMock.getCalculatedId(expectedEntity)).thenReturn(null); + + tested.save(expectedEntity); + + verify(ldapOperationsMock).update(expectedEntity); + } + + @Test + public void testSaveNonPersistableWithIdChanged() { + Object expectedEntity = new Object(); + LdapName expectedName = LdapUtils.newLdapName("ou=newlocation"); + + when(odmMock.getId(expectedEntity)).thenReturn(LdapUtils.emptyLdapName()); + when(odmMock.getCalculatedId(expectedEntity)).thenReturn(expectedName); + + tested.save(expectedEntity); + + verify(ldapOperationsMock).update(expectedEntity); + } + + @Test + public void testSaveNonPersistableWithNoIdCalculatedId() { + Object expectedEntity = new Object(); + LdapName expectedName = LdapUtils.emptyLdapName(); + + when(odmMock.getId(expectedEntity)).thenReturn(null); + when(odmMock.getCalculatedId(expectedEntity)).thenReturn(expectedName); + + tested.save(expectedEntity); + + verify(ldapOperationsMock).create(expectedEntity); + } + + @Test + public void testSavePersistableNewWithDeclaredId() { + Persistable expectedEntity = mock(Persistable.class); + + when(expectedEntity.isNew()).thenReturn(true); + when(odmMock.getId(expectedEntity)).thenReturn(LdapUtils.emptyLdapName()); + when(odmMock.getCalculatedId(expectedEntity)).thenReturn(null); + + tested.save(expectedEntity); + + verify(ldapOperationsMock).create(expectedEntity); + } + + @Test + public void testSavePersistableNewWithCalculatedId() { + Persistable expectedEntity = mock(Persistable.class); + LdapName expectedName = LdapUtils.emptyLdapName(); + + when(expectedEntity.isNew()).thenReturn(true); + when(odmMock.getId(expectedEntity)).thenReturn(null); + when(odmMock.getCalculatedId(expectedEntity)).thenReturn(expectedName); + + tested.save(expectedEntity); + + verify(ldapOperationsMock).create(expectedEntity); + } + + @Test + public void testSavePersistableNotNew() { + Persistable expectedEntity = mock(Persistable.class); + + when(expectedEntity.isNew()).thenReturn(false); + when(odmMock.getId(expectedEntity)).thenReturn(LdapUtils.emptyLdapName()); + when(odmMock.getCalculatedId(expectedEntity)).thenReturn(null); + + tested.save(expectedEntity); + + verify(ldapOperationsMock).update(expectedEntity); + } + + @Test + public void testFindOneWithName() { + LdapName expectedName = LdapUtils.emptyLdapName(); + Object expectedResult = new Object(); + + when(ldapOperationsMock.findByDn(expectedName, Object.class)).thenReturn(expectedResult); + + Object actualResult = tested.findOne(expectedName); + + assertThat(actualResult).isSameAs(expectedResult); + } + + @Test + public void verifyThatNameNotFoundInFindOneWithNameReturnsNull() { + LdapName expectedName = LdapUtils.emptyLdapName(); + + when(ldapOperationsMock.findByDn(expectedName, Object.class)).thenThrow(new NameNotFoundException("")); + + Object actualResult = tested.findOne(expectedName); + + assertThat(actualResult).isNull(); + } + + @Test + public void testFindAll() { + Name expectedName1 = LdapUtils.newLdapName("ou=aa"); + Name expectedName2 = LdapUtils.newLdapName("ou=bb"); + + Object expectedResult1 = new Object(); + Object expectedResult2 = new Object(); + + when(ldapOperationsMock.findByDn(expectedName1, Object.class)).thenReturn(expectedResult1); + when(ldapOperationsMock.findByDn(expectedName2, Object.class)).thenReturn(expectedResult2); + + Iterable actualResult = tested.findAll(Arrays.asList(expectedName1, expectedName2)); + + Iterator iterator = actualResult.iterator(); + assertThat(iterator.next()).isSameAs(expectedResult1); + assertThat(iterator.next()).isSameAs(expectedResult2); + + assertThat(iterator.hasNext()).isFalse(); + } + + @Test + public void testFindAllWhereOneEntryIsNotFound() { + Name expectedName1 = LdapUtils.newLdapName("ou=aa"); + Name expectedName2 = LdapUtils.newLdapName("ou=bb"); + + Object expectedResult2 = new Object(); + + when(ldapOperationsMock.findByDn(expectedName1, Object.class)).thenReturn(null); + when(ldapOperationsMock.findByDn(expectedName2, Object.class)).thenReturn(expectedResult2); + + Iterable actualResult = tested.findAll(Arrays.asList(expectedName1, expectedName2)); + + Iterator iterator = actualResult.iterator(); + assertThat(iterator.next()).isSameAs(expectedResult2); + + assertThat(iterator.hasNext()).isFalse(); + } + +} diff --git a/src/test/java/org/springframework/data/ldap/repository/config/AnnotationConfigTests.java b/src/test/java/org/springframework/data/ldap/repository/config/AnnotationConfigTests.java new file mode 100644 index 0000000..5977996 --- /dev/null +++ b/src/test/java/org/springframework/data/ldap/repository/config/AnnotationConfigTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.config; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportResource; +import org.springframework.data.ldap.config.DummyLdapRepository; +import org.springframework.data.ldap.repository.config.AnnotationConfigTests.Config; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @author Mattias Hellborg Arthursson + * @author Mark Paluch + */ +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = Config.class) +public class AnnotationConfigTests { + + @Autowired ApplicationContext context; + + @Test + public void testAnnotationConfig() { + + DummyLdapRepository repository = context.getBean(DummyLdapRepository.class); + assertThat(repository).isNotNull(); + } + + @Configuration + @ImportResource("classpath:/ldap-annotation-config.xml") + @EnableLdapRepositories(basePackageClasses = DummyLdapRepository.class) + static class Config {} +} diff --git a/src/test/java/org/springframework/data/ldap/repository/config/SpringLdapConfiguration.java b/src/test/java/org/springframework/data/ldap/repository/config/SpringLdapConfiguration.java new file mode 100644 index 0000000..43e4011 --- /dev/null +++ b/src/test/java/org/springframework/data/ldap/repository/config/SpringLdapConfiguration.java @@ -0,0 +1,25 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.config; + +import org.springframework.context.annotation.Configuration; + +/** + * @author Mattias Hellborg Arthursson + */ +@Configuration +@EnableLdapRepositories(basePackages = "org.springframework.ldap.config") +public class SpringLdapConfiguration {} diff --git a/src/test/java/org/springframework/data/ldap/repository/query/BaseTestPersonRepository.java b/src/test/java/org/springframework/data/ldap/repository/query/BaseTestPersonRepository.java new file mode 100644 index 0000000..58005b3 --- /dev/null +++ b/src/test/java/org/springframework/data/ldap/repository/query/BaseTestPersonRepository.java @@ -0,0 +1,30 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.query; + +import java.util.List; + +import org.springframework.data.ldap.repository.LdapRepository; +import org.springframework.data.ldap.repository.support.BaseUnitTestPerson; +import org.springframework.data.ldap.repository.support.UnitTestPerson; + +/** + * @author Rob Winch + */ +public interface BaseTestPersonRepository extends LdapRepository { + + List findByFullName(String name); +} diff --git a/src/test/java/org/springframework/data/ldap/repository/query/PartTreeLdapRepositoryQueryTest.java b/src/test/java/org/springframework/data/ldap/repository/query/PartTreeLdapRepositoryQueryTest.java new file mode 100644 index 0000000..550da9e --- /dev/null +++ b/src/test/java/org/springframework/data/ldap/repository/query/PartTreeLdapRepositoryQueryTest.java @@ -0,0 +1,147 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.query; + +import static org.assertj.core.api.Assertions.*; + +import java.lang.reflect.Method; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.ldap.repository.support.BaseUnitTestPerson; +import org.springframework.data.ldap.repository.support.UnitTestPerson; +import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.projection.SpelAwareProxyProjectionFactory; +import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.query.LdapQuery; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; + +/** + * @author Mattias Hellborg Arthursson + * @author Eddu Melendez + */ +@ContextConfiguration("classpath:/query-test.xml") +public class PartTreeLdapRepositoryQueryTest extends AbstractJUnit4SpringContextTests { + + @Autowired private LdapTemplate ldapTemplate; + private Class targetClass; + private Class entityClass; + private DefaultRepositoryMetadata repositoryMetadata; + private ProjectionFactory factory; + + @Before + public void prepareTest() { + entityClass = UnitTestPerson.class; + targetClass = UnitTestPersonRepository.class; + repositoryMetadata = new DefaultRepositoryMetadata(targetClass); + factory = new SpelAwareProxyProjectionFactory(); + } + + @Test + public void testFindByFullName() throws NoSuchMethodException { + assertFilterForMethod(targetClass.getMethod("findByFullName", String.class), "(cn=John Doe)", "John Doe"); + } + + // LDAP-314 + @Test + public void testFindByFullNameWithBase() throws NoSuchMethodException { + entityClass = BaseUnitTestPerson.class; + targetClass = BaseTestPersonRepository.class; + repositoryMetadata = new DefaultRepositoryMetadata(targetClass); + assertFilterAndBaseForMethod(targetClass.getMethod("findByFullName", String.class), "(cn=John Doe)", "ou=someOu", + "John Doe"); + } + + @Test + public void testFindByFullNameLike() throws NoSuchMethodException { + assertFilterForMethod(targetClass.getMethod("findByFullNameLike", String.class), "(cn=*John*)", "*John*"); + } + + @Test + public void testFindByFullNameStartsWith() throws NoSuchMethodException { + assertFilterForMethod(targetClass.getMethod("findByFullNameStartsWith", String.class), "(cn=John*)", "John"); + } + + @Test + public void testFindByFullNameEndsWith() throws NoSuchMethodException { + assertFilterForMethod(targetClass.getMethod("findByFullNameEndsWith", String.class), "(cn=*John)", "John"); + } + + @Test + public void testFindByFullNameContains() throws NoSuchMethodException { + assertFilterForMethod(targetClass.getMethod("findByFullNameContains", String.class), "(cn=*John*)", "John"); + } + + @Test + public void testFindByFullNameGreaterThanEqual() throws NoSuchMethodException { + assertFilterForMethod(targetClass.getMethod("findByFullNameGreaterThanEqual", String.class), "(cn>=John)", "John"); + } + + @Test + public void testFindByFullNameLessThanEqual() throws NoSuchMethodException { + assertFilterForMethod(targetClass.getMethod("findByFullNameLessThanEqual", String.class), "(cn<=John)", "John"); + } + + @Test + public void testFindByFullNameIsNotNull() throws NoSuchMethodException { + assertFilterForMethod(targetClass.getMethod("findByFullNameIsNotNull"), "(cn=*)"); + } + + @Test + public void testFindByFullNameIsNull() throws NoSuchMethodException { + assertFilterForMethod(targetClass.getMethod("findByFullNameIsNull"), "(!(cn=*))"); + } + + @Test + public void testFindByFullNameNot() throws NoSuchMethodException { + assertFilterForMethod(targetClass.getMethod("findByFullNameNot", String.class), "(!(cn=John Doe))", "John Doe"); + } + + @Test + public void testFindByFullNameNotLike() throws NoSuchMethodException { + assertFilterForMethod(targetClass.getMethod("findByFullNameNotLike", String.class), "(!(cn=*John*))", "*John*"); + } + + @Test + public void testFindByFullNameAndLastName() throws NoSuchMethodException { + assertFilterForMethod(targetClass.getMethod("findByFullNameAndLastName", String.class, String.class), + "(&(cn=John Doe)(sn=Doe))", "John Doe", "Doe"); + } + + @Test + public void testFindByFullNameAndLastNameNot() throws NoSuchMethodException { + assertFilterForMethod(targetClass.getMethod("findByFullNameAndLastNameNot", String.class, String.class), + "(&(cn=John Doe)(!(sn=Doe)))", "John Doe", "Doe"); + } + + private void assertFilterForMethod(Method targetMethod, String expectedFilter, Object... expectedParams) { + assertFilterAndBaseForMethod(targetMethod, expectedFilter, "", expectedParams); + } + + private void assertFilterAndBaseForMethod(Method targetMethod, String expectedFilter, String expectedBase, + Object... expectedParams) { + LdapQueryMethod queryMethod = new LdapQueryMethod(targetMethod, repositoryMetadata, factory); + PartTreeLdapRepositoryQuery tested = new PartTreeLdapRepositoryQuery(queryMethod, entityClass, ldapTemplate); + + LdapQuery query = tested.createQuery(expectedParams); + String base = query.base().toString(); + assertThat(base).isEqualTo(expectedBase); + assertThat(query.filter().encode()).isEqualTo(expectedFilter); + } +} diff --git a/src/test/java/org/springframework/data/ldap/repository/query/UnitTestPersonRepository.java b/src/test/java/org/springframework/data/ldap/repository/query/UnitTestPersonRepository.java new file mode 100644 index 0000000..93f6fe4 --- /dev/null +++ b/src/test/java/org/springframework/data/ldap/repository/query/UnitTestPersonRepository.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.query; + +import java.util.List; + +import org.springframework.data.ldap.repository.LdapRepository; +import org.springframework.data.ldap.repository.support.UnitTestPerson; + +/** + * @author Mattias Hellborg Arthursson + */ +public interface UnitTestPersonRepository extends LdapRepository { + + List findByFullName(String name); + + List findByFullNameNot(String name); + + List findByFullNameLike(String name); + + List findByFullNameNotLike(String name); + + List findByFullNameStartsWith(String name); + + List findByFullNameEndsWith(String name); + + List findByFullNameContains(String name); + + List findByFullNameGreaterThanEqual(String name); + + List findByFullNameLessThanEqual(String name); + + List findByFullNameIsNotNull(); + + List findByFullNameIsNull(); + + List findByFullNameAndLastName(String fullName, String lastName); + + List findByFullNameAndLastNameNot(String fullName, String lastName); + +} diff --git a/src/test/java/org/springframework/data/ldap/repository/support/BaseUnitTestPerson.java b/src/test/java/org/springframework/data/ldap/repository/support/BaseUnitTestPerson.java new file mode 100644 index 0000000..9f87cdf --- /dev/null +++ b/src/test/java/org/springframework/data/ldap/repository/support/BaseUnitTestPerson.java @@ -0,0 +1,49 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.support; + +import java.util.List; + +import javax.naming.Name; + +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; + +/** + * @author Rob Winch + */ +@Entry(base = "ou=someOu", objectClasses = { "inetOrgPerson", "organizationalPerson", "person", "top" }) +public class BaseUnitTestPerson { + + @Id private Name dn; + + @Attribute(name = "cn") @DnAttribute("cn") private String fullName; + + @Attribute(name = "sn") private String lastName; + + @Attribute(name = "description") private List description; + + @Transient @DnAttribute("c") private String country; + + @Transient @DnAttribute("ou") private String company; + + // This should be automatically found + private String telephoneNumber; + +} diff --git a/src/test/java/org/springframework/data/ldap/repository/support/QPerson.java b/src/test/java/org/springframework/data/ldap/repository/support/QPerson.java new file mode 100644 index 0000000..b4257e9 --- /dev/null +++ b/src/test/java/org/springframework/data/ldap/repository/support/QPerson.java @@ -0,0 +1,58 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.support; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import javax.annotation.Generated; + +import com.querydsl.core.types.Path; +import com.querydsl.core.types.PathMetadata; +import com.querydsl.core.types.dsl.EntityPathBase; +import com.querydsl.core.types.dsl.ListPath; +import com.querydsl.core.types.dsl.PathInits; +import com.querydsl.core.types.dsl.StringPath; + +/** + * QPerson is a Querydsl query type for Person + */ +@Generated("com.mysema.query.codegen.EntitySerializer") +public class QPerson extends EntityPathBase { + + private static final long serialVersionUID = -1526737794; + + public static final QPerson person = new QPerson("person"); + + public final StringPath fullName = createString("fullName"); + + public final ListPath description = this. createList("description", + String.class, StringPath.class, PathInits.DIRECT2); + + public final StringPath lastName = createString("lastName"); + + public QPerson(String variable) { + super(UnitTestPerson.class, forVariable(variable)); + } + + public QPerson(Path path) { + super(path.getType(), path.getMetadata()); + } + + public QPerson(PathMetadata metadata) { + super(UnitTestPerson.class, metadata); + } + +} diff --git a/src/test/java/org/springframework/data/ldap/repository/support/QueryDslFilterGeneratorTest.java b/src/test/java/org/springframework/data/ldap/repository/support/QueryDslFilterGeneratorTest.java new file mode 100644 index 0000000..f9278af --- /dev/null +++ b/src/test/java/org/springframework/data/ldap/repository/support/QueryDslFilterGeneratorTest.java @@ -0,0 +1,122 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.ldap.repository.support; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.ldap.filter.Filter; +import org.springframework.ldap.odm.core.ObjectDirectoryMapper; +import org.springframework.ldap.odm.core.impl.DefaultObjectDirectoryMapper; + +import com.querydsl.core.types.Expression; + +/** + * @author Mattias Hellborg Arthursson + * @author Eddu Melendez + */ +public class QueryDslFilterGeneratorTest { + + private LdapSerializer tested; + private QPerson person; + + @Before + public void prepareTestedInstance() { + ObjectDirectoryMapper odm = new DefaultObjectDirectoryMapper(); + tested = new LdapSerializer(odm, UnitTestPerson.class); + person = QPerson.person; + } + + @Test + public void testEqualsFilter() { + Expression expression = person.fullName.eq("John Doe"); + Filter result = tested.handle(expression); + assertThat(result.toString()).isEqualTo("(cn=John Doe)"); + } + + @Test + public void testAndFilter() { + Expression expression = person.fullName.eq("John Doe").and(person.lastName.eq("Doe")); + Filter result = tested.handle(expression); + assertThat(result.toString()).isEqualTo("(&(cn=John Doe)(sn=Doe))"); + } + + @Test + public void testOrFilter() { + Expression expression = person.fullName.eq("John Doe").or(person.lastName.eq("Doe")); + Filter result = tested.handle(expression); + assertThat(result.toString()).isEqualTo("(|(cn=John Doe)(sn=Doe))"); + } + + @Test + public void testOr() { + Expression expression = person.fullName.eq("John Doe") + .and(person.lastName.eq("Doe").or(person.lastName.eq("Die"))); + + Filter result = tested.handle(expression); + assertThat(result.toString()).isEqualTo("(&(cn=John Doe)(|(sn=Doe)(sn=Die)))"); + } + + @Test + public void testNot() { + Expression expression = person.fullName.eq("John Doe").not(); + Filter result = tested.handle(expression); + assertThat(result.toString()).isEqualTo("(!(cn=John Doe))"); + } + + @Test + public void testIsLike() { + Expression expression = person.fullName.like("kalle*"); + Filter result = tested.handle(expression); + assertThat(result.toString()).isEqualTo("(cn=kalle*)"); + } + + @Test + public void testStartsWith() { + Expression expression = person.fullName.startsWith("kalle"); + Filter result = tested.handle(expression); + assertThat(result.toString()).isEqualTo("(cn=kalle*)"); + } + + @Test + public void testEndsWith() { + Expression expression = person.fullName.endsWith("kalle"); + Filter result = tested.handle(expression); + assertThat(result.toString()).isEqualTo("(cn=*kalle)"); + } + + @Test + public void testContains() { + Expression expression = person.fullName.contains("kalle"); + Filter result = tested.handle(expression); + assertThat(result.toString()).isEqualTo("(cn=*kalle*)"); + } + + @Test + public void testNotNull() { + Expression expression = person.fullName.isNotNull(); + Filter result = tested.handle(expression); + assertThat(result.toString()).isEqualTo("(cn=*)"); + } + + @Test + public void testNull() { + Expression expression = person.fullName.isNull(); + Filter result = tested.handle(expression); + assertThat(result.toString()).isEqualTo("(!(cn=*))"); + } +} diff --git a/src/test/java/org/springframework/data/ldap/repository/support/UnitTestPerson.java b/src/test/java/org/springframework/data/ldap/repository/support/UnitTestPerson.java new file mode 100644 index 0000000..16ca968 --- /dev/null +++ b/src/test/java/org/springframework/data/ldap/repository/support/UnitTestPerson.java @@ -0,0 +1,58 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.ldap.repository.support; + +import java.util.List; + +import javax.naming.Name; + +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; + +/** + * @author Mattias Hellborg Arthursson + */ +@Entry(objectClasses = {"inetOrgPerson", "organizationalPerson", "person", "top"}) +public class UnitTestPerson { + @Id + private Name dn; + + @Attribute(name = "cn") + @DnAttribute("cn") + private String fullName; + + @Attribute(name = "sn") + private String lastName; + + @Attribute(name = "description") + private List description; + + @Transient + @DnAttribute("c") + private String country; + + @Transient + @DnAttribute("ou") + private String company; + + // This should be automatically found + private String telephoneNumber; + +} diff --git a/src/test/resources/ldap-annotation-config.xml b/src/test/resources/ldap-annotation-config.xml new file mode 100644 index 0000000..e1f92f3 --- /dev/null +++ b/src/test/resources/ldap-annotation-config.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/query-test.xml b/src/test/resources/query-test.xml new file mode 100644 index 0000000..7302e56 --- /dev/null +++ b/src/test/resources/query-test.xml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file