CDI #Instance to collect #Any, but access the Qualifiers (esp. #Named) - java

I want to collect all beans that are produced somewhere. Something like this:
static class Greeting {
public final String text;
public Greeting(String text) {
this.text = text;
}
}
#Produces
#Named("hi")
Greeting hi = new Greeting("Hi");
#Produces
#Named("hello")
Greeting hello = new Greeting("Hello");
#Inject
Instance<Greeting> greetings;
#Test
public void shouldCollectAll() {
Set<String> set = new HashSet<>();
for (Greeting greeting : greetings) {
set.add(greeting.text);
}
assertEquals(2, set.size());
assertTrue(set.contains("Hi"));
assertTrue(set.contains("Hello"));
}
I understand that I can select on the qualifiers like this:
#SuppressWarnings("all")
private static class NamedLiteral extends AnnotationLiteral<Named> implements Named {
private final String name;
public NamedLiteral(String name) {
this.name = name;
}
#Override
public String value() {
return name;
}
}
#Test
public void shouldCollectNamedHi() {
Greeting greeting = greetings.select(new NamedLiteral("hi")).get();
assertEquals("Hi", greeting.text);
}
But I want to access the qualifiers. Something like:
#Test
public void shouldCollectAllWithMeta() {
Map<String, Greeting> map = new HashMap<>();
for (Greeting greeting : greetings) {
Annotated annotated = magic(greeting);
String name = annotated.getAnnotation(Named.class).value();
map.put(name, greeting);
}
assertEquals(2, map.size());
assertEquals("Hi", map.get("hi"));
assertEquals("Hello", map.get("hello"));
}
Is there a way to implement magic without writing a CDI extension? Or is there one out there? Or is this a feature request for CDI 2.0?

It is not possible to access the bean metadata from the bean contextual instances. However, this is possible when working at the level of the beans themselves, using the BeanManager instance and without writting a CDI extension, e.g.:
#Inject
BeanManager manager;
Set<Bean<?>> beans = manager.getBeans(Greeting.class, Named.class);
for (Bean<?> bean : beans) {
String name = getQualifierOfType(bean.getQualifiers(), Named.class).value();
map.put(name, greeting);
}
<Annotation, T extends Annotation> T getQualifierOfType(Set<Annotation> qualifiers, Class<T> type) {
for (Annotation qualifier : qualifiers)
if (type.isAssignableFrom(qualifier.getClass()))
return type.cast(qualifier);
return null;
}

Related

How to Use DI to get a final variable as #PostMapping's path

I have a final class Constants, which holds some final data.
#Component
public final class Constants {
public final String TOKEN;
public final String HOST;
public final String TELEGRAM;
public Constants(#Value("${myapp.bot-token}") String token,
#Value("${myapp.host}") String host) {
this.TOKEN = token;
this.HOST = host;
this.TELEGRAM = "https://api.telegram.org/bot" + TOKEN;
}
}
The problem is that, when I want to use a variable as #PostMapping path, I faced this error:
Attribute value must be constant
#RestController
#RequestMapping
public class Controller {
private final Constants constants;
#Autowired
public Controller(Constants constants) {
this.constants = constants;
}
#PostMapping(constants.TOKEN)// Problem is here
public ResponseEntity<?> getMessage(#RequestBody String payload) {
return new ResponseEntity<HttpStatus>(HttpStatus.OK);
}
}
I've tried to load TOKEN in my controller class but faced the same issue.
#RestController
#RequestMapping
public class Controller {
#Value("${myapp.bot-token}") String token
private String token;
#PostMapping(token)// Problem is here
public ResponseEntity<?> getMessage(#RequestBody String payload) {
return new ResponseEntity<HttpStatus>(HttpStatus.OK);
}
}
When I do something like this the problem will gone. But I don't want to declare my token in source-code.
#RestController
#RequestMapping
public class Controller {
private final String TOKEN = "SOME-TOKEN";
#PostMapping(TOKEN)// No problem
public ResponseEntity<?> getMessage(#RequestBody String payload) {
return new ResponseEntity<HttpStatus>(HttpStatus.OK);
}
}
Can anyone please give me a solution to this?
Try to paste string with property path inside #PostMapping annotation. Like this
#GetMapping(value = "${app.path}")
public String hello() {
return "hello";
}
You can only use a constant (i.e. a final static variable) as the parameter for an annotation.
Example:
#Component
class Constants {
public final static String FACEBOOK = "facebook";
}
#RestController
class Controller {
#PostMapping(Constants.FACEBOOK)
public ResponseEntity<ResponseBody> getMessage(#RequestBody String payload) {
return new ResponseEntity<>(HttpStatus.OK);
}
}
You must use builder pattern(use Lombok for ease) and freeze the value that you are getting from the properties and then use that in your program.

