Add custom database session variable to Spring Boot JPA queries? - postgresql

I am trying to set SET SESSION encrypt.key='some_key' to database queries or connection.
Thing is I have following column definition in my model class
#ColumnTransformer(forColumn = "first_name",
read = "pgp_sym_decrypt(first_name, current_setting('encrypt.key'))",
write = "pgp_sym_encrypt(?, current_setting('encrypt.key'))")
#Column(name = "first_name", columnDefinition = "bytea")
private String firstName;
Above works when we set encrypt.key in postgres.conf file directly but out requirement is to have encrypt.key configurable from our spring properties file.
Things I tried.
AttributeConverter annotation with custom Converter class which only works with JPA, and LIKE operations are not supported.
I tried ContextEventListener where I executed SET SESSION query at application startup but that only works for few requests
Next I tried CustomTransactionManager extends JpaTransactionManager where I was doing following
#Override
protected void prepareSynchronization(DefaultTransactionStatus status,TransactionDefinition definition) {
super.prepareSynchronization(status, definition);
if (status.isNewTransaction()) {
final String query = "SET encrypt.key='" + encryptKey + "'";
entityManager.createNativeQuery(query).executeUpdate();
}
log.info("Encrypt Key : {}", entityManager.createNativeQuery("SElECT current_setting('encrypt.key')").getSingleResult());
}
}
Above does not work when I call normal JPA Repository methods and encrypt.key is not set as the CustomTransactionManager class in not called.
Any guidance in right direction would help me a lot

Since I created CustomTransactionManager extends JpaTransactionManager
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Primary;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.DefaultTransactionStatus;
import javax.persistence.EntityManager;
#Component
#Slf4j
#Primary
#Qualifier(value = "transactionManager")
public class CustomTransactionManager extends JpaTransactionManager {
#Autowired
private EntityManager entityManager;
#Value("${database.encryption.key}")
private String encryptKey;
#Override
protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
super.prepareSynchronization(status, definition);
if (status.isNewTransaction()) {
final String query = "SET SESSION encrypt.key='" + encryptKey + "'";
entityManager.createNativeQuery(query).executeUpdate();
}
}
}
Above was not getting called when I used normal JPA Repository methods.
For example,
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByFirstName(String firstName);
}
Adding #Transactional on Repository class did override framework logic where a shared transaction was getting created behind-the-scenes for all repository beans. This resulted in my CustomTransactionManager to be called even with repository methods.
I initially thought that adding Transactional annotation was overkill but found out that it gets created automatically at framework level as well so manually adding it had no additional footprint on its own but code/query you write inside CustomTransactionManager class will add required request footprint.
So I ended up adding #Transactional annotation on all repository classes whose domain(table) had encrypted columns.
For my use-case, this was the most flexible solution to have column level encryption on Azure postgres datbase service with Spring boot because we can not add custom environment variables there from Azure Portal, and directly adding to postgres.conf file also not possible due it being a SAAS service.

Related

Postgres similarity function with spring data

I tried to implement a search query in my spring-boot service which utilizes the similarity(text, text) function of postgres.
I got the similarity working in the postgres console, and managed to get it over to my #Repository interface as native query.
It seems to construct the query correctly, but every time I try to execute the query I get
ERROR: function similarity(text, character varying) does not exist
When I try to create the extension again, I get an exception, that this extension is already installed.
What am I missing? Do I need some Spring/JPA magic Object to enable this?
Example entity:
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.Data;
#Entity
#Table(name = "example")
#Data
public class ExampleEntity {
#Id
private String id;
private String textField;
}
Example repository:
import java.util.Set;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface ExampleRepository extends CrudRepository<ExampleEntity, String> {
#Query(nativeQuery = true,
value = "SELECT * FROM example ORDER BY similarity(:searchString)")
List<ExampleEntity> findBySimilarity();
#Query(nativeQuery = true, value = "CREATE EXTENSION pg_trgm")
void createSimilarityExtension();
}
Test code (excluding setup, as it is rather complex):
public void test() {
ExampleEntity r1 = dbUtils.persistNewRandomEntity();
ExampleEntity r2 = dbUtils.persistNewRandomEntity();
ExampleEntity r3 = dbUtils.persistNewRandomEntity();
try {
exampleRepository.createSimilarityExtension();
} catch (InvalidDataAccessResourceUsageException e) {
// always says that the extension is already setup
}
List<ExampleEntity> bySimilarity = exampleRepository.findBySimilarity(r2.getTextField());
for (ExampleEntity entity : bySimilarity) {
System.out.println(entity);
}
}
Turns out I created the extension in the wrong schema while trying out if the extension would work at all.
I then added the extension to my DB-migration script, but would skip it if the extension existed. Therefore my extension was registered for the public schema and did not work in the actual schema my service is using.
So if you have the same problem I had, make sure your extension is created for the correct schema by using:
SET SCHEMA <your_schema>; CREATE EXTENSION pg_trgm;

