Injecting list of strings in Guice - java

I am trying to inject one arraylist of Strings in Guice. For that I am trying to construct list as following in module, which I can inject later into any class:
Multibinder<String> myList =
Multibinder.newSetBinder(binder(), String.class);
myList.addBinding().to("Test1");
myList.addBinding().to("Test2");
In the above line I am getting following error:
The method to(Class<? extends String>) in the type LinkedBindingBuilder<String> is not applicable for the arguments (String)
In this context, I have found this: Guice : Inject an ArrayList of Strings, but I think given solution does not fit my use case.

Use .toInstance()
myList.addBinding().toInstance("Test1");
myList.addBinding().toInstance("Test2");`

I realize this is an older post, but I've been unsuccessful at finding a solution until today. I've wanted to inject a list of comma-delimited strings defined in a property file in a generic way without having to explicitly create #Named providers for every property that needed to be represented as a list of Strings.
Today I found this post (Injecting a List of Strings From a Properties File Using Guice). The relevant part for me was this line in my configure method:
#Override
protected void configure() {
...
binder().convertToTypes(Matchers.only(new TypeLiteral<List<String>>() {
}), (String value, TypeLiteral<?> toType) -> Arrays.asList(value.split(",")));
...
}
I didn't use DefaultListDelimiterHandler as the original solution did, but instead just did a standard string split converted to a List. Now from a property file, I can have something like:
testList=one,two,three
And I can inject it using:
#Inject
#Named("testList")
List<String> testList;

Related

ElasticSearch 5 adding context to SuggestionBuilders

I am working on ES5 through java, and am trying to add context to a CompletionSuggestionBuilder. I have a map of String objects that need to be added. The code I have so far is -
Map<String, String> context = ...
CompletionSuggestionBuilder csb = SuggestBuilders.completionSuggestion(field).text(value).size(count);
How do I add context objects to csb? I think the method to use is -
csb.contexts(Map<String, List<? extends ToXContent>> queryContexts)
But I don't know how to get from my map to the map to pass as arguments to the contexts method.
You can create Map<String, List<? extends ToXContent>> like this;
Collections.singletonMap("cat", Arrays.asList(CategoryQueryContext.builder().setCategory("cat0").setBoost(3).build(), CategoryQueryContext.builder().setCategory("cat1").build()))
I think currently supported types that extend ToXContext are CategoryQueryContext and GeoQueryContext
The strange thing here is that if I create a local variable and pass it to the contexts it does not work. So, I just passed it directly to the contexts it does work.
Full example would be like this:
CompletionSuggestionBuilder prefix = SuggestBuilders.completionSuggestion(FIELD).prefix("sugg").contexts(Collections.singletonMap("cat", Arrays.asList(CategoryQueryContext.builder().setCategory("cat0").setBoost(3).build(), CategoryQueryContext.builder().setCategory("cat1").build())));
It is all written in their test cases. You can take a look at it:
https://github.com/elastic/elasticsearch/blob/master/core/src/test/java/org/elasticsearch/search/suggest/ContextCompletionSuggestSearchIT.java#L290
Hope it helps.

CDI: Dynamical injection of a group of classes how to?

I need to dynamically Inject a variable group of classes in my application. The purpose is, as the application grows, only have to add more classes inheriting the same interface. This is easy to do with tradicional java as I just need to search for all classes in a package and perform a loop to instantiate them. I want to do it in CDI. For example:
public MyValidatorInterface {
public boolean validate();
}
#Named
MyValidator1 implements MyValidatorInterface
...
#Named
MyValidator2 implements MyValidatorInterface
...
Now the ugly non real java code just to get the idea of what I want to do:
public MyValidatorFactory {
for (String className: classNames) {
#Inject
MyValidatorInterface<className> myValidatorInstance;
myValidatorInstance.validate();
}
}
I want to loop over all implementations found in classNames list (all will be in the same package BTW) and Inject them dynamically so if next week I add a new validator, MyValidator3, I just have to code the new class and add it to the project. The loop in MyValidatorFactory will find it, inject it and execute the validate() method on the new class too.
I have read about dynamic injection but I can't find a way to loop over a group of class names and inject them just like I used to Instantiate them the old way.
Thanks
What you are describing is what Instance<T> does.
For your sample above, you would do:
`#Inject Instance<MyValidatorInterface> allInstances`
Now, allInstances variable contains all your beans which have the given Type (MyValidatorInterface). You can further narrow down the set by calling select(..) based on qualifiers and/or class of bean. This will again return an Instance but with only a subset of previously fitting beans. Finally, you call get() which retrieves the bean instance for you.
NOTE: if you call get() straight away (without select) in the above case, you will get an exception because you have two beans of given type and CDI cannot determine which one should be used. This is implied by rules of type-safe resolution.
What you most likely want to know is that Instance<T> also implements Iterable so that's how you get to iterate over the beans. You will want to do something like this:
#Inject
Instance<MyValidatorInterface> allInstances;
public void validateAll() {
Iterator<MyValidatorInterface> iterator = allInstances.iterator();
while (iterator.hasNext()) {
iterator.next().callYourValidationMethod();
}}
}

