JAXB class returned from #RestController XML elements wonky - spring-boot

I am porting an old application that runs on JBoss to Spring Boot/Tomcat and have most everything working except the response XML. The old code appears to be using xmlbeans for the XSD source generation. I've changed this to use JAXB. Here's my class:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "EventsResponseType1_1", propOrder = {
"getAllEventCodesResponse",
"saveEventCodeResponse",
"failMessageResponse",
"getEmailHistoryResponse"
})
public class EventsResponseType11 {
protected GetAllEventCodesResponseType getAllEventCodesResponse;
protected SaveEventCodeResponseType saveEventCodeResponse;
#XmlElement(name = "FailMessageResponse")
protected ResponseType failMessageResponse;
protected GetEmailHistoryResponseType getEmailHistoryResponse;
// Getters and setters
}
And one of the element classes:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "getAllEventCodesResponseType", propOrder = {
"_return",
"results"
})
public class GetAllEventCodesResponseType {
#XmlElement(name = "Return", required = true)
protected ReturnType _return;
#XmlElement(name = "Results")
protected GetAllEventCodesResponseType.Results results;
// Getters and setters
}
Here's the response XML:
<com.foo.bar.EventsResponseType11>
<getAllEventCodesResponse>
<__return>
<returnCode>0</returnCode>
<returnMessage />
</__return>
<results>
<eventCodes>
<com.foo.bar.EventCodeInfoType>
<eventCodeID>1</eventCodeID>
<eventCode>1000</eventCode>
<eventCodeDesc>Success</eventCodeDesc>
<eventCodeIndicator>SUCCESS</eventCodeIndicator>
<eventCodeContext>General</eventCodeContext>
<createdBy>system</createdBy>
</com.foo.bar.EventCodeInfoType>
</eventCodes>
</results>
</getAllEventCodesResponse>
</com.foo.bar.EventsResponseType11>
I have configured my application:
#SpringBootApplication
#ComponentScan("com.foo.bar")
public class WsApp extends WebMvcConfigurerAdapter{
public static void main(String[] args) {
SpringApplication.run(WsApp.class, args);
}
/**
* Configure the XML as the only return type on requests
*/
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.TEXT_XML);
XStreamMarshaller xmlMarshaller = new XStreamMarshaller();
MarshallingHttpMessageConverter xmlConverter = new MarshallingHttpMessageConverter(xmlMarshaller);
xmlConverter.setSupportedMediaTypes(mediaTypes);
converters.add(xmlConverter);
super.configureMessageConverters(converters);
}
}
And my controller:
#RestController
#RequestMapping("/go")
public class EventService {
#RequestMapping(value = "/events", method = RequestMethod.POST)
public ResponseEntity<EventsResponseType11> events(#RequestBody EventsRequestType11 request){
EventsResponseType11 responseDoc = eventCodesProxy.invoke(request);
return ResponseEntity.ok(responseDoc);
}
}
So my first question is, how can I stop the marshaller from including the package name on those elements that have it.
And second, since the XSD defines a field as "return" JAXB added an underscore to the field name. The #XmlElement annotation on that field identifies this as "Return" which is what I want on the response (without any underscores)
I've tried using a JAXB Marshaller in place of the XStreamMarshaller with no luck. If at all possible, I would opt not to modify the schema because it's old and has a lot of inter-department dependencies.
Thanks in advance for your help!

So after a lot of trial and error, I stumbled upon this post:
Spring 4 mvc REST XML and JSON response
My application class:
#SpringBootApplication
#ComponentScan("com.foo.bar")
#EnableWebMvc
public class WsApp extends WebMvcConfigurerAdapter{
public static void main(String[] args) {
SpringApplication.run(WsApp.class, args);
}
/**
* Configure the negotiator to return ONLY XML
*/
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false).
favorParameter(true).
parameterName("mediaType").
ignoreAcceptHeader(true).
useJaf(false).
defaultContentType(MediaType.APPLICATION_XML).
mediaType("xml", MediaType.APPLICATION_XML);
}
}
I stated before that I wouldn't be too happy about modifying the XSDs, but I had to make a few tweaks to add the #XmlRootElement. I had tried to modify the JAXB source generation through additional libraries but that didn't work out so well.

