Tuesday, August 7, 2018

Bottom-up SOAP Services with Apache CXF and Custom XSD

SOAP is not very popular these days, primarily because of almost nonhuman readable WSDL and protocol that puts a lot of overhead into a simple message. It's tough to send a message using a browser or curl. Not mentioning the fact that we have to obtain WSDL somehow.

But on the other hand side, SOAP has some advantages over REST. It allows us to define a contract and specify the data it will process. Once you have such WSLD, you can throw it over the hedge, and you do not have to answer questions like how do I delete a created resource, what 403 means in this case, or is this call idempotent?
WSLD contains everything needed to define an RPC interface and document it. I would choose REST for most cases, but still, there are some good use cases for SOAP.

This one thing bothers me about SOAP: how do I get proper WSLD? Should I use a tool to generate it or write it? Honestly... I really do not want to dig into SOAP to just write WSLD, and I do not want to spend hours playing around with some tools generating WSLD, importing it into my project, and binding generated stuff to actual implementation only to find out about the end that it does not operate as expected.

The perfect solution would be to write Java code, annotate it, and generate WSDL out of it. Those annotations should provide enough flexibility to influence WSLD generation so that we archive something that can be delivered to the customers.

I've tried a few Java frameworks, and all provide a way to generate WSLD out of Java code, but they all need to include one fundamental feature: you cannot offer XSD for data types. So it's impossible to specify the content of an email field or provide a format for a date. We get only half of a possible functionality of a WSDL: method calls, exception handling, documentation, and security, but there is no way to specify the data format.
I would like to have WSLD that is as precise as possible so that the client can call the particular method and know precisely what is possible and is not allowed.

Let's start with the standard SOAP Service generated out of Java code. We will use Apache CXF and Spring Boot for it. The source code is here: https://github.com/maciejmiklas/apache-cxf-soap.

It's a simple application where users can submit registration as a SOAP Request on http://localhost:8080/soap/Registration:


This code outpost following WSDL:
As you can see, this WSDL has little info about data types (lines 9-22). The email is mandatory, but there is no way to provide any further assertions.
The problem lies in JAXB. It ignores annotations to influence generated XML schema. There is a pull request (jaxb-facets) that would solve this issue, but it's already a few years old and looks like it will only be integrated for a while.

The limitation is not caused by SOAP frameworks but by the fact that they are based on JAXB.

This does not change the fact that I will not write WSLD myself. There has to be a better way!

We will implement a simple extension for Apache CXF to combine generated WSLD with custom XSD. We have to write XSD in this case, but CXF will generate WSLD for us and include the given schema.
XSD is mighty, so I prefer writing it by hand because it's possible to specify the data format precisely. Using annotations to generate XSD would be convenient, but it would still cover only some common areas.
The implementation below has its limitations. You might run into some issues. It might stop working after the next CXF update. But! I use it, and it does what I need, so it might be something for you as well ;)

Now we are going to modify the first example. The idea is to write XSD that defines simple types, references those types in transfer classes, and generates WSLD, combining them all.
In the beginning, we have to write a schema that defines types for transfer objects: The original source code will be modified only in a few places:
  • We will replace the default Apache CXF data binding with a custom implementation. It reads Schama from the file and integrates it into generated WSLD,
  • Some fields in transfer objects are annotated with @XmlSchemaType - this annotation provides a connection between Java types and types defined in XSD. For example, the ExRegistration#email is annotated with @XmlSchemaType(name = "email"), and XSD contains the email type. The email generated in WSDL references the type from a provided schema,
  • the classes following the pattern *Registration* have been renamed to *ExRegistration*
Here is our final WSDL:

Monday, May 28, 2018

Spring Beans Discovery through Java Config in Practice

Spring Java Config is a great tool that gives us a precise way to control an application's bootstrap and wiring. However, it only sometimes works as expected. We occasionally run into strange issues where a particular bean is not instantiated at the expected time, lazy loading does not work as expected, or the whole configuration is ignored for no apparent reason.

To get a better understanding of the inner workings of Spring bean discovery, I created some examples that can be found here: https://github.com/maciejmiklas/spring-context-test.git

Each example has a name: exa (example A) or exb (example B). Each example has several exercises, like exa has: exa01, exa02, ..., ex05.

Bean Loading Order - Single Config

exa01

This is the first exercise. It defines a basic structure that will be modified as we go along.
The main method loads Spring context from the configuration file Conf.java. This configuration instructs Spring to create 3 beans. Each of these beans has two log statements, the first in the constructor and the second in @PostConstruct. The console output after the execution of this example is shown below the code.
Nothing spectacular happens here: beans are initialized from A to C right after the constructor Spring executes also @PostConstruct. There are no dependencies between the beans.


exa02

Let's modify the order of factory methods in Conf.java.

The bean initialization order has changed. It means that method names do not matter, but the physical order within a class. It can change with JVM, so you should not rely on it!


exa03

Now we will extract the definition of BeanB into a dedicated configuration file:
Spring loads bean definitions from the imported configuration in the first place.


exa04

We will modify the previous exercise so that BeanB depends on BeanA. Spring loads dependent beans in the first place. It does not matter where it's located. Spring will scan all possible configurations to find it.

