How to read properties value from interface in java - java

I would like to read testvalue from properties file in java, can someone please help
Following is my code
package com.test;
import java.util.Map;
import com.amazonaws.services.lambda.invoke.LambdaFunction;
public interface LambdaPDFService {
#LambdaFunction(functionName="**testValue**")
Map<String, String> setResponse(LambdaPdfRequest input);
}
I have tried below annotation, and e.tc but it's not working
#PropertySources(#PropertySource("classpath:application.properties"))

You can't read in an interface.
You can read in a concrete class annotated as a #Service (or #Component). Assuming application.properties includes lambda.function.name=foo
#Service
public class LambdaPDFServiceImpl implements LambdaPDFService {
#LambdaFunction(functionName="${lambda.function.name}")
Map<String, String> setResponse(LambdaPdfRequest input) {
// TODO
}
}

Related

Unable to get annotations from Java classes when trying to autowire multiple implementations

In a Java 11/Spring REST API project I have an interface with multiple implementations. I want to choose the implementation in the configuration (in this case, application.yml file):
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Retention(RUNTIME)
#Target(TYPE)
public #interface PickableImplementation {
// This id will match an entry in the config file
public String id() default "";
}
So I have the two possible "pickable" implementations:
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import com.mycompany.api.util.PickableImplementation;
#Component
#Service
#PickableImplementation(id = "s3")
public class BatchProcessServiceS3 implements BatchProcessService {
// Some implementation biz logic
}
// In another file, with the same imports:
#Component
#Service
#PickableImplementation(id = "azure")
public class BatchProcessServiceAzure implements BatchProcessService {
// Another implementation biz logic
}
In the consumer class which in this case is a Controller, I try to pick the desired implementation:
import com.mycompany.api.util.PickableImplementation;
import java.util.List;
#RestController
#RequestMapping("/batch")
public class BatchController {
#Autowired private Environment env;
private BatchProcessService batchProcessService;
private final List<BatchProcessService> batchProcessImplementations;
public BatchController(List<BatchProcessService> batchProcessImplementations,
Environment environment){
this.env = environment;
var activeSvcImplementationId = env.getRequiredProperty("buckets-config.active");
this.batchProcessImplementations = batchProcessImplementations;
for (var batchProcessService : this.batchProcessImplementations) {
if(batchProcessService.getClass().isAnnotationPresent(PickableImplementation.class)) {
// Verify the id, compare with the one from config file, etc.
}
}
}
}
Expected behavior: inside the loop, I expected to get the annotations of each implementation, traverse the list, verify if it matches with the one with the application.yml and if it does, pick it to populate the service layer (private BatchProcessService batchProcessService).
Actual behavior: not only the isAnnotationPresent() method returns false, but also if I try getAnnotations() I get an empty array, like there are no annotations in the class. And besides my custom one, there are at least two additional annotations (Component, Service and others related to logging and the like).
As another puzzling detail, if I run getAnnotations() on the qualified name of the class in the middle of a debugging session, the annotations are present. But in that very moment, running that method on the elements on the list return 0 annotations.
I've run out of ideas, has anyone tried this same combination of autowiring several implementations and at the same time, relying in custom annotations?
Additional references:
Custom Annotations in Java:
https://www.baeldung.com/java-custom-annotation
Autowired:
https://www.baeldung.com/spring-autowire
Useful answer on autowiring multiple implementations:
https://stackoverflow.com/a/51778396/6315428

#PropertySource(factory=...) breaks #SpringBootTest when loading META-INF/build-info.properties