Related

How do I spring cloud gateway custom filter e2e test?

I have implemented custom GatewayFilterFactory filter. But I don't know how to test this filter with e2e setup.
I have referenced official spring-cloud-gateway AddRequestHeaderGatewayFilterFactoryTests test case code.
This is my custom filter code:
#Component
public class MyCustomFilter implements GatewayFilterFactory<MyCustomFilter.Config>, Ordered {
#Override
public GatewayFilter apply(Config config) {
return new OrderedGatewayFilter((this::filter), getOrder());
}
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
/* do some filtering */
}
#Override
public int getOrder() {
return 1000;
}
#Override
public Config newConfig() {
return new Config(MyCustomFilter.class.getSimpleName());
}
public static getConfig() {
return
}
#Getter
#Setter
public static class Config {
private String name;
Config(String name) {
this.name = name;
}
}
}
And this is my test code:
BaseWebClientTests class look exactly the same as official BaseWebClientTests class code
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = RANDOM_PORT)
#DirtiesContext
#ActiveProfiles("my-custom-filter")
public class MyCustomFilterTests extends BaseWebClientTests {
#LocalServerPort
protected int port = 0;
protected WebTestClient testClient;
protected WebClient webClient;
protected String baseUri;
#Before
public void setup() throws Exception {
setup(new ReactorClientHttpConnector(), "http://localhost:" + port);
}
protected void setup(ClientHttpConnector httpConnector, String baseUri) {
this.baseUri = baseUri;
this.webClient = WebClient.builder().clientConnector(httpConnector)
.baseUrl(this.baseUri).build();
this.testClient = WebTestClient
.bindToServer(httpConnector)
.baseUrl(this.baseUri)
.build();
}
#Test
public void shouldFailByFilterTests() {
/* This test should be failed but success :( */
testClient.get().uri("/api/path")
.exchange().expectBody(Map.class).consumeWith(result -> {
/* do assertion */
});
}
#EnableAutoConfiguration
#SpringBootConfiguration
#Import(DefaultTestConfig.class)
public static class TestConfig {
#Value("${test.uri}")
String uri;
#Bean
public MyCustomFilter myCustomFilter() {
return new MyCustomFilter();
}
#Bean
public RouteLocator testRouteLocator(RouteLocatorBuilder builder, MyCustomFilter myCustomFilter) {
return builder.routes().route("my_custom_filter",
r -> r.path("/api/path")
.filters(f -> f.filter(myCustomFilter.apply(new MyCustomFilter.Config("STRING"))))
.uri(uri))
.build();
}
}
}
Lastly Target controller looks like this:
#RestController
#RequestMapping("/api/path")
public class HttpBinCompatibleController {
#GetMapping("/")
public Mono<BodyData> identity() {
return Mono.just(new BodyData("api success"));
}
#NoArgsConstructor
#AllArgsConstructor
#Getter
static class BodyData {
private String message;
}
}
What I understand how this filter factory test code works is that
custom filter: custom filter is setup inside TestConfig class testRouteLocator method
target controller: target controller is defined as HttpBinCompatibleController class
testClient sends the request, and custom should do some filtering, then target controller should receive the request from testClient.
What I expect from this shouldFailByFilterTests TC is that before request from testClient is sent to target controller, that request should be rejected by MyCustomFilter. But the request is sent to the target controller.
I think the request from testClient is not proxied by testRouteLocator but I'm not sure
Question
What is the cause of this problem?
Is there another way to test my own custom filter?
This problem was related to the version incompatibility between Spring Boot and Spring Cloud.
I was using Spring Boot version 2.1.7 and Spring Cloud version Greenwich.SR2.
Then I found this 'Release train Spring Boot compatibility' table on this link
Before I've noticed version incompatibility, for using #Configuration(proxyBeanMethods = false) feature, upgraded Spring Boot version to 2.2.x.
The solution is using 2.1.x branch BaseWebClientTests class.