exa05

The same as exa04, but BeanB does not have @DependsOn("beanA"). InsteadBeanB injects BeanA. We have a similar dependency situation to exa4: BanB depends on BeanA. However @PostConstructs are called in a different order: Spring creates an instance of BeanB, and during the construction phase (constructor), references to BeanA are not set. Once the instance of BeanB is created, BeanA will be created and injected into BeanB. BeanB cannot access BeanA in the constructor, first after a complete initialization is done - in the method @PostConstruct.
Spring had to modify the order of @PostConstruct calls to ensure that the bean references were not null during an initialization phase.


Summary

  • within a single configuration class, the bean instantiation order depends only on the methods order within this class, not method names. However, this might depend on JVM,
  • Spring loads, in the first place, imported bean definitions. The previous rule applies only to the situation where direct bean definitions do not depend on imported beans. If this is the case, Spring will load the dependent bean in the first place,
  • the initialization code has to be placed in the @PostConstruct method and not in a constructor. Otherwise, references to some beans might be null.

Bean Loading Order - Mixed Config


exb01

BeanA, BeanB, and BeanC are created through dedicated configuration classes: ConfBeanA, ConfBeanB, and ConfBeanC. BeanD is declared in Conf Beans are created in order: A, B, C, D.


exb02

Similar to exb01, but the configuration class ConfBeanB has been renamed to ConfBeanXB.
The instantiation order remains unchanged.


exb03

The same as exb01, but the factory method in ConfBeanB has been renamed from beanB to xyz. The instantiation order remains unchanged.


exb04

The same as exb01, but BeanB has been renamed to BeanXB. This change did not influence instantiation order.


exb05

Similar to exb01, but we've changed the import order from A, B, C into B, A, C. The instantiation order has changed as well.

exb06

Same as exb01, but BeanC has been injected into BeanA. The instantiation order has been changed because BeanC had to be created before BeanA


Summary


  • when the @Import statement contains multiple classes, Spring will load them from the left to the right side - it's iterating over an array, and names of configuration classes do not matter in this case,
  • within single configuration class physical order of methods matters, not their names.

Duplicated Bean Name

exc01

We have 3 beans, A, B, and C, with dedicated config here. BeanA injects BeanB and BeanC.


exc02

The same as exc01, but we've renamed factory method ConfBeanB#beanB() into ConfBeanB#beanC(). Spring goes over our @Import declaration to determine all possible bean definitions: ConfBeanA, ConfBeanB, ConfBeanC. There are two factory methods with the same name: ConfBeanB#beanC() and ConfBeanC#beanC(), so ConfBeanC overwrites the bean created through ConfBeanB because it creates a bean with the same name.


exc03

Similar to exc02, but this time we've renamed method ConfBeanC#beanC() into ConfBeanC#beanB(), so that we have two methods beanB, and not as it was in exc02 two methods with the name: beanC. The output is still the same, BeanB is missing. We've just changed the name of the factory method, and we already know that names do not change loading order, and we still have two beans with the same name.


exc04

Similar to exc02, but we've changed the import order for config classes from A, B, C to A, C, B. Now ConfBeanB will get scanned on end, and it overwrites the previous bean with the same name, so BeanC is missing.


exc05 

It's a modified exc02. We've set AllowBeanDefinitionOverriding to false on Application Context. Bean overwriting is disabled now, so instead of bean not found, we are getting an exception that we are trying to register two beans under the same name.


exc06

The same as exc02, there are still two factory methods: beanC. Additionally, BeanB and BeanC are implementing the common interface. Now BeanA does not inject BeanB and BeanC directly but injects a collection of beans implementing our interface. In the case of direct injection, one of the beans was missing; now both are there!

Summary
In the case of two beans with the same name, the last one wins. You cannot directly inject such overwritten beans, but you can inject a collection of such beans sharing a common interface.

Lazy Loading


exd01

We have three beans: A, B, and C, and there are no dependencies between them. BeanC is being defined through a dedicated configuration class. Nothing special here. The initialization order is as expected.


exd02

The same as exd02, but we've annotated BeanB as lazy. Spring does not load BeanB at all. There are no dependencies to that bean.


exd03

Now we've injected BeanB into BeanA. @Lazy is set on injection point and bean definition. Additionally, we have defined a method on BeanA that calls a method on BeanB: ApplicationExD03 -> beanA.method() -> beanB.method() Spring postpones the creation of BeanB until the method is being called on it, @Lazy works as expected.


exd04

Same as exd03, but @Lazy has been removed on the injection point. BeanB is not lazy anymore, @Lazy on bean definition is not enough.


exd05

Same as exd03, but @Lazy has been removed from the bean configuration. BeanB is not lazy anymore, @Lazy on bean definition is not enough.


Summary

  • lazy annotation has to be provided on configuration, and all injection points, otherwise Spring eagerly initializes such beans,
  • lazy beans that are not referenced (injected) are not loaded at all,
  • you should not rely on @Lazy unless you are 100% sure you can control all possible injection points. Otherwise, one missing Lazy-Annotation will disable laziness on the such bean.