I am trying to setup a custom #ConfigurationProperties class loaded from a HOCON syntax .conf file.
I have a Class annotated with #PropertySource(factory=TypesafePropertySourceFactory.class, value = "classpath:app.conf")
#Configuration
#ConfigurationProperties(value = "app.server")
#PropertySource(factory = TypesafePropertySourceFactory.class, value = "classpath:app.conf")
public class ServerProperties {
public int port;
}
and a simple test class:
#SpringBootTest
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SomeTest {
#Test
public void someCoolTest() {/* ... */}
// ...
}
When i run my junit test runner, i get the following error:
Caused by: com.typesafe.config.ConfigException$BadPath: path parameter: Invalid path 'spring.info.build.location:classpath:META-INF/build-info.properties': Token not allowed in path expression: ':' (you can double-quote this token if you really want it here)
at com.typesafe.config.impl.PathParser.parsePathExpression(PathParser.java:155) ~[config-1.4.0.jar:1.4.0]
at com.typesafe.config.impl.PathParser.parsePathExpression(PathParser.java:74) ~[config-1.4.0.jar:1.4.0]
at com.typesafe.config.impl.PathParser.parsePath(PathParser.java:61) ~[config-1.4.0.jar:1.4.0]
...
If i uncomment the #PropertySource line on the ServerProperties class, the tests proceed normally. It seems strange to me that my custom PropertySourceFactory gets in the way of the default .properties file resolution process.
PropertySource and Factory classes
// TypesafeConfigPropertySource.java
import com.typesafe.config.Config;
import org.springframework.core.env.PropertySource;
public class TypesafeConfigPropertySource extends PropertySource<Config> {
public TypesafeConfigPropertySource(String name, Config source) {
super(name, source);
}
#Override
public Object getProperty(String path) {
if (source.hasPath(path)) {
return source.getAnyRef(path);
}
return null;
}
}
// TypesafePropertySourceFactory.java
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import java.io.IOException;
import java.util.Objects;
public class TypesafePropertySourceFactory implements PropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
Config config = ConfigFactory.load(Objects.requireNonNull(resource.getResource().getFilename())).resolve();
String safeName = name == null ? "typeSafe" : name;
return new TypesafeConfigPropertySource(safeName, config);
}
}
Am I missing something fundamental about configuring custom property resource factories, or is this a bug?
Versions
Spring boot 2.3.4
Junit Jupiter 5.6.2
Maybe you can also solve it with the use of a ContextInitializer as suggested in the answer here:
Spring Environment backed by Typesafe Config
TL;DR
Return null if you cannot process the path in your custom impl
public class TypesafeConfigPropertySource extends PropertySource<Config> {
// ...
#Override
public Object getProperty(String path) {
try {
if (source.hasPath(path)) {
return source.getAnyRef(path);
}
} catch(ConfigException.BadPath ignore) {
}
return null;
}
// ...
}
Explanation
I am making educated guesses, but functionally this appears supported by the way the code behaves
the most likely scenario here is the resolution order will consider our custom implementation before any default implementation. The method used in our implementation will error out with any path containing a ":" and "[" as the error occurs in the check for the path's existence.
I'm simply wrapping the BadPath exception in order to catch any problem and then returning null to signify no match.

Is it possible to have only 1 implementation of IAnnotationTransformer in a project

Can we have more than 1 implementation of IAnnotationTransformer in a project that is using TestNG?
I'm using TestNg version 7.0.0.
TestNG currently lets you wire in ONLY ONE implementation of IAnnotationTransformer. If you try to plug in multiple ones of them, the last one that got added is what will get invoked.
There's an open issue that is tracking this ask. See GITHUB-1894.
As an alternative you can build your own composite IAnnotationTransformer which can be used to iterate through all the other annotation transformer instances. Here's a sample (Its available in the above mentioned github link)
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import org.testng.IAnnotationTransformer;
import org.testng.annotations.ITestAnnotation;
import org.testng.collections.Lists;
import org.testng.internal.ClassHelper;
public class CompositeTransformer implements IAnnotationTransformer {
private static final String JVM_ARGS =
"com.rationaleemotions.github.issue1894.Listener1, com.rationaleemotions.github.issue1894.Listener2";
private List<IAnnotationTransformer> transformers = Lists.newArrayList();
public CompositeTransformer() {
// Ideally this would get a value from the command line. But just for demo purposes
// I am hard-coding the values.
String listeners = System.getProperty("transformers", JVM_ARGS);
Arrays.stream(listeners.split(","))
.forEach(
each -> {
Class<?> clazz = ClassHelper.forName(each.trim());
IAnnotationTransformer transformer =
(IAnnotationTransformer) ClassHelper.newInstance(clazz);
transformers.add(transformer);
});
}
#Override
public void transform(
ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) {
for (IAnnotationTransformer each : transformers) {
each.transform(annotation, testClass, testConstructor, testMethod);
}
}
}

Guice assisted inject with several factory methods and null parameters