Accesing data from different collections of mongodb via springboot

I have been trying to join two different collections of MongoDB within a Spring boot application in order to fetch the data within a single #GetMapping call.
These are my application.properties:
spring.data.mongodb.uri=mongodb://localhost:27017/alpha1
#spring.data.mongodb.uri=mongodb://username:password#host:port/database
server.port = 4000
And this is what my repository looks like:
package com.example.demo.repository;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import com.example.demo.model.Person;
#Repository
public interface PersonRepository extends MongoRepository<Person, String>{
public Person findByContId(String firstName);
public Person findByUid(String uid);
}
The problem is I need to make another collection regarding user feeds and its data should be fetched within the same method.
You need to create another repository for the same like and fetch it where ever you need it
#Repository
public interface UserFeedsRepository extends MongoRepository<UserFeeds, String>{
public List<UserFeeds> findAll();
}

Wildfly throws "Unable to find a constructor that takes a String param or a valueOf() or fromString() method for javax.ws.rs.QueryParam" error

Im using wildfly 9.0 to deploy my war file. I have java LocalDateTime, Java Money types defined in my REST GET endpoints.
When i deploy my war file, i get following error[1]. Based on this answer [2] I have written "ParamConverterProvider" implementations for both types.
It was working fine( I haven't seen same issue again till now) and now i get same issue.
Any clue?
[1]
Caused by: java.lang.RuntimeException: Unable to find a constructor that takes a String param or a valueOf() or fromString() method for javax.ws.rs.QueryParam(\"totalMoneyVolumeForPeriod\") on public javax.ws.rs.core.Response com.test.rest.StockEndpoint.getItems(java.lang.Integer,java.lang.Integer,java.lang.String,java.lang.String,java.lang.Long,org.javamoney.moneta.Money,java.util.Set,java.lang.String) for basetype: org.javamoney.moneta.Money"}}}}
[2]
jaxrs could not find my custom (de)serializers for joda.money type
Sample code
package com.test;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import javax.money.Monetary;
import javax.ws.rs.ext.ParamConverter;
import javax.ws.rs.ext.ParamConverterProvider;
import javax.ws.rs.ext.Provider;
import org.javamoney.moneta.Money;
#Provider
public class MoneyConverterProvider implements ParamConverterProvider {
private final MoneyConverter converter = new MoneyConverter();
#Override
public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {
if (!rawType.equals(Money.class)) return null;
return (ParamConverter<T>) converter;
}
public class MoneyConverter implements ParamConverter<Money> {
public Money fromString(String value) {
if (value == null ||value.isEmpty()) return null; // change this for production
return Money.of(new BigDecimal(value), Monetary.getCurrency("AUD"));
}
public String toString(Money value) {
if (value == null) return "";
return value.toString(); // change this for production
}
}
}
Application claas
package com.test;
import javax.ws.rs.core.Application;
import com.test.autogen*;
import io.swagger.jaxrs.config.BeanConfig;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.ApplicationPath;
#ApplicationPath("/rest")
public class RestApplication extends Application {
public RestApplication() {
BeanConfig beanConfig = new BeanConfig();
//beanConfig.setVersion("1.0");
beanConfig.setSchemes(new String[] { "http" });
beanConfig.setTitle("My API");
beanConfig.setBasePath("/rest");
beanConfig.setResourcePackage("com.test.autogen");
beanConfig.setScan(true);
}
#Override
public Set<Class<?>> getClasses() {
HashSet<Class<?>> set = new HashSet<Class<?>>();
set.add(EmailEndpoint.class);
set.add(StockEndpoint.class);
set.add(io.swagger.jaxrs.listing.ApiListingResource.class);
set.add(io.swagger.jaxrs.listing.SwaggerSerializers.class);
return set;
}
}
When you are using classpath scanning, JAX-RS components annotated with #Path or #Provider will get picked up and registered. There are a couple way to use classpath scanning. The most common way is to just have an empty Application class annotated with #ApplicationPath
#ApplicationPath("/api")
public class MyApplication extends Application {}
This is enough for a JAX-RS application to be loaded, and to have the application's classpath scanned to components to register.
But, per the specification, once we override any of the Set<Object> getSingletons or Set<Class> getClasses methods of the Application class, and return a non-empty set, this automatically disables classpath scanning, as it is assumed we want to register everything ourselves.
So in previous cases, you were probably just using classpath scanning. In this case, you need to explicitly add the provider to the set of classes in your getClasses method, since you overrode the method to add other component classes.

Using Spring Security ACL with Spring Data REST

