Spring Boot 2.x.x - Combine 2 conditions in #ConditionalOnProperty - spring

I have the following interface :
#Primary
#ConditionalOnProperty(value = "ms.couchbase.enabled", havingValue = "true", matchIfMissing = true)
#ConditionalOnProperty(value = "iiams.switch.iiamsSingleProcessIdPer100", havingValue = "true", matchIfMissing = true)
public interface AsyncCommonRepository extends CouchbaseRepository<CouchbaseDocument, Long> { }
Of course this does not compile.
All I want is to combine somehow the two #ConditionalOnProperty. Should I use #ConditionalOnExpression?
Thanks in advance!

You can use AllNestedConditions to combine two or more conditions:
class AsyncCommonRepositoryCondition extends AllNestedConditions {
AsyncCommonRepositoryCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
#ConditionalOnProperty(value = "ms.couchbase.enabled", havingValue = "true", matchIfMissing = true)
static class CouchbaseEnabled {
}
#ConditionalOnProperty(value = "iiams.switch.iiamsSingleProcessIdPer100", havingValue = "true", matchIfMissing = true)
static class SingleProcessIdPer100 {
}
}
This condition can then be used on your repository:
#Primary
#Conditional(AsyncCommonRepositoryCondition.class)
public interface AsyncCommonRepository extends CouchbaseRepository<CouchbaseDocument, Long> { }

Related

How to validate configuration properties only on certain condition?

I have the following configuration properties class:
#Getter
#Setter
#ConfigurationProperties(prefix = "myprops")
public class MyProps {
private boolean enabled = true;
#NotEmpty
private String hostname;
#NotNull
private Integer port;
}
I want the validation annotations on hostname and port only to be considered when enabled = true. When enabled = false the validation should not be executed.
I have already tried putting the validations inside a validation group named OnEnabled and I tried applying #Validated(OnEnabled.class) in a #Configuration class annotated with #ConditionalOnProperty, but that didn't seem to work:
#Configuration(proxyBeanMethods = false)
#ConditionalOnProperty(name = "myprops.enabled", matchIfMissing = true)
public class MyPropsConfiguration {
#Bean
#Validated(OnEnabled.class)
#ConfigurationProperties(prefix = "myprops")
public MyProps myProps() {
return new MyProps();
}
}
I also tried the following, but it is giving me a compile time error about duplicate configuration property prefixes:
#Configuration(proxyBeanMethods = false)
public class MyPropsAutoConfiguration {
#Configuration(proxyBeanMethods = false)
#ConditionalOnProperty(name = "myprops.enabled", matchIfMissing = true)
public static class MyPropsEnabledConfiguration {
#Bean
#Validated
#ConfigurationProperties(prefix = "myprops")
public MyProps myProps() {
return new MyProps();
}
}
#Configuration(proxyBeanMethods = false)
#ConditionalOnProperty(name = "myprops.enabled", havingValue = "false")
public static class MyPropsDisabledConfiguration {
#Bean
#ConfigurationProperties(prefix = "myprops")
public MyProps myProps() {
return new MyProps();
}
}
}
Moving the #ConfigurationProperties to the properties class got rid of the compile error, but also didn't work as expected
Is there any way to achieve this? I know that a custom Validator might be a solution, but I would be interested if this is possible with pure spring annotations?

Spring Data JPA: using property in #Query as parameter

I have several application-x.properties files which are used for different profiles and each file contains a property with a specific value for each profile. I want to use this property in queries to database as a parameter.
Is it possible to add it using SpEL or something else?
For instance application.properties:
query.parameters.role: ADMIN
possible usage:
#Query(value = "select u from User u where u.role = :#{query.parameters.role}")
Set<User> getAllUsers();
You might do it by following way:
1.- Find all users by role using Repository Query Keywords
#Repository
public interface UserRepository extends JpaRepository<User, UUID> {
Set<User> findByRole(String role);
}
2.- Create a method called getAllUsers in UserService and get role value by using #Value:
#Service
public class UserService {
#Autowired
private UserRepository repository;
#Value("${query.parameters.role}")
private String role;
public Set<User> getAllUsers() {
return repository.findByRole(role);
}
}
Other way to answer to this question is implement a custom SpEL that is supported by #Query you can take a look this SpEL support in Spring Data JPA
Then you should follow these steps for your case:
1.- Create a ConfigProperties class so that you can read and get the application.properties.
#Configuration
#PropertySource("classpath:application.properties")
#ConfigurationProperties(prefix = "query")
public class ConfigProperties {
private Parameters parameters;
// standard getters and setters
}
public class Parameters {
private String role;
// standard getters and setters
}
2.- Implement a custom EvaluationContextExtensionSupport and reference to ConfigProperties
public class PropertyEvaluationContextExtension extends EvaluationContextExtensionSupport {
private final ConfigProperties configProperties;
public PropertyEvaluationContextExtension(final ConfigProperties configProperties) {
this.configProperties= configProperties;
}
#Override
public String getExtensionId() {
return "properties";
}
#Override
public ConfigProperties getRootObject() {
return this.configProperties;
}
}
3.- Create a bean in order to be called your custom PropertyEvaluationContextExtension
#Configuration
public class CustomConfig {
private final ConfigProperties configProperties;
public CustomConfig(final ConfigProperties configProperties) {
this.configProperties= configProperties;
}
#Bean
EvaluationContextExtensionSupport propertyExtension() {
return new PropertyEvaluationContextExtension(configProperties);
}
}
4.- Call the query.parameters.role by following format: ?#{query.parameters.role}
#Query(value = "SELECT u FROM User u WHERE u.role = ?#{query.parameters.role}")
Set<User> getAllUsers();