I have this interface and simple implementation:
public interface Data {
}
import java.nio.file.Path;
import javax.annotation.Nullable;
import javax.inject.Inject;
import com.google.inject.assistedinject.Assisted;
public class SimpleData implements Data {
#Inject
public SimpleData(#Assisted #Nullable Path path) {
}
}
I want to generate a Factory with different methods using guice.
import java.nio.file.Path;
import javax.annotation.Nullable;
public interface Factory {
Data create();
Data load(#Nullable Path path);
}
But the following module configuration:
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.assistedinject.FactoryModuleBuilder;
public class Main {
public static void main(String[] args) {
Injector injector = Guice.createInjector(
binder -> binder.install(
new FactoryModuleBuilder().implement(Data.class, SimpleData.class)
.build(Factory.class)));
Data data = injector.getInstance(Factory.class).create();
}
}
fails:
Exception in thread "main" com.google.inject.CreationException: Guice creation errors:
1) No implementation for java.nio.file.Path annotated with #com.google.inject.assistedinject.Assisted(value=) was bound.
while locating java.nio.file.Path annotated with #com.google.inject.assistedinject.Assisted(value=)
for parameter 0 at SimpleData.<init>(SimpleData.java:10)
at Factory.create(Factory.java:1)
at com.google.inject.assistedinject.FactoryProvider2.initialize(FactoryProvider2.java:539)
at com.google.inject.assistedinject.FactoryModuleBuilder$1.configure(FactoryModuleBuilder.java:335)
1 error
at com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:435)
at com.google.inject.internal.InternalInjectorCreator.injectDynamically(InternalInjectorCreator.java:175)
at com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:109)
at com.google.inject.Guice.createInjector(Guice.java:95)
at com.google.inject.Guice.createInjector(Guice.java:72)
at com.google.inject.Guice.createInjector(Guice.java:62)
at Main.main(Main.java:9)
I solved my problem using the annotation #AssistedInject. Quote from the javadoc:
When used in tandem with FactoryModuleBuilder, constructors annotated with #AssistedInject indicate that multiple constructors can be injected, each with different parameters.
So i add the annotation and a constructor to the SimpleData class:
public class SimpleData implements Data {
#AssistedInject
public SimpleData(#Assisted Path path) {
}
#AssistedInject
public SimpleData() {
}
}
i removed the #Nullable annotation from the factory:
import java.nio.file.Path;
public interface Factory {
Data create();
Data load(Path path);
}
#Nullable does not mean that if you don't have a binding, then null will be injected. It only allows writing bindings to null. If you don't have a binding and there is no applicable JIT-binding, then injection will fail.
Your factory's create() method requires Guice to find an #Assisted Path binding, but it obviously can't find it since you've never created one, so it fails.
Honestly, I'm not sure if there is a clean way to implement such defaulting. Ideally you should mark Path with some binding annotation and add a default binding to null for it, but #Assisted already is a binding annotation, and it is not possible to have multiple binding annotations on a single injection point. You can try creating a binding for #Assisted Path:
binder.bind(Path.class).annotatedWith(Assisted.class).toInstance(null);
However, I'm not sure if it would work because Assisted can be special to Guice. And even if it will work, it is not very clean - there may be conflicts with other assisted factories accepting Paths.
I would have Guice implement some kind of internal factory interface, then expose something else. Like this:
interface InternalFactory {
Data load(#Nullable Path path);
}
public interface Factory {
Data load();
Data load(#Nullable Path path);
}
class FactoryImpl implements Factory {
#Inject InternalFactory internalFactory;
#Override
public Data load() {
return load(null); // Pass your defaults here
}
#Override
public Data load(#Nullable Path path) {
// Sadly you'll have to explicitly forward arguments here, but it's not
// too bad IMO
return internalFactory.load(path);
}
}
public class MyModule extends AbstractModule {
#Override
protected void configure() {
install(new FactoryModuleBuilder()
.implement(Data.class, SimpleData.class)
.build(InternalFactory.class));
bind(Factory).to(FactoryImpl.class);
}
}

How to implement #RequestMapping custom properties

As an example, take subdomain mapping.
This article: Managing multiple Domain and Sub Domain on Google App Engine for Same Application
recommends to resolve subdomain on Filter and assign variable to ServletRequest headers.
Then the mapping will look like this:
#RequestMapping(value = "/path", headers="subdomain=www")
public String subsiteIndexPage(Model model,HttpServletRequest request) { ... }
If we'd like to create custom #RequestMapping property, such as subdomain, eg. to create mapping like this:
#RequestMapping(value = "/some/action", subdomain = "www")
public String handlerFunction(){ ... }
we should override #RequestMapping #interface definition and override RequestMappingHandlerMapping protected methods, with our own implementation
(as stated on JIRA: "Allow custom request mapping conditions SPR-7812").
Is it right? Can anybody provide a hint, how to achieve this functionality?
Idea 1:
As suggested on original jira thread, is to create own implementation of RequestCondition
There is an project which uses this solution available on github: https://github.com/rstoyanchev/spring-mvc-31-demo/
And related SO question: Adding custom RequestCondition's in Spring mvc 3.1
Maybe mapping like #Subdomain("www") for both Type and Method, is possible solution?
Link to same question on forum.springsource.com
I've created solution based on referenced spring-mvc-31-demo
This solution can be used to map only single RequestCondition as of now. I've created two Issues to notify, this should be changed:
https://github.com/rstoyanchev/spring-mvc-31-demo/issues/5
https://jira.springsource.org/browse/SPR-9350
This solution uses custom #RequestCondition feature of Spring 3.1.1.RELEASE platform
USAGE
Example 1:
#Controller
#SubdomainMapping(value = "subdomain", tld = ".mydomain.com")
class MyController1 {
// Code here will be executed only on address match:
// subdomain.mydomain.com
}
Example 2:
#Controller
class MyController2 {
#RequestMapping("/index.html")
#SubdomainMapping("www")
public function index_www(Map<Object, String> map){
// on www.domain.com
// where ".domain.com" is defined in SubdomainMapping.java
}
#RequestMapping("/index.html")
#SubdomainMapping("custom")
public function index_custom(Map<Object, String> map){
// on custom.domain.com
// where ".domain.com" is defined in SubdomainMapping.java
}
}
We need three files
SubdomainMapping.java
SubdomainRequestCondition.java
SubdomainRequestMappingHandlerMapping.java
SubdomainMapping.java
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface SubdomainMapping {
/**
* This param defines single or multiple subdomain
* Where the Method/Type is valid to be called
*/
String[] value() default {};
/**
* This param defines site domain and tld
* It's important to put the leading dot
* Not an array, so cannot be used for mapping multiple domains/tld
*/
String tld() default ".custom.tld";
}
SubdomainRequestCondition.java
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
public class SubdomainRequestCondition implements
RequestCondition<SubdomainRequestCondition> {
private final Set<String> subdomains;
private final String tld;
public SubdomainRequestCondition(String tld, String... subdomains) {
this(tld, Arrays.asList(subdomains));
}
public SubdomainRequestCondition(String tld, Collection<String> subdomains) {
this.subdomains = Collections.unmodifiableSet(new HashSet<String>(
subdomains));
this.tld = tld;
}
#Override
public SubdomainRequestCondition combine(SubdomainRequestCondition other) {
Set<String> allRoles = new LinkedHashSet<String>(this.subdomains);
allRoles.addAll(other.subdomains);
return new SubdomainRequestCondition(tld, allRoles);
}
#Override
public SubdomainRequestCondition getMatchingCondition(
HttpServletRequest request) {
try {
URL uri = new URL(request.getRequestURL().toString());
String[] parts = uri.getHost().split(this.tld);
if (parts.length == 1) {
for (String s : this.subdomains) {
if (s.equalsIgnoreCase(parts[0])) {
return this;
}
}
}
} catch (Exception e) {
e.printStackTrace(System.err);
}
return null;
}
#Override
public int compareTo(SubdomainRequestCondition other,
HttpServletRequest request) {
return org.apache.commons.collections.CollectionUtils.removeAll(other.subdomains, this.subdomains).size();
}
}
SubdomainRequestMappingHandlerMapping.java
import java.lang.reflect.Method;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
public class CustomRequestMappingHandlerMapping extends
RequestMappingHandlerMapping {
#Override
protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
SubdomainMapping typeAnnotation = AnnotationUtils.findAnnotation(
handlerType, SubdomainMapping.class);
return createCondition(typeAnnotation);
}
#Override
protected RequestCondition<?> getCustomMethodCondition(Method method) {
SubdomainMapping methodAnnotation = AnnotationUtils.findAnnotation(
method, SubdomainMapping.class);
return createCondition(methodAnnotation);
}
private RequestCondition<?> createCondition(SubdomainMapping accessMapping) {
return (accessMapping != null) ? new SubdomainRequestCondition(
accessMapping.tld(), accessMapping.value()) : null;
}
}
Instalation
IMPORTANT: So far, it is not possible to use this solution with XML element
<mvc:annotation-driven />, see JIRA https://jira.springsource.org/browse/SPR-9344 for explanation
You have to register custom MappingHandler bean, pointing at this custom implementation SubdomainRequestMappingHandlerMapping class
You have to set it's order to be lower than default RequestMappingHandlerMapping
OR
Replace the registered RequestMappingHandlerMapping (possibly on order=0)
For more wide explanation on implementing this solution, see the related github project
That's correct, but that would be too complicated. You'd better check the Host header, whether it contains a given subdomain.
But you should not really need this more than once or twice, so you can also do it manually in the method body. If you really need it in many places, it would be an odd requirement.

Categories

Resources