My enum:
enum ServiceEnvironment implements ServiceEnvironmentStatics {
Local(LOCAL_URI),
Sandbox(SANDBOX_URI),
Production(PRODUCTION_URI)
private final String uri
private ServiceEnvironment(String uri) {
super()
this.uri = uri
}
}
interface ServiceEnvironmentStatics {
static final String LOCAL_URI = "localhost:8080"
static final String SANDBOX_URI = "sandbox.myapi.example.com"
static final String PRODUCTION_URI = "myapi.example.com"
}
When I use this in code like so:
ServiceEnvironment env = ServiceEnvironment.Local
I get:
Caused by: groovy.lang.GroovyRuntimeException: Could not find matching constructor for: com.me.myapp.ServiceEnvironment(java.lang.String, java.lang.Integer, java.lang.String)
at groovy.lang.MetaClassImpl.selectConstructorAndTransformArguments1(MetaClassImpl.java:1490)
at groovy.lang.MetaClassImpl.selectConstructorAndTransformArguments(MetaClassImpl.java:1409)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.selectConstructorAndTransformArguments(ScriptBytecodeAdapter.java:234)
at com.me.myapp.ServiceEnvironment.$INIT(ServiceEnvironment.groovy)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite.invoke(StaticMetaMethodSite.java:43)
at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite.callStatic(StaticMetaMethodSite.java:99)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallStatic(CallSiteArray.java:53)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callStatic(AbstractCallSite.java:157)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callStatic(AbstractCallSite.java:173)
at com.me.myapp.ServiceEnvironment.<clinit>(ServiceEnvironment.groovy)
... 36 more
What's going on?
tl;dr: Remove the super() invocation from your enum constructor.
The Actual Answer: The sole superclass of all enum classes is java.lang.Enum, which does not have a no-argument constructor. Therefore the line super() is in error. The runtime error here is rather misleading, due to the nature of dynamic method/constructor dispatch in Groovy. If we were instead to try to compile a roughly equivalent enum class in Java:
public enum ServiceEnvironment {
Local("localhost:8080"),
Sandbox("sandbox.myapi.example.com"),
Production("myapi.example.com");
private final String uri;
private ServiceEnvironment(String uri) {
super();
this.uri = uri;
}
}
The compiler would directly tell us that the super() statement is the problem. My Java compiler says:
Cannot invoke super constructor from enum constructor ServiceEnvironment(String)
...which makes much more sense at compile-time than the message that Groovy is giving at runtime.
Related
I'm having the following code:
#Data
#Validated
#ConfigurationProperties
public class Keys {
private final Key key = new Key();
#Data
#Validated
#ConfigurationProperties(prefix = "key")
public class Key {
private final Client client = new Client();
private final IntentToken intentToken = new IntentToken();
private final Intent intent = new Intent();
private final OAuth oauth = new OAuth();
private final ResourceToken resourceToken = new ResourceToken();
#Valid #NotNull private String authorization;
#Valid #NotNull private String bearer;
...
}
}
That is an instance representing a properties file such as:
key.authorization=Authorization
key.bearer=Bearer
..
As I can have different sources for the properties (properties file, MongoDB, etc), I have a client that inherit from Keys as follow:
Properties files source
#Component
#Configuration
#Primary
#PropertySource("classpath:${product}-keys.${env}.properties")
//#JsonAutoDetect(fieldVisibility = Visibility.ANY)
public class CustomerKeysProperties extends Keys {
}
Mongo source
#Data
#EqualsAndHashCode(callSuper=true)
#Component
//#Primary
#Document(collection = "customerKeys")
public class CustomerKeysMongo extends Keys {
#Id
private String id;
}
I just select the source I want to use annotating the class with #Primary. In the example above, CustomerKeysProperties is the active source.
All this work fine.
The issue I have is when I try to convert an instance of CustomerKeysProperties into JSON, as in the code below:
#SpringBootApplication
public class ConverterUtil {
public static void main(String[] args) throws Exception {
SpringApplication.run(ConverterUtil.class, args);
}
#Component
class CustomerInitializer implements CommandLineRunner {
#Autowired
private Keys k;
private final ObjectMapper mapper = new ObjectMapper();
#Override
public void run(String... args) throws Exception {
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
//mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
String jsonInString = mapper.writeValueAsString(k);
System.out.println(jsonInString);
}
}
}
While k contains all the properties set, the conversion fails:
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: x.client.customer.properties.CustomerKeysProperties$$EnhancerBySpringCGLIB$$eda308bd["CGLIB$CALLBACK_0"]->org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor["advised"]->org.springframework.aop.framework.ProxyFactory["targetSource"]->org.springframework.aop.target.SingletonTargetSource["target"]->x.client.customer.properties.CustomerKeysProperties$$EnhancerBySpringCGLIB$$4fd6c568["CGLIB$CALLBACK_0"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1191)
And if I uncomment
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
as suggested in the logs, I have an infinite loop happening in Jackson causing a stackoverflow:
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:119)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:79)
at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serialize(IndexedListSerializer.java:18)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
..
Questions
At the end, I just want to provide an Util class than can convert a properties file in a JSON format that will be stored in MongoDB.
How can I solve this problem ?
Without passing through the object above, how can I transform a properties file into JSON ?
Can I save an arbitrary Java bean in MongoDB, with the conversion to JSON automagically done ?
The answer to any of the 3 questions above would be helpful.
Notes
To be noted that I use lombok. Not sure if this is the problem.
Another guess is that I'm trying to serialize a Spring managed bean and the proxy it involve cause jackson to not be able to do the serialization ? If so, what can be the turn-around ?
Thanks!
So found the problem:
jackson can't process managed bean.
The turn around was
try (InputStream input = getClass().getClassLoader().getResourceAsStream("foo.properties")) {
JavaPropsMapper mapper = new JavaPropsMapper();
Keys keys = mapper.readValue(input, Keys.class);
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
String res = ow.writeValueAsString(keys);
System.out.println(res);
} catch (IOException e) {
e.printStackTrace();
}
where Keys was the Spring managed bean I was injecting.
And:
JavaPropsMapper come from:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-properties</artifactId>
</dependency>
I am trying to mock the following functionality
public class ServerConnection{
public ServerConnection(ClientConnection cn) {
super(cn);
}
public void setUrl(String url) {
this.url = URLUtil.processURL(url);
try {
URL dst = new URL(this.url);
InputStream is = dst.openStream();
Scanner scanner = new Scanner(is);
StringBuilder sb = new StringBuilder();
while(scanner.hasNextLine())
sb.append(scanner.nextLine()).append("\n");
if (validate(sb.toString())) {
--------
} else { }
is.close();
scanner.close();
} catch (Exception ex) {
}
}
private boolean validate(String content) {
JSONParser parser = new JSONParser();
Boolean isJsonValid = false;
JSONObject json = null;
try {
--------
//json validation goes here
} catch (Exception e) {
}
return isJsonValid;
}
public void setId(Integer id) {
if(id == null)
this.id = 0;
else
this.id = id;
}
}
PowerMockito Junit code
#RunWith(PowerMockRunner.class)
#PrepareForTest({PathTest.class })
public class URLTest {
ServerConnection sc ;
String URL = "http://test.com";
#Before
public void setUp() throws Throwable{
ClientConnection con =PathTest.getCon(); // Here getCon() is a static method
sc = new ServerConnection(con);
sc.setId(1000);
}
#Test
public void testName() throws Throwable {
String expectedResponse = "test";
URL url = PowerMockito.mock(URL.class);
HttpURLConnection connection = PowerMockito.mock(HttpURLConnection.class);
InputStream inputStream = PowerMockito.mock(InputStream.class);
Scanner scanner = PowerMockito.mock(Scanner.class);
PowerMockito.whenNew(URL.class).withArguments(URL).thenReturn(url);
PowerMockito.whenNew(Scanner.class).withArguments(inputStream).thenReturn(scanner);
PowerMockito.when(scanner.useDelimiter("\\A")).thenReturn(scanner);
PowerMockito.when(url.openConnection()).thenReturn(connection);
// Response code mocked here
PowerMockito.when(connection.getResponseCode()).thenReturn(200);
PowerMockito.when(connection.getInputStream()).thenReturn(inputStream);
PowerMockito.when(scanner.hasNext()).thenReturn(true);
PowerMockito.when(scanner.next()).thenReturn(expectedResponse);
sc.setUrl(URL);
}
}
While Iam executing this I observed following error message
URLTest
com.objects.URLTest
testName(com.objects.URLTest)
java.lang.ExceptionInInitializerError
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at javassist.runtime.Desc.getClassObject(Desc.java:43)
at javassist.runtime.Desc.getClassType(Desc.java:152)
at javassist.runtime.Desc.getType(Desc.java:122)
at javassist.runtime.Desc.getType(Desc.java:78)
at com.objects.PathTest.getCon(TargetPathTest.java:24)
at com.objects.URLTest.setUp(URLTest.java:41)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.internal.runners.MethodRoadie.runBefores(MethodRoadie.java:133)
at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:96)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.executeTest(PowerMockJUnit44RunnerDelegateImpl.java:310)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTestInSuper(PowerMockJUnit47RunnerDelegateImpl.java:131)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.access$100(PowerMockJUnit47RunnerDelegateImpl.java:59)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner$TestExecutorStatement.evaluate(PowerMockJUnit47RunnerDelegateImpl.java:147)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.evaluateStatement(PowerMockJUnit47RunnerDelegateImpl.java:107)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTest(PowerMockJUnit47RunnerDelegateImpl.java:82)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runBeforesThenTestThenAfters(PowerMockJUnit44RunnerDelegateImpl.java:298)
at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:87)
at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:50)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.invokeTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:218)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.runMethods(PowerMockJUnit44RunnerDelegateImpl.java:160)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$1.run(PowerMockJUnit44RunnerDelegateImpl.java:134)
at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:34)
at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:44)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.run(PowerMockJUnit44RunnerDelegateImpl.java:136)
at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run(JUnit4TestSuiteChunkerImpl.java:121)
at org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.run(AbstractCommonPowerMockRunner.java:57)
at org.powermock.modules.junit4.PowerMockRunner.run(PowerMockRunner.java:59)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: java.lang.NullPointerException
... 38 more
So many things wrong with this code.
The first technical answer is: you seem to not know what you are doing. You have #PrepareForTest({PathTest.class }) which indicates that you intend to mock a static method of that class.
But then you aren't doing what is necessary to mock a static method in that class. Simply follow their documentation step by step. I also think that the URL class is final, so you would have to prepare-annotate that class as well, in order for it be used with whenNew()!
But then: you should avoid mocking URL or URL connection objects. Simply use some sort of dependency injection, and make sure you can pass some mocked instance into your code under test, for example using the #InjectMocks annotation of Mockito. From there, you might also avoid using a static method.
Long story short: your production code is badly written, and your unit test is (honestly) plain horrible. You should seriously step back here, and throw that stuff away. Then re-think your production code to not use new directly, and to not rely on static methods. By doing so, you can get rid of PowerMockito, you go with ordinary Mockito (well, you need to enable mocking of final classes). But rest assured: there is no point in using PowerMock(ito) unless you absolutely have to.
And then: don't "invent" mocking code. Read tutorials, and follow them step by step. You are mocking like everything in your test case, but you should do the exact opposite: you only mock things when there is absolutely no other way to test your code. And you absolutely do not make a method static when that breaks your ability to reasonably unit test your code.
( static has its place, but when it gets in your way, you are doing the wrong thing ! )
I guess I have a rather complex configuration structure that I can't get to work. Here are the important pieces of the configuration classes:
#ConfigurationProperties
public abstract class AbstractConfigHolder<T extends AbstractComponentConfig> {
}
#Component
public class ExportConfigHolder extends AbstractConfigHolder<GenericExportConfig> {
#NestedConfigurationProperty
private Map<String, GenericExportConfig> exports;
// getters and setters for all fields
}
public class GenericExportConfig extends AbstractComponentConfig {
#NestedConfigurationProperty
private AbstractLocatedConfig target;
// getters and setters for all fields
}
public abstract class AbstractLocatedConfig extends RemoteConfig {
#NestedConfigurationProperty
private ProxyConfig proxy;
// getters and setters for all fields
}
public class ProxyConfig extends RemoteConfig {
private Type type;
// getters and setters for all fields
}
public class RemoteConfig {
private String host;
private int port;
private String user;
private String password;
// getters and setters for all fields
}
Here's the properties file:
exports.mmkb.name=MMKB
exports.mmkb.target=ftp
exports.mmkb.target.path=${user.home}/path/blah
# throws an exception:
exports.mmkb.target.proxy.host=super-host
The conversion stuff is what IMHO should cover everything and provide the proper beans to Spring:
#Configuration
public class ConversionSupport {
#ConfigurationPropertiesBinding
#Bean
public Converter<String, AbstractLocatedConfig> locatedConfigConverter(ApplicationContext applicationContext) {
return new Converter<String, AbstractLocatedConfig>() {
private ProxyConfigs proxyConfigs;
private ConnectionConfigs connectionConfigs;
#Override
public AbstractLocatedConfig convert(String targetType) {
System.out.println("Converting " + targetType);
initFields(applicationContext);
switch (targetType.toLowerCase()) {
case "ftp":
return new FtpTargetConfig(proxyConfigs, connectionConfigs);
// others...
}
}
// This is necessary to avoid conflicts in bean dependencies
private void initFields(ApplicationContext applicationContext) {
if (proxyConfigs == null) {
AbstractConfigHolder<?> configHolder = applicationContext.getBean(AbstractConfigHolder.class);
proxyConfigs = configHolder.getProxy();
connectionConfigs = configHolder.getConnection();
}
}
};
}
}
However, I get this instead:
Converting ftp
2016-04-29 09:33:23,900 WARN [org.springframework.context.annotation.AnnotationConfigApplicationContext] [main] Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'exportConfigHolder': Could not bind properties to ExportConfigHolder (prefix=, ignoreInvalidFields=false, ignoreUnknownFields=true, ignoreNestedProperties=false); nested exception is org.springframework.beans.InvalidPropertyException: Invalid property 'exports[mmkb].target.proxy[host]' of bean class [at.a1.iap.epggw.exporter.config.GenericExportConfig]: Property referenced in indexed property path 'proxy[host]' is neither an array nor a List nor a Map; returned value was [at.a1.iap.epggw.commons.config.properties.ProxyConfig#52066604]
2016-04-29 09:33:23,902 ERROR [org.springframework.boot.SpringApplication] [main] Application startup failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'exportConfigHolder': Could not bind properties to ExportConfigHolder (prefix=, ignoreInvalidFields=false, ignoreUnknownFields=true, ignoreNestedProperties=false); nested exception is org.springframework.beans.InvalidPropertyException: Invalid property 'exports[mmkb].target.proxy[host]' of bean class [at.a1.iap.epggw.exporter.config.GenericExportConfig]: Property referenced in indexed property path 'proxy[host]' is neither an array nor a List nor a Map; returned value was [at.a1.iap.epggw.commons.config.properties.ProxyConfig#52066604]
at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:339)
at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:289)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:408)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1570)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:545)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:772)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:839)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:538)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:766)
at org.springframework.boot.SpringApplication.createAndRefreshContext(SpringApplication.java:361)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:307)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1191)
at at.a1.iap.epggw.exporter.Application.main(Application.java:23)
Caused by: org.springframework.beans.InvalidPropertyException: Invalid property 'exports[mmkb].target.proxy[host]' of bean class [at.a1.iap.epggw.exporter.config.GenericExportConfig]: Property referenced in indexed property path 'proxy[host]' is neither an array nor a List nor a Map; returned value was [at.a1.iap.epggw.commons.config.properties.ProxyConfig#52066604]
at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:406)
at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:280)
at org.springframework.boot.bind.RelaxedDataBinder$RelaxedBeanWrapper.setPropertyValue(RelaxedDataBinder.java:700)
at org.springframework.beans.AbstractPropertyAccessor.setPropertyValues(AbstractPropertyAccessor.java:95)
at org.springframework.validation.DataBinder.applyPropertyValues(DataBinder.java:834)
at org.springframework.validation.DataBinder.doBind(DataBinder.java:730)
at org.springframework.boot.bind.RelaxedDataBinder.doBind(RelaxedDataBinder.java:128)
at org.springframework.validation.DataBinder.bind(DataBinder.java:715)
at org.springframework.boot.bind.PropertiesConfigurationFactory.doBindPropertiesToTarget(PropertiesConfigurationFactory.java:269)
at org.springframework.boot.bind.PropertiesConfigurationFactory.bindPropertiesToTarget(PropertiesConfigurationFactory.java:241)
at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:334)
... 17 common frames omitted
I mean the error clearly expresses that so far it all worked, there is a proper object in place, but somehow it fails to further apply the properties. I know that it's neither an array nor a List nor a Map, because I want it to be POJO.
What can I do here to make this work?
This is Spring-boot 1.3.3 BTW.
Well, it seems as if I somehow hit a corner-case where Spring doesn't do much about it. The main problem is that Spring seems to collect the available bean structure including their nested field structure before it knows of (or at least makes use of) the Converters lying around in the system.
I let the class with #ConfigurationProperties implement ApplicationContextAware and the new method
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
AnnotationConfigApplicationContext context = (AnnotationConfigApplicationContext) applicationContext;
#SuppressWarnings("unchecked")
Converter<String, AbstractLocatedConfig> locatedConfigSupport = context.getBean("locatedConfigConverter", Converter.class);
:
}
then also looked for all properties in the context's environment that would trigger the conversion process, manually called the conversion and created the bean structure that way.
For some reason the following lifecycle-stuff of Spring caused not all properties to end up in the bean, which made me do this:
#Configuration
public class SampleConfiguration {
#Autowired
private Environment environment;
#Autowired
private ClassWithTheConfigurationPropertiesAbove theBeanWithTheConfigurationPropertiesAbove;
#PostConstruct
void postConstruct() throws Exception {
if (environment instanceof AbstractEnvironment) {
MutablePropertySources sources = ((AbstractEnvironment) environment).getPropertySources();
// This is a MUST since Spring calls the nested properties handler BEFORE
// calling the conversion service on that field. Therefore, our converter
// for AbstractLocatedConfigs is called too late the first time. A second
// call will fill in the fields in the new objects and set the other ones
// again, too.
// See org.springframework.core.env.PropertySourcesPropertyResolver.getProperty(String, Class<T>, boolean)
// Note: in case Spring reorders this, the logic here won't be needed.
setProperties(theBeanWithTheConfigurationPropertiesAbove, sources);
} else {
throw new IllegalArgumentException("The environment must be an " + AbstractEnvironment.class.getSimpleName());
}
}
void setProperties(Object target, MutablePropertySources propertySources) {
// org.springframework.boot.bind.PropertiesConfigurationFactory.doBindPropertiesToTarget()
// was the base for this. Go there for further logic if needed.
RelaxedDataBinder dataBinder = new RelaxedDataBinder(target);
dataBinder.bind(new MutablePropertyValues(getProperties(propertySources)));
}
public String getProperty(String propertyName) {
return environment.getProperty(propertyName);
}
private Map<String, String> getProperties(MutablePropertySources propertySources) {
Iterable<PropertySource<?>> iterable = () -> propertySources.iterator();
return StreamSupport.stream(iterable.spliterator(), false)
.map(propertySource -> {
Object source = propertySource.getSource();
if (source instanceof Map) {
#SuppressWarnings("unchecked")
Map<String, String> sourceMap = (Map<String, String>) source;
return sourceMap.keySet();
} else if (propertySource instanceof SimpleCommandLinePropertySource) {
return Arrays.asList(((SimpleCommandLinePropertySource) propertySource).getPropertyNames());
} else if (propertySource instanceof RandomValuePropertySource) {
return null;
} else {
throw new NotImplementedException("unknown property source " + propertySource.getClass().getName() + " or its source " + source.getClass().getName());
}
})
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.collect(Collectors.toMap(Function.identity(), this::getProperty));
}
}
It would be nice if Spring could do something about this to make it easier...
The tested method has the following code:
SuppressWarnings suppressWarnings = method.getAnnotation(SuppressWarnings.class);
In my test method.I mocked java.lang.reflect.Method:
Method method= PowerMock.createMock(Method.class);
SuppressWarnings sw = EasyMock.createMock(SuppressWarnings.class);
EasyMock.expect(method.getAnnotation(SuppressWarnings.class)).andReturn(sw);
In the tested method,
method.getAnnotation(SuppressWarnings.class); always returns null.
I don't know why.Could anyone help me?
//code:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Anonymous {
}
public class AnnotationClass {
public Anonymous fun(Method m){
Anonymous anonymous = m.getAnnotation(Anonymous.class);
return anonymous;
}
}
// test class:
#RunWith(PowerMockRunner.class)
#PrepareForTest(Method.class)
public class AnnotationClassTest {
#Test
public void test() throws NoSuchMethodException, SecurityException {
AnnotationClass testClass = new AnnotationClass();
final Method mockMethod = PowerMock.createMock(Method.class);
final Anonymous mockAnot = EasyMock.createMock(Anonymous.class);
EasyMock.expect(mockMethod.getAnnotation(Anonymous.class)).andReturn(mockAnot);
PowerMock.replay(mockMethod);
final Anonymous act = testClass.fun(mockMethod);
Assert.assertEquals(mockAnot, act);
PowerMock.verify(mockMethod);
}
}
error:
java.lang.AssertionError: expected:<EasyMock for interface
com.unittest.easymock.start.Anonymous> but was:<null>
SuppressWarnings has #Retention(value=SOURCE) which means that it is not available at runtime:
public static final RetentionPolicy SOURCE: Annotations are to be discarded by the compiler.
However, if you would try your code with a different annotation that is available at runtime, method.getAnnotation(MyAnnotation.class) would still return null. That is, because by default the mocked Method will return null for method calls.
I think your problem is in the configuration of the mock, when I run your code (using an annotation that is available at runtime) I get the following exception:
Exception in thread "main" java.lang.IllegalStateException: no last call on a mock available
at org.easymock.EasyMock.getControlForLastCall(EasyMock.java:466)
at org.easymock.EasyMock.expect(EasyMock.java:444)
at MockStuff.main(MockStuff.java:54)
This page has some explanations about how to mock a final class (such as Method).
Your code gives the exact same result for me. I was able to get it working using the following code:
#RunWith(PowerMockRunner.class)
#PrepareForTest(Method.class)
public class AnnotationClassTest {
#Test
public void test() throws NoSuchMethodException, SecurityException {
final Method mockMethod = PowerMock.createMock(Method.class);
final Anot mockAnot = EasyMock.createMock(Anot.class);
EasyMock.expect(mockMethod.getAnnotation(Anot.class)).andReturn(mockAnot);
PowerMock.replay(mockMethod);
final Anot methodReturn = mockMethod.getAnnotation(Anot.class);
Assert.assertEquals(mockAnot, methodReturn);
}
}
#Retention(RetentionPolicy.RUNTIME)
#interface Anot {}
Note that this code is self contained, I defined the Anot interface since you didn't give the definition of Anonymous.
I'm using Bean Validation with RestEasy in Wildfly 8.2.0.Final:
#Path("/user")
#Produces(MediaType.APPLICATION_JSON)
public class UserEndpoint
{
//more code
#GET
#Path("/encrypt/{email}")
public Response fetchEncryptedId(#PathParam("email") #NotNull String email)
{
String encryptedUserId = userService.getEncryptedUserId(email);
return Response.ok().entity(new UserBo(encryptedUserId)).build();
}
}
This basically works. Now I'd like to get the response as JSON object but I can't get it working. All my "application" exceptions are handled by my Exception Mapper, this works:
#Provider
public class DefaultExceptionMapper implements ExceptionMapper<Exception>
{
private static final String MEDIA_TYPE = "application/json";
private LoggingService loggingService;
#EJB
public void setLoggingService(LoggingService loggingService)
{
this.loggingService = loggingService;
}
#Override
public Response toResponse(Exception exception)
{
ResponseObject responseObject = new ResponseObject();
responseObject.registerExceptionMessage(exception.getMessage());
if (exception instanceof ForbiddenException)
{
loggingService.log(LogLevel.ERROR, ((ForbiddenException)exception).getUserId(), ExceptionToStringMapper.map(exception));
return Response.status(Status.FORBIDDEN).type(MEDIA_TYPE).entity(responseObject).build();
}
//more handling
loggingService.log(LogLevel.ERROR, "", ExceptionToStringMapper.map(exception));
return Response.status(Status.INTERNAL_SERVER_ERROR).type(MEDIA_TYPE).entity(responseObject).build();
}
}
But bean validation somehow bypasses it. Then I thought about using Throwable instead of Exception but it didn't help either. I guess the ExceptionMapper is not triggered because there is some life cycle problem with JAX-RS and JSR303. But how can I syncronize them to handle bean validation exceptions?
Additional information: The exception passes the javax.ws.rs.container.ContainerResponseFilter so I could write some workaround by implementing the filter method in a subclass, but this is not clean solution. The target is to handle the exceptions in the Exception mapper.
It's not always the case that your ExceptionMapper<Exception> will catch all exception under the Exception hierarchy. If there is another more specific mapper, say one for RuntimeException, that mapper will be used for all exception of RuntimeException and its subtypes.
That being said (assuming you're using resteasy-validation-provider-11), there is already a ResteasyViolationExceptionMapper that handles ValidationException.
#Provider
public class ResteasyViolationExceptionMapper
implements ExceptionMapper<ValidationException>
This mapper is automatically registered. It returns results in the form of a ViolationReport. The client needs to set the Accept header to application/json in order to see a response similar to
{
"exception":null,
"fieldViolations":[],
"propertyViolations":[],
"classViolations":[],
"parameterViolations":[
{
"constraintType":"PARAMETER",
"path":"get.arg0",
"message":"size must be between 2 and 2147483647",
"value":"1"}
],
"returnValueViolations":[]
}
You can see more at Violation reporting.
If you want to completely override this behavior, you can create a more specific mapper for ResteasyViolationException, which is the exception thrown by the RESTeasy validator
#Provider
public class MyValidationMapper
implements ExceptionMapper<ResteasyViolationException> {
#Override
public Response toResponse(ResteasyViolationException e) {
}
}