From 6876e179f39971b12ab0f3b7f2199ffde0727f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Basl=C3=A9?= Date: Fri, 15 Jan 2016 18:35:04 +0100 Subject: [PATCH] DATACOUCH-190 - Document SpEL support example. --- .../couchbase/repository/spel/SpelConfig.java | 43 ++++++++++++ .../repository/spel/SpelRepository.java | 16 +++++ .../repository/spel/SpelRepositoryTests.java | 69 +++++++++++++++++++ src/main/asciidoc/repository.adoc | 41 +++++++++++ 4 files changed, 169 insertions(+) create mode 100644 src/integration/java/org/springframework/data/couchbase/repository/spel/SpelConfig.java create mode 100644 src/integration/java/org/springframework/data/couchbase/repository/spel/SpelRepository.java create mode 100644 src/integration/java/org/springframework/data/couchbase/repository/spel/SpelRepositoryTests.java diff --git a/src/integration/java/org/springframework/data/couchbase/repository/spel/SpelConfig.java b/src/integration/java/org/springframework/data/couchbase/repository/spel/SpelConfig.java new file mode 100644 index 00000000..4838b22f --- /dev/null +++ b/src/integration/java/org/springframework/data/couchbase/repository/spel/SpelConfig.java @@ -0,0 +1,43 @@ +package org.springframework.data.couchbase.repository.spel; + +import java.util.Collections; +import java.util.Map; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.couchbase.IntegrationTestApplicationConfig; +import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; +import org.springframework.data.repository.query.EvaluationContextProvider; +import org.springframework.data.repository.query.ExtensionAwareEvaluationContextProvider; +import org.springframework.data.repository.query.spi.EvaluationContextExtension; +import org.springframework.data.repository.query.spi.EvaluationContextExtensionSupport; + +@Configuration +@EnableCouchbaseRepositories +public class SpelConfig extends IntegrationTestApplicationConfig { + + @Bean + public EvaluationContextExtension customSpelExtension() { + return new CustomSpelExtension(); + } + + public static class CustomSpelExtension extends EvaluationContextExtensionSupport { + + /** + * Returns the identifier of the extension. The id can be leveraged by users to fully qualify property lookups and + * thus overcome ambiguities in case multiple extensions expose properties with the same name. + * + * @return the extension id, must not be {@literal null}. + */ + @Override + public String getExtensionId() { + return "custom"; + } + + @Override + public Map getProperties() { + return Collections.singletonMap("oneCustomer", "uname-3"); + } + } + +} diff --git a/src/integration/java/org/springframework/data/couchbase/repository/spel/SpelRepository.java b/src/integration/java/org/springframework/data/couchbase/repository/spel/SpelRepository.java new file mode 100644 index 00000000..cbc9f307 --- /dev/null +++ b/src/integration/java/org/springframework/data/couchbase/repository/spel/SpelRepository.java @@ -0,0 +1,16 @@ +package org.springframework.data.couchbase.repository.spel; + +import java.util.List; + +import org.springframework.data.couchbase.core.query.N1qlPrimaryIndexed; +import org.springframework.data.couchbase.core.query.Query; +import org.springframework.data.couchbase.repository.User; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +@N1qlPrimaryIndexed +public interface SpelRepository extends CrudRepository { + + @Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND username = \"#{oneCustomer}\"") + List findCustomUsers(); +} diff --git a/src/integration/java/org/springframework/data/couchbase/repository/spel/SpelRepositoryTests.java b/src/integration/java/org/springframework/data/couchbase/repository/spel/SpelRepositoryTests.java new file mode 100644 index 00000000..87e2e3a8 --- /dev/null +++ b/src/integration/java/org/springframework/data/couchbase/repository/spel/SpelRepositoryTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2013 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.couchbase.repository.spel; + +import static org.junit.Assert.*; + +import java.util.List; + +import com.couchbase.client.java.Bucket; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.couchbase.repository.SimpleCouchbaseRepositoryListener; +import org.springframework.data.couchbase.repository.User; +import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping; +import org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory; +import org.springframework.data.couchbase.repository.support.IndexManager; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.data.repository.query.EvaluationContextProvider; +import org.springframework.data.repository.query.spi.EvaluationContextExtension; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Simon Baslé + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = SpelConfig.class) +@TestExecutionListeners(SimpleCouchbaseRepositoryListener.class) +public class SpelRepositoryTests { + + @Autowired + private Bucket client; + + @Autowired + private RepositoryOperationsMapping operationsMapping; + + @Autowired + private IndexManager indexManager; + + @Autowired + private SpelRepository repository; + + @Test + public void testSpelExtensionResolved() { + List users = repository.findCustomUsers(); + assertEquals(1, users.size()); + assertEquals("testuser-3", users.get(0).getKey()); + assertEquals("uname-3", users.get(0).getUsername()); + } + +} diff --git a/src/main/asciidoc/repository.adoc b/src/main/asciidoc/repository.adoc index b2350f35..27988fb6 100644 --- a/src/main/asciidoc/repository.adoc +++ b/src/main/asciidoc/repository.adoc @@ -146,6 +146,47 @@ A few N1QL-specific values are provided through SpEL: Another example: "`#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND test = $1`", which is equivalent to `SELECT #{#n1ql.fields} FROM #{#n1ql.bucket} WHERE #{#n1ql.filter} AND test = $1`". +.A practical application of SpEL with Spring Security +**** +SpEL can be useful when you want to do a query depending on data injected by other Spring components, like Spring Security. +Here is what you need to do to extend the SpEL context to get access to such external data. + +First, you need to implement an `EvaluationContextExtension` (use the support class as below): +[source,java] +---- +class SecurityEvaluationContextExtension extends EvaluationContextExtensionSupport { + + @Override + public String getExtensionId() { + return "security"; + } + + @Override + public SecurityExpressionRoot getRootObject() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + return new SecurityExpressionRoot(authentication) {}; + } +} +---- + +Then all you need to do for Spring Data Couchbase to be able to access associated SpEL values is to declare a corresponding bean in your configuration: +[source,java] +---- +@Bean +EvaluationContextExtension securityExtension() { + return new SecurityEvaluationContextExtension(); +} +---- + +This could be useful to craft a query according to the role of the connected user for instance: +[source,java] +---- +@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} AND " + +"role = '?#{hasRole('ROLE_ADMIN') ? 'public_admin' : 'admin'}'") +List findAllAdmins(); //only ROLE_ADMIN users will see hidden admins +---- +**** + The second method uses Spring-Data's query derivation mechanism to build a N1QL query from the method name and parameters. This will produce a query looking like this: `SELECT ... FROM ... WHERE firstName = "valueOfFnameAtRuntime"`. You can combine these criteria, even do a count with a name like `countByFirstname` or a limit with a name like `findFirst3ByLastname`... NOTE: Actually the generated N1QL query will also contain an additional N1QL criteria in order to only select documents that match the repository's entity class.