Junit test cases for javax Custom Validator

I am working on cross field validation using javax validation API in Spring Boot Application. I have a User bean and i have to validate that both firstname and lastname are not null/empty. At least one of this field should have a value.
I have created custom annotation (NameMatch.java) and custom Validator (NameValidator.java) for this requirement.
#NameMatch(first = "firstname", second = "lastname", message = "The first and lastname can't be null")
public class User {
private String firstname;
private String lastname;
#NotNull
#Email
private String email;
#NotNull
private String phone;
}
NameMatch.java
#Target({TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = NameValidator.class)
#Documented
public #interface NameMatch
{
String message() default "{constraints.fieldmatch}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/**
* #return The first field
*/
String first();
/**
* #return The second field
*/
String second();
}
NameValidator.java
public class NameValidator implements ConstraintValidator<NameMatch, Object>
{
private String firstFieldName;
private String secondFieldName;
#Override
public void initialize(final NameMatch constraintAnnotation)
{
firstFieldName = constraintAnnotation.first();
secondFieldName = constraintAnnotation.second();
}
#Override
public boolean isValid(final Object value, final ConstraintValidatorContext context)
{
boolean isValidName = false;
try
{
final Object firstName = BeanUtils.getProperty(value, firstFieldName);
final Object lastName = BeanUtils.getProperty(value, secondFieldName);
// Validation logic
}
catch (final Exception ignore)
{
}
return isValidName;
}
}
UserValidator.java
public class UserValidator
{
public void isValidUser()
{
//Create ValidatorFactory which returns validator
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
//It validates bean instances
Validator validator = factory.getValidator();
User user = new User();
user.setEmail("test#gmail.com");
user.setPhone("12345678")
//Validate bean
Set<ConstraintViolation<User>> constraintViolations = validator.validate(user);
//Show errors
if (constraintViolations.size() > 0) {
for (ConstraintViolation<User> violation : constraintViolations) {
System.out.println(violation.getMessage());
}
} else {
System.out.println("Valid Object");
}
}
}
I have to write JUnit test cases for the Custom Validator class. I explored hibernate validator docs but couldn't find a way to invoke custom validator method through JUnit. Can someone please help to write JUnit test cases for above scenario.
Your NameValidator has public methods, so you can instantiate an object and write unit tests like for any other public method.
A possible JUnit 5 test with Mockito can look like the following:
#ExtendWith(MockitoExtension.class)
class NameValidatorTest {
#Mock
private NameMatch nameMatch;
#Mock
private ConstraintValidatorContext constraintValidatorContext;
#Test
public void testIsValid() {
when(nameMatch.first()).thenReturn("firstname");
when(nameMatch.second()).thenReturn("lastname");
NameValidator nameValidator = new NameValidator();
nameValidator.initialize(nameMatch);
User user = new User();
user.setFirstname("Duke");
user.setLastname("Duke");
boolean result = nameValidator.isValid(user, constraintValidatorContext);
assertTrue(result);
}
}
Depending of what you need the ConstraintValidatorContext you might also want to mock methods or later verify that specific methods were invoked.
If you are not using JUnit 5, you can adjust the code to not the JUnit 5 MockitoExtension and create the mocks using Mockito.mock().
One way is definitely Mockito (as #rieckpil mentioned).
If you dont want that, and actually want to invoke the validator, you can have something like this:
#SpringBootTest
public class NameValidatorUnitTest {
#Test
public void whenExistingRootRole_thenFail()
{
AnnotationDescriptor<NameMatch> descriptor = new AnnotationDescriptor<NameMatch>( NameMatch.class );
AnnotationFactory.create( descriptor );
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
User user = new User();
user.setEmail("test#gmail.com");
user.setPhone("12345678");
Set<ConstraintViolation<User>> constraintViolations = validator.validate(user);
if (constraintViolations.size() > 0) {
for (ConstraintViolation<User> violation : constraintViolations) {
System.out.println(violation.getMessage());
}
} else {
System.out.println("Valid Object");
}
Assert.assertEquals(true, constraintViolations.size()>0);
}
}

Can't bind properties with #Bean and #ConfigurationProperties

I am reading configuration from properties file. Now I am having an error that I think is related to sequence of initialization of spring bean.
If I do private Map name = new HashMap<>(); It can be successfully load from properties file.
But now I am having Could not bind properties to ServiceNameConfig
I don't know why this happen and how to deal with it.
#ConfigurationProperties(prefix = "amazon.service")
#Configuration
#EnableConfigurationProperties(ServiceNameConfig.class)
public class ServiceNameConfig {
//If I do private Map<String, String> name = new HashMap<>(); It can be successfully load from properties file.
private Map<String, String> name;
#Bean(value = "serviceName")
public Map<String, String> getName() {
return name;
}
public void setName(Map<String, String> name) {
this.name = name;
}
}
its usage;
#Autowired
#Qualifier("serviceName")
Map<String, String> serviceNameMap;
You can replace your config class to be like this (simpler);
#Configuration
public class Config {
#Bean
#ConfigurationProperties(prefix = "amazon.service")
public Map<String, String> serviceName() {
return new HashMap<>();
}
}
For #ConfigurationProperties injection, you'd need to supply an empty instance of the bean object. Check more about it on baeldung
Or an alternative way, you can use a pojo class to handle the configuration. For example;
You have properties like;
amazon:
service:
valueA: 1
valueB: 2
details:
valueC: 3
valueD: 10
and you can use a pojo like the following;
class Pojo {
private Integer valueA;
private Integer valueB;
private Pojo2 details;
// getter,setters
public static class Pojo2 {
private Integer valueC;
private Integer valueD;
// getter,setters
}
}
and use it in the config class like;
#Configuration
public class Config {
#Bean
#ConfigurationProperties(prefix = "amazon.service")
public Pojo serviceName() {
return new Pojo();
}
}

Spring #ConfigurationProperties for multiple properties returning empty

application.properties file contains properties that have sub properties:
status.available=00, STATUS.ALLOWED
status.forbidden=01, STATUS.FORBIDDEN
status.authdenied=05, STATUS.AUTH_DENIED
The idea was to get those properties into the application like this:
#Configuration
#ConfigurationProperties(prefix = "status")
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE)
public class StatusProperties {
private Map <String, List <String>> statusMapping;
public Map <String, List <String>> getStatusMapping () {
return statusMapping;
}
public void setStatusMapping (Map <String, List <String>> statusMapping) {
this.statusMapping = statusMapping;
}
}
The problem is that this Map is returned empty. I must be doing something wrong. Maybe this is not even possible in Spring to do like this?
I'm not sure about your choice regarding the data type and its assignment. I'd suggest you to rethink this design.
To your main question:
Spring can't know, that status.* should be mapped to private Map <String, List <String>> statusMapping;. Also as your class is named *properties, It seems that you don't want it to be a #Configuration class. Consider the following pattern:
First, create a properties class to hold the properties:
#ConfigurationProperties(prefix = "status")
public class StatusProperties {
private Map.Entry<Integer, String> available;
private Map.Entry<Integer, String> forbidden;
private Map.Entry<Integer, String> authdenied;
public Map.Entry<Integer, String> getAvailable() {
return available;
}
public void setAvailable(Map.Entry<Integer, String> available) {
this.available = available;
}
public Map.Entry<Integer, String> getForbidden() {
return forbidden;
}
public void setForbidden(Map.Entry<Integer, String> forbidden) {
this.forbidden = forbidden;
}
public Map.Entry<Integer, String> getAuthdenied() {
return authdenied;
}
public void setAuthdenied(Map.Entry<Integer, String> authdenied) {
this.authdenied = authdenied;
}
}
Now, your IDE should be able to read the docs from the setters while editing application.properties and check the validity. Spring can autowire the fields and automatically create the correct data types for you.
Consider mapping the Entries to a Map (Or, as I already told, change the design)
Now, you can use this properties class in your configuration:
#Configuration
#EnableConfigurationProperties(StatusProperties.class)
public class StatusConfiguration {
#Bean
public MyBean myBean(StatusProperties properties) {
return new MyBean(properties);
}
}
I found the solution:
application.properties:
app.statuses[0].id=00
app.statuses[0].title=STATUS.ALLOWED
app.statuses[1].id=01
app.statuses[1].title=STATUS.FORBIDDEN
app.statuses[2].id=02
app.statuses[2].title=STATUS.CONTRACT_ENDED
Properties.java
#Component
#ConfigurationProperties(prefix = "app")
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE)
public class StatusProperties {
private List<Status> statuses = new ArrayList<>();
public List <Status> getStatuses () {
return statuses;
}
public void setStatuses (List <Status> statuses) {
this.statuses = statuses;
}
public static class Status {
private String id;
private String title;
public String getId () {
return id;
}
public void setId (String id) {
this.id = id;
}
public String getTitle () {
return title;
}
public void setTitle (String title) {
this.title = title;
}
}
}

