I have a nested XML structure based in an XSD. I use JAXB for unmarshalling (read only).
Regularly, I need to find one or more elements somewhere in the large structure. To avoid traversing the structure each time I need to search, I would like to add an optimized search function with an internal cache.
What it the optimal way to define that? What are pro/cons for different ways?
I initially thought of using a facade or an adapter, where the adaper class accesses the generated class and adds methods as needed; however I would like to ask for recommendations.
As a (slightly) simplified example, an XML based on this XSD needs to be searched for Elements of type "step" having a certain "boq" element:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="test">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="group"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="group">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="step"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="step">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" ref="number"/>
<xs:element ref="name"/>
<xs:element ref="type"/>
<xs:element ref="target"/>
<xs:sequence minOccurs="0">
<xs:element ref="boq"/>
<xs:element ref="remote"/>
</xs:sequence>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="number" type="xs:integer"/>
<xs:element name="name" type="xs:NCName"/>
<xs:element name="type" type="xs:NCName"/>
<xs:element name="target" type="xs:NCName"/>
<xs:element name="boq" type="xs:string"/>
<xs:element name="remote" type="xs:string"/>
</xs:schema>
That Schema has been compiled using JAXB, so I get several classes. Using the unmarshalling Features, I have the data structure in memory that accesses my XML.
Consider now, I need an optimized search function, that accesses all steps where a boq element is defined, and return the value of boq and remote (if also defined).
HashMap<String,Step> resultMap = new HashMap<>();
test.getGroup().forEach(group ->
group.getStep().forEach(step -> {
if ("searchpattern".equals(step.getBoq()))
resultMap.put("searchpattern", step);
}));
What is the best way to do encapsulate such a search? I could write a second class as an adaptor that includes this method, or are there better options? Inheritance? using the Options of JAXB itself? using a third-party plugin such as the jaxb-delegate plugin for maven?
There is a number of options to solve this.
The simplest is to simply implement the access routinges in a utility class. So basically you'll call something like SearchPatterns.of(foo)
and pass an instance of schema-derived class to it. Comparing this to foo.getSearchPatterns()
(where getSearchPatterns()
is somehow added to the schema-derived class) it's not that much of the difference. Well, OK, not so OOP-incapsulation-whatever, but, frankly who cares.
You can use the XJC Code Injector Plugin to inject whatever code in the generated classes. Please see the following question for an example:
Inserting code with XJC+xsd+jxb using the options " -Xinject-code -extension "
(If you have trouble with it, please ask another question. I realize we don't have a go-to XJC Code Injector question in jaxb.)
It is relatively easy to you and would allow you to inject whatever code you want.
The disadvantage is that part of your Java code will reside in a weird XML file.
Another option is to prepare an abstract class with the accessor method you need (say, getSearchpatterns()
) as well as methods it uses (getGroup()
) as abstract methods. Then make your schema-derived class extends this prepared abstract class. Generated methods will implement abstract methods defined in the prepared superclass. This is essentially a "Template method" pattern.
There are many ways to make schema-derived class extend an existing class. This is one of them:
JAXB Marshalling - extending an existing class
Or you can use the Inheritance plugin from JAXB2 Basics. Disclaimer: I'm the author.
Instead of extending a class, you can define an interface with template methods + default method for the accessor and let your schema-derived class implement it.
I'm not a fan of this option as it misuses inheritance only to add utility methods.
There might be some specific logic behind the accessors you want to add. So maybe instead of simply injecting code (like with Code Injector plugin) you may actually generate the accessor according to the "specific logic".
This is, however a very complicated approach. See this answer for a short overview. I'd only take it if there is really a "specific logic"
I personally prefer to keep my business logic apart from schema-derived classes. And I'm probably one of the biggest JAXB/XJC fans over there. I would definitely write an utility class which provides whatever accessors you want.
I don't like the Code Injector option because then you'll have part of your code in some weird XML file. So if you'll for instance refactor anything in an IDE, that code won't be touched.
Extending a prepared abstract class or implementing a prepared interface is also not my favorite. I think it's just a misuse of OOP constructs to add some utility code.
Writing your own plugin is simply too complicated for a dev without much experience in XJC plugins. Also, I can't recognize "specific logic" which can be somehow generalized, so this option probably doesn't even make sense.