JPA Query works only native

Here is my repository:
public interface MachineRepository extends JpaRepository<Machine, Integer> {
#Query(value="select m.name FROM Machine m", nativeQuery = true)
Set<SomeName> getAllMachineTypes();
}
Interface:
public interface SomeName {
String getName();
}
Model:
#Data
#Entity
#Table(name="Machine")
public class Machine {
#Id
#Column(name = "id")
#GeneratedValue
private Integer id;
#Column(name = "name")
private String name;
}
Service:
#Service
public class MachineService {
#Autowired
MachineRepository machineRepository;
public Set<SomeName> getAllMachines(){
return machineRepository.getAllMachines();
}
Controller:
#Autowired
MachineService machineService;
#RequestMapping("/")
public String findMachines(){
Set<SomeName> machines = machineService.getAllMachines();
for (SomeName mch: machines
) {
System.out.println(mch.getName());
}
//...
}
When I run as it is, it prints the name to the console. But when I change nativeQuery to false (or remove it because it is false by default):
public interface MachineRepository extends JpaRepository<Machine, Integer> {
#Query(value="select m.name FROM Machine m", nativeQuery = false)
Set<SomeName> getAllMachineTypes();
}
Then I don't get any output.
Since I don't want to use nativeQuery, I would like to ask how to make it work without it.
Use this:
public interface MachineRepository extends JpaRepository<Machine, Integer> {
#Query(value="select m.name FROM Machine m")
List<String> getAllMachineTypes();
}
because m.name is a String.

Spring Data Envers org.springframework.data.mapping.PropertyReferenceException: No property findRevisions found for type

I have a Spring boot 1.4.2 application with Hibernate 5.2.6 and Spring data Envers 1.0.5. I am auditing my entities and the audit records are persisted properly.
My application config class is annotated to use the EnversRevisionRepositoryFactoryBean.class as the JPA repository factory.
Application config
#Configuration
#EnableAutoConfiguration
#ComponentScan
#EnableJpaRepositories(repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class)
#EnableTransactionManagement
public class ApplicationConfig {}
I am trying to read the revisions for an audited entity. The entity repository extends RevisionRepository.
Entity Models
#Entity(name = "Base")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.INTEGER)
#Table(name = "BASE")
#Audited
public abstract class Base {
#Id
#GeneratedValue(generator = "baseSeq", strategy = GenerationType.SEQUENCE)
#SequenceGenerator(name = "baseSeq", sequenceName = "BASE_SEQ", allocationSize = 1)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "name", nullable = false)
private long barId;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getBarId() {
return barId;
}
public void setBarId(long barId) {
this.barId = barId;
}
public abstract String getType();
}
#Entity
#DiscriminatorValue("1")
#Audited
#NamedQueries({
#NamedQuery(
name = "Foo.findById",
query = "select f from Base b where b.id = ?1"),
#NamedQuery(
name = "Foo.findByBarId",
query = "select f from Base b where b.barId = ?1")})
public class Foo extends Base {
private String type = "Foo";
#Override
public String getType() {
return type;
}
}
Entity repository
interface FooRepository extends JpaRepository<Foo, Long>,
JpaSpecificationExecutor<Foo>, RevisionRepository<Foo, Long, Integer> {
foo findById(Long Id);
foo findByBarId(Long barId);
}
The application start up fails as the repository cannot be initialized due to a PropertyReferenceException.
Caused by:
org.springframework.data.mapping.PropertyReferenceException: No
property findRevisions found for type Foo! at
org.springframework.data.mapping.PropertyPath.(PropertyPath.java:77)
at
org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:329)
at
org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:309)
at
org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:272)
at
org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:243)
at
org.springframework.data.repository.query.parser.Part.(Part.java:76)
at
org.springframework.data.repository.query.parser.PartTree$OrPart.(PartTree.java:235)
at
org.springframework.data.repository.query.parser.PartTree$Predicate.buildTree(PartTree.java:373)
at
org.springframework.data.repository.query.parser.PartTree$Predicate.(PartTree.java:353)
at
org.springframework.data.repository.query.parser.PartTree.(PartTree.java:84)
at
org.springframework.data.jpa.repository.query.PartTreeJpaQuery.(PartTreeJpaQuery.java:63)
at
org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:103)
at
org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateIfNotFoundQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:214)
at
org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$AbstractQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:77)
at
org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.(RepositoryFactorySupport.java:435)
at
org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:220)
at
org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.initAndReturn(RepositoryFactoryBeanSupport.java:266)
at
org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:252)
at
org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:92)
at
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1642)
at
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1579)
From what I understand, this should work out of the box. It seems like the repository is being bound to a different implementation than the desired one. Any ideas?
Relevant snippets from the gradle build script
buildscript {
ext {
springBootVersion = "1.4.2.RELEASE"
verifier_version = "1.0.0.RELEASE"
}
repositories {
maven {url "https://plugins.gradle.org/m2/"}
maven {url "http://repo.spring.io/plugins-release"}
jcenter()
mavenCentral()
}
dependencies {
classpath("org.springframework.build.gradle:propdeps-plugin:0.0.7") // this enables optional dependencies
classpath("io.spring.gradle:dependency-management-plugin:0.6.1.RELEASE")
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath("se.transmode.gradle:gradle-docker:1.2")
classpath("com.commercehub:gradle-cucumber-jvm-plugin:0.7")
classpath("org.ajoberstar:grgit:1.1.0")
classpath("org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.1-rc3")
classpath("gradle.plugin.org.detoeuf:swagger-codegen-plugin:1.6.3")
classpath("org.springframework.cloud:spring-cloud-contract-gradle-plugin:${verifier_version}")
classpath "net.linguica.gradle:maven-settings-plugin:0.5"
}
}
...
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:Brixton.SR7"
mavenBom "org.springframework.cloud:spring-cloud-contract-dependencies:${verifier_version}"
mavenBom 'org.springframework.cloud:spring-cloud-stream-dependencies:Brooklyn.SR1'
}
}
...
compile(
"org.springframework.boot:spring-boot-starter-data-jpa",
'org.springframework.data:spring-data-commons',
'org.springframework.cloud:spring-cloud-starter-config',
'org.springframework.cloud:spring-cloud-starter-eureka',
'org.springframework.cloud:spring-cloud-starter-sleuth',
'org.springframework.cloud:spring-cloud-sleuth-zipkin',
'com.netflix.hystrix:hystrix-javanica',
'org.springframework.boot:spring-boot-starter-aop',
"org.springframework.boot:spring-boot-starter-web",
"io.swagger:swagger-annotations:1.5.9",
"com.google.code.gson:gson:2.7",
"gradle.plugin.org.detoeuf:swagger-codegen-plugin:1.6.3",
"org.springframework:spring-orm",
"com.oracle.jdbc:ojdbc7:12.1.0.2",
'org.springframework.cloud:spring-cloud-stream',
'org.springframework.cloud:spring-cloud-stream-test-support',
'org.springframework.cloud:spring-cloud-stream-binder-test',
"org.springframework.boot:spring-boot-starter-hateoas",
"com.fasterxml.jackson.module:jackson-module-parameter-names",
"com.fasterxml.jackson.datatype:jackson-datatype-jdk8",
"com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.6.1",
"org.hibernate:hibernate-core:5.2.6.Final",
"org.hibernate:hibernate-envers:5.2.6.Final",
"org.springframework.data:spring-data-envers:1.0.6.RELEASE"
)
Thanks in advance.
You need to add an attribute #EnableJpaRepositories(repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class) to your application class (the class annotated with #SpringBootApplication)
As shown in this answer: https://stackoverflow.com/a/36416266

#Converter() is ignored when I am using it in abstract class

I have problem that #Converter() is ignored when I am using it in abstract class.
#Entity
#Table(name = "CHALLENGE")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class AbstractChallenge implements SecretChallenge {
...
#Convert(converter = InstantConverter.class)
Instant creationTime;
...
}
If I use it in standard JPA it works as expected.
#Entity
public class PasswordResetTokenJPA {
...
#Convert(converter = InstantConverter.class)
private Instant issuedAt;
...
}
Here is my converter:
#Converter(autoApply = true)
public class InstantConverter implements AttributeConverter<Instant, Timestamp> {
#Override
public Timestamp convertToDatabaseColumn(Instant instant) {
if (instant == null) return null;
return new Timestamp(instant.toEpochMilli());
}
#Override
public Instant convertToEntityAttribute(Timestamp value) {
if (value == null) return null;
return value.toInstant();
}
}

Resources