Cant instantiate class when using managed property

I'm learning Java 6 EE and I have a simple web app.
I have UserBean class that uses CurrencyManager class. CurrencyManager is application scoped and is a managed bean. UserBean is managed bean and session scoped.
Here is my UserBean:
#ManagedBean
#SessionScoped
public class UserBean implements Serializable{
private String username;
private ArrayList<Money> ownedMoney;
private CurrencyManager currencyManager;
private BigDecimal credits;
public UserBean() {
currencyManager = new CurrencyManager();
username = "User";
ownedMoney = new ArrayList<>();
ownedMoney.add(new Money(new BigDecimal(15000), currencyManager.getCurrency("CZK")));
ownedMoney.add(new Money(new BigDecimal(100), currencyManager.getCurrency("USD")));
credits = new BigDecimal(150);
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public BigDecimal getCredits() {
return credits;
}
public void setCredits(BigDecimal credits) {
this.credits = credits;
}
public ArrayList<Money> getOwnedMoney() {
return ownedMoney;
}
public void setOwnedMoney(ArrayList<Money> ownedMoney) {
this.ownedMoney = ownedMoney;
}
public CurrencyManager getCurrencyManager() {
return currencyManager;
}
public void setCurrencyManager(CurrencyManager currencyManager) {
this.currencyManager = currencyManager;
}
}
And here my CurrencyManager:
#ManagedBean(name = "currencyManager")
#ApplicationScoped
public class CurrencyManager {
private HashMap<String, Currency> currencies;
public CurrencyManager() {
this.currencies = new HashMap<>();
currencies.put("CZK", new Currency("CZK", new BigDecimal("0.0503")));
currencies.put("GBP", new Currency("GBP", new BigDecimal("0.59")));
currencies.put("EUR", new Currency("EUR", new BigDecimal("1.38")));
currencies.put("USD", new Currency("USD", new BigDecimal("1.0")));
}
public Currency getCurrency(String name){
return currencies.get(name);
}
public java.util.Collection<Currency> getCurrencies() {
return currencies.values();
}
public void setCurrencies(HashMap<String, Currency> currencies) {
this.currencies = currencies;
}
}
The code I posted works fine as is. However I don't want to instantiate CurrencyManager in my UserBean class - that's is why I made it ApplicationScoped, since it should be available at all times.
If I remove the instantiation (first line in UserBean constructor) and change declaration to:
#ManagedProperty(value = "#{currencyManager}")
private CurrencyManager currencyManager;
then the first page that queries ownedMoney property in UserBean throws javax.servlet.ServletException: Cant instantiate class: model.UserBean. with root cause of NullPointerException. GlassFish log showed that the NullPtr occurs in UserBean constructor, when I call getCurrency on currencyManager, here:
ownedMoney.add(new Money(new BigDecimal(15000), currencyManager.getCurrency("CZK")));
Can you tell me what I'm doing wrong?
I just came across the same problem, and found out by chance, that it is not working, if I try with firefox (actually icedove under linux), but well working, if I try with the eclipse build-in browser.
Even so this does not make sense to me, have you tried with different browsers already?

Categories