I am trying to authorize apis exposed by Spring Data REST. So far I am able to do role-based authorization i.e:
#RepositoryRestResource(path = "book")
public interface BookRepository extends JpaRepository<Book, Long> {
#PreAuthorize("hasRole('ROLE_ADMIN')")
<S extends Book> Book save(Book book);
}
Also in the same project i have a service layer with ACL mechanism, which is working.
I am unable to use PostFilter expression with Spring Data REST i.e:
#PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, admin)")
List<Book> findAll();
It would be of great help, if anyone using ACL with Spring Data REST.
Note: I am aware of below open issues:
https://jira.spring.io/browse/DATAREST-236
https://jira.spring.io/browse/SEC-2409
using JpaRepository was shadowing List<Book> findAll() method. Then I used CrudRepository, and PostFilter got applied.
For more details, a sample project is available on GitHub:
https://github.com/charybr/spring-data-rest-acl
ACL-based authorization is working for below entity exposed by Spring Data REST.
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.security.access.method.P;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
#RepositoryRestResource(path = "book")
public interface BookRepository extends CrudRepository<Book, Long> {
#PreAuthorize("hasRole('ROLE_ADMIN') or hasPermission(#book, 'write')")
<S extends Book> Book save(#P("book") Book book);
#Override
#PostFilter("hasPermission(filterObject, 'read') or hasPermission(filterObject, admin)")
Iterable<Book> findAll();
}

What is the difference between #BeforeClass and Spring #TestExecutionListener beforeTestClass()

What is the difference between using JUnit #BeforeClass and the Spring #TestExecutionListener beforeTestClass(TestContext testContext) "hook"? If there is a difference, which one to use under which circumstances?
Maven Dependencies:
spring-core:3.0.6.RELEASE
spring-context:3.0.6.RELEASE
spring-test:3.0.6.RELEASE
spring-data-commons-core:1.2.0.M1
spring-data-mongodb:1.0.0.M4
mongo-java-driver:2.7.3
junit:4.9
cglib:2.2
Using JUnit #BeforeClass annotation:
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.Assert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
#ContextConfiguration(locations = { "classpath:test-config.xml" })
public class TestNothing extends AbstractJUnit4SpringContextTests {
#Autowired
PersonRepository repo;
#BeforeClass
public static void runBefore() {
System.out.println("#BeforeClass: set up.");
}
#Test
public void testInit() {
Assert.assertTrue(repo.findAll().size() == 0 );
}
}
=> #BeforeClass: set up.
=> Process finished with exit code 0
Using the Spring hook:
(1) Override beforeTestClass(TextContext testContext):
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
public class BeforeClassHook extends AbstractTestExecutionListener {
public BeforeClassHook() { }
#Override
public void beforeTestClass(TestContext testContext) {
System.out.println("BeforeClassHook.beforeTestClass(): set up.");
}
}
(2) Use #TestExecutionListeners annotation:
import org.springframework.test.context.TestExecutionListeners;
// other imports are the same
#ContextConfiguration(locations = { "classpath:test-config.xml" })
#TestExecutionListeners(BeforeClassHook.class)
public class TestNothing extends AbstractJUnit4SpringContextTests {
#Autowired
PersonRepository repo;
#Test
public void testInit() {
Assert.assertTrue(repo.findAll().size() == 0 );
}
}
=> BeforeClassHook.beforeTestClass(): set up.
=> Process finished with exit code 0
TestExecutionListeners are a way to externalize reusable code that instruments your tests.
As such, if you implement a TestExecutionListener you can reuse it across test class hierarchies and potentially across projects, depending on your needs.
On the flip side, a #BeforeClass method can naturally only be used within a single test class hierarchy.
Note, however, that JUnit also supports Rules: if you implement org.junit.rules.TestRule you can declare it as a #ClassRule to achieve the same thing... with the added benefit that a JUnit Rule can be reused just like a Spring TestExecutionListener.
So it really depends on your use case. If you only need to use the "before class" functionality in a single test class or a single test class hierarchy, then you'd be better off going the simple route of just implementing a #BeforeClass method. However, if you foresee that you will need the "before class" functionality in different test class hierarchies or across projects, you should consider implementing a custom TestExecutionListener or JUnit Rule.
The benefit of a Spring TestExecutionListener over a JUnit Rule is that a TestExecutionListener has access to the TestContext and therefore access to the Spring ApplicationContext which a JUnit Rule would not have access to. Furthermore, a TestExecutionListener can be automatically discovered and ordered.
Related Resources:
SPR-8854
Regards,
Sam (author of the Spring TestContext Framework)
The first solution with #BeforeClass doesn't have application context loaded. I did exteneded AbstractJUnit4SpringContextTests and defined #ContextConfiguration.
I think listner is the only way to get context loaded before #beforeclass method. Or even better extending SpringJUnit4ClassRunner class as mentioned here