Spring Boot: Testing custom MongoTemplate converters

I'm using this custom converters into my Spring Boot service:
#Configuration
public class MongoConfig {
#Bean
public MongoCustomConversions customConversions(){
List<Converter<?,?>> converters = new ArrayList<>();
converters.add(ReferenceWriterConverter.INSTANCE);
return new MongoCustomConversions(converters);
}
#WritingConverter
enum ReferenceWriterConverter implements Converter<Reference, DBObject> {
INSTANCE;
#Override
public String convert(Reference reference) {
//do stuff
}
}
}
Into my controllers, I'm using MontoTemplate in order to talk with MongoDB. So, all converters are already loaded into template.
However, I'd like to test MongoDbTemplate using Spring injection features. I mean, I want to test MongoDbTemplate using custom converters which should already be loaded.
Any ideas on how it can be achieved?
EDIT
public class ModelTest {
private List<Reference> references;
public ModelTest() {
this.references = new ArrayList<Reference>();
}
#Before
public void setUp() {
Reference reference = new Reference();
reference.setId("Ref1");
reference.setTimestamp(new Date());
Metadata met = new Metadata();
met.setId("Mdt1");
met.setUser("user");
met.setCreationTimestamp(new Date());
met.setMetadata("[{'departament': 'JUST'}]");
reference.setMetadata(met);
this.references.add(reference);
ServerAddress serverAddress = new ServerAddress("127.0.0.1", 27017);
MongoClient mongoClient = new MongoClient(serverAddress);
MongoTemplate mongoTemplate = new MongoTemplate(mongoClient, "db");
mongoTemplate.insert(reference);
}
/**
* Assert Office mime type documents.
*/
#Test
public void office() {
fail("Not yet implemented");
}
}
EDIT 2
I also would like to use custom testing properties. I mean, currently, we are setting properties into src/test/resources/application.properties.
spring.data.mongodb.host: localhost
spring.data.mongodb.port: 27017
How could I load these file properties?
Solution 1
If you want to test it with the Spring context, you can annotate your Test class as SpringBootTest and autowire the MongoTemplate. This should then contain your custom conversions for you to test them:
#RunWith(SpringRunner.class)
#SpringBootTest
public class ModelTest {
private List<Reference> references;
#Autowired
private final MongoTemplate mongoTemplate;
public ModelTest() {
this.references = new ArrayList<Reference>();
}
#Before
public void setUp() {
Reference reference = new Reference();
reference.setId("Ref1");
reference.setTimestamp(new Date());
Metadata met = new Metadata();
met.setId("Mdt1");
met.setUser("user");
met.setCreationTimestamp(new Date());
met.setMetadata("[{'departament': 'JUST'}]");
reference.setMetadata(met);
this.references.add(reference);
mongoTemplate.insert(reference);
}
/**
* Assert Office mime type documents.
*/
#Test
public void office() {
fail("Not yet implemented");
}
}
Solution 2
If you just want to test the converter alone, you could make a ReferenceWriterConverterTest like so:
public class ReferenceWriterConverterTest {
private ReferenceWriterConverter converter;
#Before
public void setUp() {
converter = ReferenceWriterConverter.INSTANCE;
}
//test stuff
}

Configuration for RestController Deserializing YAML into pojo Uploaded as raw body?

