Small question on Spring Boot, and how to use a design pattern combined with Spring #Value configuration in order to select the appropriate #Repository please.
Setup: A springboot project which does nothing but save a pojo. The "difficulty" is the need to choose where to save the pojo, based on some info from inside the payload request.
I started with a first straightforward version, which looks like this:
#RestController
public class ControllerVersionOne {
#Autowired private ElasticRepository elasticRepository;
#Autowired private MongoDbRepository mongoRepository;
#Autowired private RedisRepository redisRepository;
//imagine many more other repositories
//imagine many more other repositories
//imagine many more other repositories
#PostMapping(path = "/save")
public String save(#RequestBody MyRequest myRequest) {
String whereToSave = myRequest.getWhereToSave();
MyPojo myPojo = new MyPojo(UUID.randomUUID().toString(), myRequest.getValue());
if (whereToSave.equals("elastic")) {
return elasticRepository.save(myPojo).toString();
} else if (whereToSave.equals("mongo")) {
return mongoRepository.save(myPojo).toString();
} else if (whereToSave.equals("redis")) {
return redisRepository.save(myPojo).toString();
// imagine many more if
// imagine many more if
// imagine many more if
} else {
return "unknown destination";
}
}
With the appropriate #Configuration and #Repository for each and every databases. I am showing 3 here, but imagine many. The project has a way to inject future #Configuration and #Repository as well (the question is not here actually)
#Configuration
public class ElasticConfiguration extends ElasticsearchConfiguration {
#Repository
public interface ElasticRepository extends CrudRepository<MyPojo, String> {
#Configuration
public class MongoConfiguration extends AbstractMongoClientConfiguration {
#Repository
public interface MongoDbRepository extends MongoRepository<MyPojo, String> {
#Configuration
public class RedisConfiguration {
#Repository
public interface RedisRepository {
Please note, some of the repositories are not children of CrudRepository. There is no direct ___Repository which can cover everything.
And this first version is working fine. Very happy, meaning I am able to save the pojo to where it should be saved, as I am getting the correct repository bean, using this if else structure.
In my opinion, this structure is not very elegant (if it ok if we have different opinion here), especially, not flexible at all (need to hardcode each and every possible repository, again imagine many).
This is why I went to refactor and change to this second version:
#RestController
public class ControllerVersionTwo {
private ElasticRepository elasticRepository;
private MongoDbRepository mongoRepository;
private RedisRepository redisRepository;
private Map<String, Function<MyPojo, MyPojo>> designPattern;
#Autowired
public ControllerVersionTwo(ElasticRepository elasticRepository, MongoDbRepository mongoRepository, RedisRepository redisRepository) {
this.elasticRepository = elasticRepository;
this.mongoRepository = mongoRepository;
this.redisRepository = redisRepository;
// many more repositories
designPattern = new HashMap<>();
designPattern.put("elastic", myPojo -> elasticRepository.save(myPojo));
designPattern.put("mongo", myPojo -> mongoRepository.save(myPojo));
designPattern.put("redis", myPojo -> redisRepository.save(myPojo));
//many more put
}
#PostMapping(path = "/save")
public String save(#RequestBody MyRequest myRequest) {
String whereToSave = myRequest.getWhereToSave();
MyPojo myPojo = new MyPojo(UUID.randomUUID().toString(), myRequest.getValue());
return designPattern.get(whereToSave).apply(myPojo).toString();
}
As you can see, I am leveraging a design pattern refactoring the if-else into a hashmap.
This post is not about if-else vs hashmap by the way.
Working fine, but please note, the map is a Map<String, Function<MyPojo, MyPojo>>, as I cannot construct a map of Map<String, #Repository>.
With this second version, the if-else is being refactored, but again, we need to hardcode the hashmap.
This is why I am having the idea to build a third version, where I can configure the map itself, via a spring boot property #Value for Map:
Here is what I tried:
#RestController
public class ControllerVersionThree {
#Value("#{${configuration.design.pattern.map}}")
Map<String, String> configurationDesignPatternMap;
private Map<String, Function<MyPojo, MyPojo>> designPatternStrategy;
public ControllerVersionThree() {
convertConfigurationDesignPatternMapToDesignPatternStrategy(configurationDesignPatternMap, designPatternStrategy);
}
private void convertConfigurationDesignPatternMapToDesignPatternStrategy(Map<String, String> configurationDesignPatternMap, Map<String, Function<MyPojo, MyPojo>> designPatternStrategy) {
// convert configurationDesignPatternMap
// {elastic:ElasticRepository, mongo:MongoDbRepository , redis:RedisRepository , ...}
// to a map where I can directly get the appropriate repository based on the key
}
#PostMapping(path = "/save")
public String save(#RequestBody MyRequest myRequest) {
String whereToSave = myRequest.getWhereToSave();
MyPojo myPojo = new MyPojo(UUID.randomUUID().toString(), myRequest.getValue());
return designPatternStrategy.get(whereToSave).apply(myPojo).toString();
}
And I would configure in the property file:
configuration.design.pattern.map={elastic:ElasticRepository, mongo:MongoDbRepository , saveToRedis:RedisRepositry, redis:RedisRepository , ...}
And tomorrow, I would be able to configure add or remove the future repository target.
configuration.design.pattern.map={elastic:ElasticRepository, anotherElasticKeyForSameElasticRepository, redis:RedisRepository , postgre:PostGreRepository}
Unfortunately, I am stuck.
What is the correct code in order to leverage a configurable property for mapping a key with it's "which #Repository to use" please?
Thank you for your help.
You can create a base repository to be extended by all your repositories:
public interface BaseRepository {
MyPojo save(MyPojo onboarding);
}
so you will have a bunch of repositories like:
#Repository("repoA")
public interface ARepository extends JpaRepository<MyPojo, String>, BaseRepository {
}
#Repository("repoB")
public interface BRepository extends JpaRepository<MyPojo, String>, BaseRepository {
}
...
Those repositories will be provided by a factory:
public interface BaseRepositoryFactory {
BaseRepository getBaseRepository(String whereToSave);
}
that you must configure in a ServiceLocatorFactoryBean:
#Bean
public ServiceLocatorFactoryBean baseRepositoryBean() {
ServiceLocatorFactoryBean serviceLocatorFactoryBean = new ServiceLocatorFactoryBean();
serviceLocatorFactoryBean.setServiceLocatorInterface(BaseRepositoryFactory.class);
return serviceLocatorFactoryBean;
}
Now you can inject the factory wherever you need and get the repo want:
#Autowired
private BaseRepositoryFactory baseRepositoryFactory;
...
baseRepositoryFactory.getBaseRepository("repoA").save(myPojo);
...
Hope it helps.
Short answer:
create a shared interface
create multiple sub-class of this interface (one per storage) using different spring component names
Use a map to deal with aliases
use Spring context to retrieve the right bean by alias (instead of creating a custom factory)
Now adding a new storage is only adding a new Repository classes with a name
Explanation:
As mentioned in the other answer you first need to define a common interface as you can't use the CrudRepository.save(...).
In my example I reuse the same signature as the save method to avoid re-implementing it in the sub-classes of CrudRepository.
public interface MyInterface<T> {
<S extends T> S save(S entity);
}
Redis Repository:
#Repository("redis") // Here is the name of the redis repo
public class RedisRepository implements MyInterface<MyPojo> {
#Override
public <S extends MyPojo> S save(S entity) {
entity.setValue(entity.getValue() + " saved by redis");
return entity;
}
}
For the other CrudRepository no need to provide an implementation:
#Repository("elastic") // Here is the name of the elastic repo
public interface ElasticRepository extends CrudRepository<MyPojo, String>, MyInterface<MyPojo> {
}
Create a configuration for your aliases in application.yml
configuration:
design:
pattern:
map:
redis: redis
saveToRedisPlease: redis
elastic: elastic
Create a custom properties to retrieve the map:
#Component
#ConfigurationProperties(prefix = "configuration.design.pattern")
public class PatternProperties {
private Map<String, String> map;
public String getRepoName(String alias) {
return map.get(alias);
}
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
}
Now create the version three of your repository with the injection of SpringContext:
#RestController
public class ControllerVersionThree {
private final ApplicationContext context;
private PatternProperties designPatternMap;
public ControllerVersionThree(ApplicationContext context,
PatternProperties designPatternMap) {
this.context = context;
this.designPatternMap = designPatternMap;
}
#PostMapping(path = "/save")
public String save(#RequestBody MyRequest myRequest) {
String whereToSave = myRequest.getWhereToSave();
MyPojo myPojo = new MyPojo(UUID.randomUUID().toString(), myRequest.getValue());
String repoName = designPatternMap.getRepoName(whereToSave);
MyInterface<MyPojo> repo = context.getBean(repoName, MyInterface.class);
return repo.save(myPojo).toString();
}
}
You can check that this is working with a test:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.HttpEntity;
import static org.junit.jupiter.api.Assertions.assertEquals;
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ControllerVersionThreeTest {
#LocalServerPort
private int port;
#Autowired
private TestRestTemplate restTemplate;
#Test
void testSaveByRedis() {
// Given: here 'redis' is the name of the spring beans
HttpEntity<MyRequest> request = new HttpEntity<>(new MyRequest("redis", "aValue"));
// When
String response = restTemplate.postForObject("http://localhost:" + port + "/save", request, String.class);
// Then
assertEquals("MyPojo{value='aValue saved by redis'}", response);
}
#Test
void testSaveByRedisAlias() {
// Given: here 'saveToRedisPlease' is an alias name of the spring beans
HttpEntity<MyRequest> request = new HttpEntity<>(new MyRequest("saveToRedisPlease", "aValue"));
// When
String response = restTemplate.postForObject("http://localhost:" + port + "/save", request, String.class);
// Then
assertEquals("MyPojo{value='aValue saved by redis'}", response);
}
}
Have you tried creating a configuration class to create your repository map
#Configuration
public class MyConfiguration {
#Bean
public Map repositoryMap() {
Map<String, ? extends Repository> repositoryMap = new HashMap<>();
repositoryMap.put('redis', new RedisRepository());
repositoryMap.put('mongo', new MongoRepository());
repositoryMap.put('elastic', new ElasticRepository());
return Collections.unmodifiableMap(repositoryMap);
}
}
Then you could have the following in your rest controller
#RestController
#Configuration
public class ControllerVersionFour {
#Autowired
private Map<String, ? extends Repository> repositoryMap;
#PostMapping(path = "/save/{dbname}")
public String save(#RequestBody MyRequest myRequest, #PathVariable("dbname") String dbname) {
MyPojo myPojo = new MyPojo(UUID.randomUUID().toString(), myRequest.getValue());
return repisitoryMap.get(dbname).save(myPojo);
}
It might be better to have the db as a path/query parameter instead of having it in the request body. That way you may or may not be able to just save the request body depending on your use case instead of creating another pojo.
This post may also be useful for autowiring a map
I'm working on a Spring app and defining various find methods on a repository:
#Repository
public interface TicketRepository extends JpaRepository<TicketEntity, Long> {
List<TicketEntity> findByTicketId(#Param("ticketId") Long ticketId);
List<TicketEntity> findByTicketIdAndState(#Param("ticketId") Long ticketId, #Param("state") String state);
List<TicketEntity> findByTicketIdAndStateAndFlagged(#Param("ticketId") Long ticketId, #Param("state") String state, #Param("flagged") String Flagged);
}
The problem is that I have 30 columns which can be optionally filtered on. This is will result in the repository methods becoming unwieldy:
List<TicketEntity> findByTicketIdAndStateAndFlaggedAndCol4AndCol5AndCol6AndCol7AndCol8AndCol9AndCol10AndCol11AndCol12AndCol13AndCol14AndCol15AndCol16AndCol17AndCol18AndCol19AndCol120....);
How should the JPA layer be designed to cater for this scenario ?
If I create an object with attributes:
public class SearchObject {
private String attribute1;
//Getter and Setters
.
.
.
.
}
Can I pass SearchObject into a a find method and Spring JPA will determine which attributes to insert AND statements for depending on which attributes are Null - if the attribute is not null a corresponding AND is generated for that attribute.
Create filter object that will contain all optional columns e.g.:
#AllArgsConstructor
public class TicketFilter {
private final String col1;
private final Integer col2;
public Optional<String> getCol1() {
return Optional.ofNullable(col1);
}
public Optional<Integer> getCol2() {
return Optional.ofNullable(col2);
}
}
Extend your Respoitory with JpaSpecificationExecutor
Create specification class:
public class TicketSpecification implements Specification {
private final TicketFilter ticketFilter;
public TicketSpecification(TicketFilter ticketFilter) {
this.ticketFilter = ticketFilter;
}
#Override
public Predicate toPredicate(Root<Ticket> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
ticketFilter.getTitle().ifPresent(col1 -> predicates.add(getCol1Predicate(root, col1)));
ticketFilter.getDescription().ifPresent(col2 -> predicates.add(getCol2Predicate(root, col2)));
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
}
private Predicate getCol1Predicate(Root root, String title) {
return root.get("col1").in(col1);
}
}
Use your repository: ticketRepository.findAll(specification);
Use Spring Data JPA Specification
Detail Solution be patient
First create a SpecificationCriteria class to define your criterias means filtering column as key and filtering value as value
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class SpecificationCriteria {
private String key;
private Object value;
}
Then create SpecificationCriteriaBuilder to build your Criteria
#Service
public class SpecificationCriteriaBuilder {
public List<SpecificationCriteria> buildCriterias(String name) {
List<SpecificationCriteria> specificationCriterias = new ArrayList<SpecificationCriteria>();
if (!StringUtils.isEmpty(name)) {
specificationCriterias
.add(SpecificationCriteria.builder().key("name")
.value(name).build());
}
// Here you can add other filter one by one
return specificationCriterias;
}
}
Then create a SpecificationBuilder class to build your specifications.
You can build from the list of filter options(Criteria) to List of specification
import java.util.List;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
#Service
public class SpecificationBuilder<T> {
public Specification<T> buildSpecification(List<SpecificationCriteria> specificationCriterias) {
if (ObjectUtils.isEmpty(specificationCriterias)) {
return null;
}
Specification<T> specification = getSpecification(specificationCriterias.get(0));
for (int index = 1; index < specificationCriterias.size(); index++) {
SpecificationCriteria specificationCriteria = specificationCriterias.get(index);
specification =
Specification.where(specification).and(getSpecification(specificationCriteria));
}
return specification;
}
public Specification<T> getSpecification(SpecificationCriteria specificationCriteria) {
Specification<T> specification = new Specification<T>() {
private static final long serialVersionUID = 2089704018494438143L;
#Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
return builder.equal(root.get(specificationCriteria.getKey()),
specificationCriteria.getValue());
}
};
return specification;
}
}
In service first build criteria and then build specification using them. Then use specifications in repository call
#Service
#Transactional
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class UserService {
private final SpecificationCriteriaBuilder criteriaBuilder;
private final SpecificationBuilder<User> specificationBuilder;
private final UserRepository userRepository;
public List<User> getAll(String name) {
List<SpecificationCriteria> specificationCriterias =
criteriaBuilder.buildCriterias(name); // here you can pass other parameter as function argument
Specification<User> specification =
specificationBuilder.buildSpecification(specificationCriterias);
List<User> users = userRepository.findAll(specification);// pass the specifications
return users;
}
Repository extend JpaSpecificationExecutor
#Repository
public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {
}
I have a DTO which is validated at Controller layer with a mix of BeanValidation (javax.validation) and a custom Validator (org.springframework.validation.Validator). This way I can check if the input provided is valid and then convert the DTO in an entity and forward it to the Service layer.
#Data
public class UserDTO {
#NotBlank
#Size(max = 25)
private String name;
#NotNull
private Date birthday;
#NotNull
private Date startDate;
private Date endDate;
private Long count;
}
public class UserDTOValidator implements Validator {
private static final String START_DATE= "startDate";
private static final String END_DATE= "endDate";
private static final String COUNT= "count";
#Override
public boolean supports(Class<?> clazz) {
return UserDTO.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
UserDTO vm = (UserDTO) target;
if (vm.getEndDate() != null) {
if (vm.getStartDate().after(vm.getEndDate())) {
errors.rejectValue(START_DATE, ErrorCode.ILLEGAL_ARGUMENT.toString(), ErrorCode.ILLEGAL_ARGUMENT.description());
}
if (vm.getEndDate().equals(vm.getStartDate()) || vm.getEndDate().before(vm.getStartDate())) {
errors.rejectValue(END_DATE, ErrorCode.ILLEGAL_ARGUMENT.toString(), ErrorCode.ILLEGAL_ARGUMENT.description());
}
}
if (vm.getCount() < 1) {
errors.rejectValue(COUNT, ErrorCode.ILLEGAL_ARGUMENT.toString(), ErrorCode.ILLEGAL_ARGUMENT.description());
}
.....
}
}
public class UserController {
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new UserDTOValidator());
}
#PostMapping()
public ResponseEntity<UserDTO> create(#RequestBody #Valid UserDTO userDTO) {
.....
}
.....
}
Then there is the business logic validation. For example: the #Entity User's startDate must be after some event occurred and the count has to be greater than some X if the last created User's birthDay is in Summer, in other case, the entity should be discarded by the User service.
#Service
#Transactional
public class UserServiceImpl implements UserService {
#Autowired
private UserRepository userRepository;
#Autowired
private SomeEventService someEventService ;
#Override
public User create(User entity) {
String error = this.validateUser(entity);
if (StringUtils.isNotBlank(error)) {
throw new ValidationException(error);
}
return this.userRepository.save(entity);
}
....
private String validateUser(User entity) {
SomeEvent someEvent = this.someEventService.get(entity.getName());
if (entity.getStartDate().before(someEvent.getDate())) {
return "startDate";
}
User lastUser = this.userRepository.findLast();
....
}
}
However I feel like this is not the best approach to handle business logic validation. What should I do? ConstraintValidator/HibernateValidator/JPA Event listeners? Can they work at #Entity class level or I have to create X of them for each different field check? How do you guys do it in a real production application?
In my suggestion,
Use classic field level validation by #Valid
sample
void myservicemethod(#Valid UserDTO user)
For custom business level validation in entity level, create validate method in DTO itself
sample
class UserDTO {
//fields and getter setter
void validate() throws ValidationException {
//your entity level business logic
}
}
This strategy will help to keep entity specific validation logic will be kept within the entity
If still you have validation logic that requires some other service call, then create custom validation annotation with custom ConstraintValidator (eg. question on stackoverflow). In this case, my preference will be to invoke UserDTO.validate() from this custom validator in spiote of calling from service
This will help to keep your validation logic separated from service layer and also portable and modular
I'm using Spring Boot 2.0 with default application.yml properties file. I would like to split it to separate property files because it becomes huge.
Also I would like to write tests to check properties correctness: values that will present on production application context (not the test one).
Here is my property file: src/main/resources/config/custom.yml
my-property:
value: 'test'
Property class:
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
#Data
#Configuration
#ConfigurationProperties(prefix = "my-property")
#PropertySource("classpath:config/custom.yml")
public class MyProperty {
private String value;
}
Test:
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyProperty.class)
#EnableConfigurationProperties
public class MyPropertyTest {
#Autowired
private MyProperty property;
#Test
public void test() {
assertEquals("test", property.getValue());
}
}
But test fails with error:
java.lang.AssertionError:
Expected :test
Actual :null
Also I see that property value is null when running the application by printing it in ApplicationRunner.
When I used application.yml for all properties it were well with the same configuration.
How to put correct configuration for properties and tests for make it work?
Link to Github repo
Finely I found the right way for having custom yaml properties in my app.
The issue is that Spring doesn't support yaml files as #PropertySource (link to issue). And here is a workaround how to deal with that described in spring documentation.
So, to be able to load properties from yaml files you need:
* To implement EnvironmentPostProcessor
* To register it in spring.factories
Please visit this github repo for complete example.
Also, thanks a lot for your support, guys!
#TestPropertySource can solve your problem.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MyProperty.class)
#TestPropertySource(locations="classpath:test.properties")
public class MyPropertyTest {
#Autowired
private MyProperty property;
#Test
public void test() {
assertEquals("test", property.getValue());
}
}
Hope it helps.
I am a little late to the party, but this might also help. The solution provided as answer is the best approach so far, but here is an alternative that I used
Make use of profiles and modify the PropertySoucesPlaceHolderConfiguration bean to do load the necessary property files based on the profiles. It loads the application.properties as the default one but the other propertyfiles -oauth_DEV and oauth_QA are loaded based on the profiles set
#Bean
public PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurerconfigurer() {
System.out.println("Inside Placeholder bean");
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
ClassPathResource cls1= new ClassPathResource("application.properties");
ClassPathResource cls2 = null;
Map<String, Object> propMap = ((ConfigurableEnvironment) ctx.getEnvironment()).getSystemProperties();
for(Map.Entry<String, Object> entrySet: propMap.entrySet()) {
System.out.println("Map.Key:"+entrySet.getKey()+" Map.valiue:"+entrySet.getValue());
}
List<String> profiles= Arrays.asList(ctx.getEnvironment().getActiveProfiles());
if(profiles == null || profiles.isEmpty()) {
if(!propMap.containsKey("spring.profiles.active")) {
cls2 = new ClassPathResource("oauth-default.properties");
} else {
cls2 = new ClassPathResource("oauth-"+propMap.get("spring.profiles.active")+".properties");
}
}else {
for(String profile:profiles) {
if(profile.equalsIgnoreCase("DEV")) {
cls2 = new ClassPathResource("oauth-DEV.properties");
}else if(profile.equalsIgnoreCase("QA")) {
cls2 = new ClassPathResource("oauth-QA.properties");
}else if (profile.equalsIgnoreCase("UAT")) {
cls2 = new ClassPathResource("oauth-UAT.properties");
}else if(profile.equalsIgnoreCase("PROD")){
cls2 = new ClassPathResource("oauth-PROD.properties");
}else {
cls2 = new ClassPathResource("oauth-default.properties");
}
}
}
cfg.setLocations(cls1,cls2);
//cfg.setPlaceholderPrefix("#{");
return cfg;
}
Then create another bean that reads the properties based on the prefix - "security.oauth2.client"
#Configuration
#ConfigurationProperties(prefix="security.oauth2.client")
public class OauthSecurityConfigurationDto {
private String clientId;
private String clientSecret;
private String scope;
private String accessTokenUri;
private String userAuthorizationUri;
private String grantType;
private String resourceIds;
private String registeredRedirectUri;
private String preEstablishedRedirectUri;
private String useCurrentUri;
private String userInfoUri;
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public String getAccessTokenUri() {
return accessTokenUri;
}
public void setAccessTokenUri(String accessTokenUri) {
this.accessTokenUri = accessTokenUri;
}
public String getUserAuthorizationUri() {
return userAuthorizationUri;
}
public void setUserAuthorizationUri(String userAuthorizationUri) {
this.userAuthorizationUri = userAuthorizationUri;
}
public String getGrantType() {
return grantType;
}
public void setGrantType(String grantType) {
this.grantType = grantType;
}
public String getResourceIds() {
return resourceIds;
}
public void setResourceIds(String resourceIds) {
this.resourceIds = resourceIds;
}
public String getRegisteredRedirectUri() {
return registeredRedirectUri;
}
public void setRegisteredRedirectUri(String registeredRedirectUri) {
this.registeredRedirectUri = registeredRedirectUri;
}
public String getPreEstablishedRedirectUri() {
return preEstablishedRedirectUri;
}
public void setPreEstablishedRedirectUri(String preEstablishedRedirectUri) {
this.preEstablishedRedirectUri = preEstablishedRedirectUri;
}
public String getUseCurrentUri() {
return useCurrentUri;
}
public void setUseCurrentUri(String useCurrentUri) {
this.useCurrentUri = useCurrentUri;
}
public String getUserInfoUri() {
return userInfoUri;
}
public void setUserInfoUri(String userInfoUri) {
this.userInfoUri = userInfoUri;
}
}
Remember the setters are important because the ConfigurationProperties load the values into the properties of the class only when getters and setters are defined
Now we can autowire the dependency wherever needed and use the property.
If This is your exact code that means you are reading your property from a wrong property file.
replace your property resource to this line.
#PropertySource("classpath:config/services.yml")
Is there a way to allow a field as input but exclude it from output in JAX-B? I added #XMLTransient, but that prevents the field from being used as an input field. I'm using Jersey 2.25.1 and Moxy.
The scenario is a password field on a user record. I want to allow it to be passed in when creating a new user record, but I don't want to include it in any responses as part of the user POJO.
EDIT:
I tried the #XmlReadOnly attribute and it seems to have done the trick. It is part of the org.eclipse.persistence.oxm.annotations package.
One way would be to use an XmlAdapter. You could return the result on the unmarshal() and return null on the marhsall()
public class OnlyInputAdapter extends XmlAdapter<String, String> {
#Override
public String unmarshal(String s) throws Exception {
return s;
}
#Override
public String marshal(String v) throws Exception {
return null;
}
}
Then just annotate the password property on the model
#XmlJavaTypeAdapter(OnlyInputAdapter.class)
public String getPassword() {
return this.password;
}
Here's a test
public class PasswordSerializationTest extends JerseyTest {
private User serverUser;
#Path("test")
#Produces("application/json")
#Consumes("application/json")
public class TestResource {
#POST
public User postUser(User user) {
serverUser = user;
return user;
}
}
#Override
public ResourceConfig configure() {
return new ResourceConfig().register(new TestResource());
}
#Test
public void doIt() {
User clientUser = target("test")
.request()
// post string; can't post User as the
// password field wouldn't serialize
.post(Entity.json("{\"password\":\"secret\"}"), User.class);
assertThat(serverUser.getPassword()).isEqualTo("secret");
assertThat(clientUser.getPassword()).isEqualTo(null);
}
}