Map a collection with parameter with mapstruct

To map a certain object with mapstruct I need some custom post processing which needs an additional parameter to do it's work:
#Mapper
public abstract class AlertConfigActionMapper {
#Mappings({ #Mapping(target = "label", ignore = true)})
public abstract AlertConfigActionTO map (AlertConfigAction action, Locale userLanguage);
#AfterMapping
public void setLabel (AlertConfigAction action, #MappingTarget AlertConfigActionTO to, Locale userLanguage) {
for (AlertConfigActionLabel label : action.getAlertConfigActionLabels()) {
if (label.getLanguage().equals(userLanguage)) {
to.setLabel(label.getLabel());
break;
} else if (label.getLanguage().equals(Locale.ENGLISH)) {
to.setLabel(label.getLabel());
}
}
}
}
This works just fine.
The problem starts when I add following method to this mapper:
public abstract ArrayList<AlertConfigActionTO> mapList (List<AlertConfigAction> actions, Locale userLanguage);
I need to pass this parameter (userLanguage) as well but mapstruct seems to 'break down' in this case: I generates following code for this part (which naturally gives a compilation error):
#Override
public List<AlertConfigActionTO> mapList(List<AlertConfigAction> actions, Locale userLanguage) {
if ( actions == null && userLanguage == null ) {
return null;
}
List<AlertConfigActionTO> list = new List<AlertConfigActionTO>();
return list;
}
I'm sure it is related to the parameter since if I remove it (from all mapping methods) then the mapList method is generated correctly.
What is needed to be done to allow custom parameters in this case?
What you describe is not possible (yet). Could you open a feature request in our issue tracker? We should provide means of denoting parameters as some sort of "context" which is passed down the call stack.
As a work-around for the time being, you might take a look at using a ThreadLocal which you set before invoking the mapping routine and which you access in your after-mapping customization. It's not elegant - and you need to make sure to clean up the thread local to avoid memory leaks - but it should do the trick.
I know that this question is quiet old, but I run into this issue, and starting at version 1.2 of mapstruct you can resolve it using #Context
So declaring the mapping for the list need to be like this :
public abstract ArrayList<AlertConfigActionTO> mapList (List<AlertConfigAction> actions, #Context Locale userLanguage);
Now, you juste need to add another non abstract mapping like this :
public AlertConfigActionTO mapConcrete (AlertConfigAction action, #Context Locale userLanguage){
return map (action, userLanguage);
}
I don't think it is possible. At least not that way. Problem is that you prepare interface/abstract class - and rest is done by the engine. And that engine expects methods with one parameter... There are decorators, but they go the same way. I would try to inject language. Create bean, mark it as session scoped, and find out. With Spring, you would use ScopedProxyMode for that... Not sure how that goes with CDI.
Other option is more workaround, then solution - maybe that AlertConfigAction can pass that information?

How is Spring's DataBinder used to convert a plain parameter value?

I've been digging through the Spring DataBinder code and docs while answering this question and i've noticed the DataBinder constructor docs mentionioning :
target - the target object to bind onto (or null if the binder is just
used to convert a plain parameter value)
I've been searching around and haven't found such a usage and it really made me curious. Would appreciate any insight related to :
How would such a data binder be used with a null target to convert a plain parameter value?
Or what does it actually mean to convert a plain parameter value in this context?
Is it also applicable to Spring MVC? (since i noticed it mentioned in WebDataBinder's constructor docs as well).
I have been done some digging in the Spring Framework source, searching for usages with null parameter as you described. Since I was not aware of this kind of usage either, looking at the testcases to understand things better was my way to go.
The test class for DataBinder is (not surprisingly) DataBinderTests.
I am going to paste a usage example here, along with the link to github where I found the code, for reference:
For your first question, the answer seems if you use a DataBinder with null constructor parameter, it means that you just want to use the conversion facility, without the data binding mechanism (since we have not passed an object to bind values to).
This pretty explanatory testcase shows this type of usage, creating a DataBinder with null, setting a DefaultFormattingConversionService to the dataBinder and registering a custom editor after that.
You can add a custom converter with the addConverter method if you want a different String representation after converting your bean.
#Test
public void testConversionWithInappropriateStringEditor() {
DataBinder dataBinder = new DataBinder(null);
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
dataBinder.setConversionService(conversionService);
dataBinder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
NameBean bean = new NameBean("Fred");
assertEquals("ConversionService should have invoked toString()", "Fred", dataBinder.convertIfNecessary(bean, String.class));
conversionService.addConverter(new NameBeanConverter());
assertEquals("Type converter should have been used", "[Fred]", dataBinder.convertIfNecessary(bean, String.class));
}
For reference, here is the code of NameBeanConverter (also from the test class)
public static class NameBeanConverter implements Converter<NameBean, String> {
#Override
public String convert(NameBean source) {
return "[" + source.getName() + "]";
}
}
Source: https://github.com/spring-projects/spring-framework/blob/d5ee787e1e6653257720afe31ee3f8819cd4605c/spring-context/src/test/java/org/springframework/validation/DataBinderTests.java#L598-L609
I think the above explanation was an answer for the first two questions:
How would such a data binder be used with a null target to convert a plain parameter value?
Or what does it actually mean to convert a plain parameter value in this context?
For your third question, unfortunately, I have not found any usable testcase like above, but going through the WebDataBinder's code make me think of there is no "added value" compared to DataBinder in terms of a null constructor parameter, so you can use the conversion facility through the WebDataBinder as well.
You probably know that you can bind your web request fields to your backing bean, adding conversion (e.g. converting a particularly formatted date to an actual Date field object on the backing bean) etc etc, but I don't want to describe this in more detail as this was not your point with the question I guess.
Lastly, I found this article very useful about how the WebDataBinder could be used: http://www.intertech.com/Blog/spring-frameworks-webdatabinder/
You can think of DataBinder as filter or handler that all request parameters go thorough before they get consumed. To make use of the DataBinder functionality you need to implement a method in your controller class with "#InitBinder" annotation. For example:
#InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
// bind empty strings as null
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}
As you see, all you need to do in the method is register CustomEditor for a specific data type.
In Spring MVC, a DataBinder with a null target is automatically used in a case like this :
#RequestMapping(method = RequestMethod.GET)
public String showHome(#RequestParam("date") Date date) {
return "home";
}
In this case, the created DataBinder has an objectName "date" and a null target.

How to use Custom type for #PathParam?

I want to use non spring bean class object as parameter for jersey web service class method. But it is giving missing dependency error at build time.
My code is:
#Component
#Path("/abcd")
public class ActorServiceEndpoint {
#POST
#Path("/test/{nonspringBean}")
#Produces(MediaType.APPLICATION_XML)
public void addActor(#PathParam("nonspringBean") MyNonSpringBeanClass nonspringBean){
}
}
The thing is path parameters come in String form. As per the specification, if we want the have a custom type be injected as a #PathParam, the custom class, should have one of three things:
A public static valueOf(String param) that returns the type
A public static fromString(String param) that returns the type
Or a public constructor that accepts a String
Another option implement a ParamConverter. You can see an example here.
If you don't own the class (it's a third-party class that you can't change) then your only option is to use the ParamConverter/ParamConverterProvider pair.
In either of these cases you'll want to construct the instance accordingly by parsing the String either in the constructor or in one of the above mentioned methods. After doing this, the custom type can be made a method parameter with the annotation.
The same holds true for other params, such as #FormParam, #HeaderParam, #QueryParam, etc.
It would help if you gave a bit more details of the error you're getting, but I see two problems with your code snippet:
The correct Spring annotation is #PathVariable, #PathParam is probably from another package. This doesn't apply as I guess you're using JAX-RS, not Spring annotations.
I'm not sure what converters are applied to path variables, but in any case it would need to have one for MyNonSpringBeanClass. I would take a String parameter and then instantiate MyNonSpringBeanClass myself in the function body.

Categories

Resources