What will be the equivalent configuration of below spring mvc code in spring 5 webflux? how can i add multiple converters in webflux?
#Configuration
public class YamlConfiguration extends WebMvcConfigurerAdapter {
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new YamlJackson2HttpMessageConverter());
}
}
final class YamlJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
YamlJackson2HttpMessageConverter() {
super(new YAMLMapper(), MediaType.parseMediaType("application/x-yaml"));
}
}
I know this is old, but after digging and finding no answers, I was finally able to piece this together using a bunch of different posts, posting here in hopes of helping future people.
/**
* Modelled off of Jackson2JsonDecoder
*/
public class Jackson2YamlDecoder extends AbstractJackson2Decoder {
public Jackson2YamlDecoder() {
super(YAMLMapper.builder().build(), new MimeType("application","x-yaml"));
}
}
/**
* Modelled off of Jackson2JsonEncoder
*/
public class Jackson2YamlEncoder extends AbstractJackson2Encoder {
#Nullable
private final PrettyPrinter ssePrettyPrinter;
public Jackson2YamlEncoder() {
super(YAMLMapper.builder().build(), new MimeType("application","x-yaml"));
this.ssePrettyPrinter = initSsePrettyPrinter();
}
private static PrettyPrinter initSsePrettyPrinter() {
DefaultPrettyPrinter printer = new DefaultPrettyPrinter();
printer.indentObjectsWith(new DefaultIndenter(" ", "\ndata:"));
return printer;
}
#Override
protected ObjectWriter customizeWriter(ObjectWriter writer, MimeType mimeType, ResolvableType elementType, Map<String, Object> hints) {
return this.ssePrettyPrinter != null && MediaType.TEXT_EVENT_STREAM.isCompatibleWith(mimeType) && writer.getConfig().isEnabled(SerializationFeature.INDENT_OUTPUT) ? writer.with(this.ssePrettyPrinter) : writer;
}
}
#Configuration
public class WebFluxConfig implements WebFluxConfigurer {
#Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
CodecConfigurer.CustomCodecs customCodecs = configurer.customCodecs();
customCodecs.registerWithDefaultConfig(new Jackson2YamlDecoder());
customCodecs.registerWithDefaultConfig(new Jackson2YamlEncoder());
}
}
I found that if you just register the YAML HttpMessageConverter as a bean webflux will automatically use it.

How to consume protobuf parameters using Spring REST?

I'm trying to pass a protobuf parameter to a REST endpoint but I get
org.springframework.web.client.HttpServerErrorException: 500 null
each time I try. What I have now is something like this:
#RestController
public class TestTaskEndpoint {
#PostMapping(value = "/testTask", consumes = "application/x-protobuf", produces = "application/x-protobuf")
TestTaskComplete processTestTask(TestTask testTask) {
// TestTask is a generated protobuf class
return generateResult(testTask);
}
}
#Configuration
public class AppConfiguration {
#Bean
ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
}
#SpringBootApplication
public class JavaConnectorApplication {
public static void main(String[] args) {
SpringApplication.run(JavaConnectorApplication.class, args);
}
}
and my test looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
#WebAppConfiguration
public class JavaConnectorApplicationTest {
#Configuration
public static class RestClientConfiguration {
#Bean
RestTemplate restTemplate(ProtobufHttpMessageConverter hmc) {
return new RestTemplate(Arrays.asList(hmc));
}
#Bean
ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
}
#Autowired
private RestTemplate restTemplate;
private int port = 8081;
#Test
public void contextLoaded() {
TestTask testTask = generateTestTask();
final String url = "http://127.0.0.1:" + port + "/testTask/";
ResponseEntity<TestTaskComplete> customer = restTemplate.postForEntity(url, testTask, TestTaskComplete.class);
// ...
}
}
I'm sure that it is something with the parameters because if I create a variant which does not take a protobuf parameter but returns one it just works fine. I tried debugging the controller code but the execution does not reach the method so the problem is probably somewhere else. How do I correctly parametrize this REST method?
This is my first stack overflow answer but I was a lot to frustred from searching for working examples with protobuf over http and spring.
the answer https://stackoverflow.com/a/44592469/15705964 from Jorge is nearly correct.
Like the comments mention: "This won't work in itself. You need to add a converter somewhere at least."
Do it like this:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Autowired
ProtobufHttpMessageConverter protobufHttpMessageConverter;
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(protobufHttpMessageConverter);
}
}
The ProtobufHttpMessageConverter will do his job automatically and add the object to your controller methode
#RestController
public class ProtobufController {
#PostMapping(consumes = "application/x-protobuf", produces = "application/x-protobuf")
public ResponseEntity<TestMessage.Response> handlePost(#RequestBody TestMessage.Request protobuf) {
TestMessage.Response response = TestMessage.Response.newBuilder().setQuery("This is a protobuf server Response")
.build();
return ResponseEntity.ok(response);
}
Working example with send and reseive with rest take a look: https://github.com/Chriz42/spring-boot_protobuf_example
Here it's the complete answer
#SpringBootApplication
public class JavaConnectorApplication {
public static void main(String[] args) {
SpringApplication.run(JavaConnectorApplication.class, args);
}
}
Then you need to provide the right configuration.
#Configuration
public class AppConfiguration {
//You need to add in this list all the messageConverters you will use
#Bean
RestTemplate restTemplate(ProtobufHttpMessageConverter hmc) {
return new RestTemplate(Arrays.asList(hmc,smc));
}
#Bean
ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
}
And finally your RestController.
#RestController
public class TestTaskEndpoint {
#PostMapping(value = "/testTask")
TestTaskComplete processTestTask(#RequestBody TestTask testTask) {
// TestTask is a generated protobuf class
return generateResult(testTask);
}
}
The #RequestBody annotation: The body of the request is passed through an HttpMessageConverter (That you already defined) to resolve the method argument depending on the content type of the request
And your test class:
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
public class JavaConnectorApplicationTest {
#Autowired
private RestTemplate restTemplate;
private int port = 8081;
#Test
public void contextLoaded() {
TestTask testTask = generateTestTask();
final String url = "http://127.0.0.1:" + port + "/testTask/";
ResponseEntity<TestTaskComplete> customer = restTemplate.postForEntity(url, testTask, TestTaskComplete.class);
// Assert.assertEquals("dummyData", customer.getBody().getDummyData());
}
}

Spring - Create bean based on Annotation field

UPDATE: I used a different approach for my problem.
Side-Question: I would like to know how spring does it with the exclude in SpringBootApplication
The SpringBootApplication:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Inherited
#SpringBootConfiguration
#EnableAutoConfiguration
#ComponentScan(excludeFilters = #Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class))
public #interface SpringBootApplication {
So when the context is loaded, and the EnableAutoConfiguration is executed, the excludes are available.
Thats the same what i want.
At Bean-Creation i want to know if an Annotation has some field (for example boolean)
Old Question:
I have an Annotation:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Import(TaskSpringContext.class)
public #interface TaskTest
{
Class<? extends DatabaseService> db() default DatabaseService.class;
}
This Annotation is used at:
#TaskTest(db = DatabaseServiceExtended.class)
#SpringBootApplication()
public class TaskServer
{
public static void main(String[] args)
{
final ApplicationContext ctx = SpringApplication.run(TaskServer.class, args);
}
}
Now, at TaskSpringContext.class i want to create a bean based on the db-Field of the TaskTest-Annotation:
#Bean(name = "databaseService")
public DatabaseService databaseService()
{
return ??
Here i want to return the DatabaseServiceExtended
}
Anyone knows how to do it?
I assume that there's a better way for it, but this will scan your classpath, starting from "com.example" for all classes annotated with com.example.TaskTest and add a bean definition for it, so that bean will be created later.
This will allow you to check all classes for your annotation, but of course you will have to solve the problem that two (or more) #TaskTest could be on your classpath.
#Component
public class TestBeanProcessor implements BeanDefinitionRegistryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
; // does nothing
}
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
Set<BeanDefinition> definitions = scanForBeanDefinitionsIn("com.example"); // the base package
// test if one or more, perhaps error, whatever
BeanDefinition def = ...; // one of them
Class<?> clz = Class.forName(def.getBeanClassName());
TaskTest annotation = clz.getAnnotation(TaskTest.class);
// create new RootBeanDefinition with TaskText Data (pretty analogous to XML)
RootBeanDefinition dataSourceDefinition = ...;
registry.registerBeanDefinition("dataSource", dataSourceDefinition);
}
protected Set<BeanDefinition> scanForBeanDefinitionsIn(String basePackage) {
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new TypeFilter() {
#Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return metadataReader.getAnnotationMetadata().getAnnotationTypes().contains("com.example.TaskTest");
}
});
return scanner.findCandidateComponents(basePackage);
}
}

Resources