I have a xml representation of object like
OrderList (has list of) Orders and each order has a list of commodities.
I want to validate my commodities and if not valid I want to remove them from order. If all commodities are invalid then I remove the order from the orderlist.
I have been able to validate Orderlist
JAXBContext jaxbContext = JAXBContext.newInstance("com.jaxb");
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = sf.newSchema(new File(XSD));
JAXBSource source = new JAXBSource(jaxbContext, orderList);
Validator validator = schema.newValidator();
DataFeedErrorHandler handler = new DataFeedErrorHandler();
validator.setErrorHandler(handler);
validator.validate(source);
I am not able to find a way to validate commodities.
Something like
for(Order order: orderList){
for(Commodity commodity: order.getCommodity()){
if(!isCommodityValid(commodity)){
// mark for removal
}
}
}
Any help would be greatly appreciated.
TL;DR
You can do a dummy marshal and leverage the JAXB validation mechanisms rather than using the javax.xml.validation mechanisms directly.
LEVERAGING Marshaller.Listener & ValidationEventHandler (CommodityValidator)
For this example we will leverage aspects of Marshaller.Listener and ValidationEventHandler to accomplish the use case.
Marshal.Listener - This will be called for each object being marshalled. We can use it to cache the instance of Order that we may need to remove the instance of Commodity from.
ValidationEventHandler this will give us access to each problem occuring with validation during the marshal operation. For each problem it will be passed a ValidationEvent. This ValidationEvent will hold a ValidationEventLocator from which we can get the object that had the problem being marshalled.
import javax.xml.bind.*;
public class CommodityValidator extends Marshaller.Listener implements ValidationEventHandler {
private Order order;
#Override
public void beforeMarshal(Object source) {
if(source instanceof Order) {
// If we are marshalling an Order Store It
order = (Order) source;
}
}
#Override
public boolean handleEvent(ValidationEvent event) {
if(event.getLocator().getObject() instanceof Commodity) {
// If the Error was Caused by a Commodity Object Remove it from the Order
order.setCommodity(null);
return true;
}
return false;
}
}
DEMO CODE
The following code can be run to prove that everything works.
import java.io.File;
import javax.xml.XMLConstants;
import javax.xml.bind.*;
import javax.xml.validation.*;
import org.xml.sax.helpers.DefaultHandler;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(Orders.class);
// STEP 1 - Build the Object Model
Commodity commodity1 = new Commodity();
commodity1.setId("1");
Order order1 = new Order();
order1.setCommodity(commodity1);
Commodity commodityInvalid = new Commodity();
commodityInvalid.setId("INVALID");
Order order2 = new Order();
order2.setCommodity(commodityInvalid);
Commodity commodity3 = new Commodity();
commodity3.setId("3");
Order order3 = new Order();
order3.setCommodity(commodity3);
Orders orders = new Orders();
orders.getOrderList().add(order1);
orders.getOrderList().add(order2);
orders.getOrderList().add(order3);
// STEP 2 - Check that all the Commodities are Set
System.out.println("\nCommodities - Before Validation");
for(Order order : orders.getOrderList()) {
System.out.println(order.getCommodity());
}
// STEP 3 - Create the XML Schema
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = sf.newSchema(new File("src/forum16953248/schema.xsd"));
// STEP 4 - Perform Validation with the Marshal Operation
Marshaller marshaller = jc.createMarshaller();
// STEP 4a - Set the Schema on the Marshaller
marshaller.setSchema(schema);
// STEP 4b - Set the CommodityValidator as the Listener and EventHandler
CommodityValidator commodityValidator = new CommodityValidator();
marshaller.setListener(commodityValidator);
marshaller.setEventHandler(commodityValidator);
// STEP 4c - Marshal to Anything
marshaller.marshal(orders, new DefaultHandler());
// STEP 5 - Check that the Invalid Commodity was Removed
System.out.println("\nCommodities - After Validation");
for(Order order : orders.getOrderList()) {
System.out.println(order.getCommodity());
}
}
}
OUTPUT
Below is the output from running the demo code. Node how after the marshal operation the invalid commodity was removed.
Commodities - Before Validation
forum16953248.Commodity#3bb505fe
forum16953248.Commodity#699c8551
forum16953248.Commodity#22f4bf02
Commodities - After Validation
forum16953248.Commodity#3bb505fe
null
forum16953248.Commodity#22f4bf02
XML SCHEMA (schema.xsd)
Below is the XML schema used for this example.
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema">
<element name="orders">
<complexType>
<sequence>
<element name="order" minOccurs="0" maxOccurs="unbounded">
<complexType>
<sequence>
<element name="commodity">
<complexType>
<attribute name="id" type="int"/>
</complexType>
</element>
</sequence>
</complexType>
</element>
</sequence>
</complexType>
</element>
</schema>
JAVA MODEL
Below is the object model I used for this example.
Orders
import java.util.*;
import javax.xml.bind.annotation.*;
#XmlRootElement
public class Orders {
private List<Order> orderList = new ArrayList<Order>();
#XmlElement(name="order")
public List<Order> getOrderList() {
return orderList;
}
}
Order
public class Order {
private Commodity commodity;
public Commodity getCommodity() {
return commodity;
}
public void setCommodity(Commodity commodity) {
this.commodity = commodity;
}
}
Commodity
import javax.xml.bind.annotation.*;
public class Commodity {
private String id;
#XmlAttribute
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
Related
I have data structures generated by jaxb. Parts of the structures are basically identical but they are in different namespaces and therefore the generated Java types are different.
I need to transfer data between these structures. In the project ModelMapper is used for mapping so I am expected to use that.
My problem is that ModelMapper can't map the lists generated for 'maxOccurs="unbounded"' elements.
Let's say I have the following schema:
<xs:complexType name="CityData">
<xs:sequence>
<xs:element name="districtData" type="DistrictData" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="DistrictData">
<xs:sequence>
<xs:element name="population" type="xs:int" nillable="false" minOccurs="1" maxOccurs="1"/>
</xs:sequence>
</xs:complexType>
I have this schema in namespacea and in namespaceb so
Jaxb generates the following types into package namespacea and into package namespaceb:
public class CityData {
#XmlElement(required = true)
protected List<DistrictData> districtData;
//... jaxb explanation why there's no setter
public List<DistrictData> getDistrictData() {
if (districtData == null) {
districtData = new ArrayList<DistrictData>();
}
return this.districtData;
}
}
public class DistrictData {
protected int population;
public int getPopulation() {
return population;
}
public void setPopulation(int value) {
this.population = value;
}
}
Now If I create a source CityData from package namespacea and ask modelmapper to map it to a destination CityData in namespaceb then the data is not mapped:
CityData cityData = new CityData();
DistrictData districtData = new DistrictData();
districtData.setPopulation(1234);
cityData.getDistrictData().add(districtData);
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
namespaceb.CityData dest = modelMapper.map(cityData, namespaceb.CityData.class);
System.out.println("dest.districtData: " + dest.getDistrictData());
result is:
dest.districtData: []
In other words, districtData is not copied to destination.
I understand that ModelMapper does not find a setter for districtData and therefore not map it. I read that one can reconfigure Jaxb to generate setters for list properties, but the jaxb object generation is not in my hand in the project.
So I would like to find out if there is a nice solution for the mapping with ModelMapper, or maybe with other mapper library in these cases.
I've created a mini project: https://github.com/riskop/ModelMapperJaxb
I think that you just need to enable FieldMatching and set the access level of the fields to match to handle the missing setter. Check this configuration:
modelMapper.getConfiguration()
.setMatchingStrategy(MatchingStrategies.STRICT)
.setFieldMatchingEnabled(true)
.setFieldAccessLevel(AccessLevel.PROTECTED);
Javadoc:
setFieldAccessEnabled
Sets whether field matching should be enabled. When true, mapping may take place between accessible fields. Default is false.
setFieldAccessLevel
Indicates that fields should be eligible for matching at the given accessLevel.
Note: Field access is only used when field matching is enabled.
I had a rough idea of a clumsy workaround with ModelMapper.Converter facility before reading pirho's answer. I think that pirho's answer is better (accepted) but for the record, below is the Converter workaround. This is basically manually defining the conversion for the substructures where there is no setter:
CountryData countryData = new CountryData();
CityData cityData = new CityData();
DistrictData districtData = new DistrictData();
districtData.setPopulation(1234);
cityData.getDistrictData().add(districtData);
countryData.getCityData().add(cityData);
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
modelMapper.addConverter(new Converter<CountryData, namespaceb.CountryData>() {
#Override
public namespaceb.CountryData convert(MappingContext<CountryData, namespaceb.CountryData> context) {
namespaceb.CountryData result = new namespaceb.CountryData();
if(context.getSource() != null) {
for(CityData cityData : context.getSource().getCityData()) {
namespaceb.CityData mapped = modelMapper.map(cityData, namespaceb.CityData.class);
result.getCityData().add(mapped);
}
}
return result;
}
});
modelMapper.addConverter(new Converter<CityData, namespaceb.CityData>() {
#Override
public namespaceb.CityData convert(MappingContext<CityData, namespaceb.CityData> context) {
namespaceb.CityData result = new namespaceb.CityData();
if(context.getSource() != null) {
for(DistrictData districtData : context.getSource().getDistrictData()) {
namespaceb.DistrictData mapped = modelMapper.map(districtData, namespaceb.DistrictData.class);
result.getDistrictData().add(mapped);
}
}
return result;
}
});
namespaceb.CountryData destCountryData = modelMapper.map(countryData, namespaceb.CountryData.class);
assertEquals(1, destCountryData.getCityData().size());
namespaceb.CityData destCityData = destCountryData.getCityData().get(0);
assertEquals(1, destCityData.getDistrictData().size());
namespaceb.DistrictData destDistrictData = destCityData.getDistrictData().get(0);
assertEquals(1234, destDistrictData.getPopulation());
We have a requirement where around 25 CSV files would come each day & stored the Database in equivalent table structure.
Any of CSV file column structure could change in future by add new /remove columns & underlying DB table would align to the new format, without code change or redeployment.
Here are the choice of tech.
SpringBoot as Run time
Hibernate as JPA/DB Inetraction
Oracle DB as database
If using Hibernate, how to achieving this dynamic column management of the table as per the incoming CSV?
As far as I know, Hibernate would have Java Entity classes equivalent to the Table , which will be used to persists data. Any table change need Entity class change too.
Possible solution could be
just define basic JPA Entity & table structure (like ids & FKs linking to other tables etc) for CSV equivalent table,
then on arrival of CSV files, add the columns to the table by running the ALTER table command from application
In future 1st CSVs, if column added/removed , use similar alter commands
Is this achievable by Hibernate?
Or any other product better suited for this kind of tasks.
Task definition
We will have to implement a mechanism allowing for creating/deleting custom fields in real time avoiding the application restart, add a value into it and make sure the value is present in the application database. Besides we will have to make sure that the custom field can be used in queries.
Solution
Domain Model
We will first need a business entity class which we will experiment with. Let is be Contact class. There will be two persistent fields: id and name.
However besides these permanent and unchangeable fields the class should be some sort of construction to store values of custom fields. Map would be an ideal construction for this.
Let's create a base class for all business entities supporting custom fields - CustomizableEntity, that contains Map CustomProperties to work with custom fields:
package com.enterra.customfieldsdemo.domain;
import java.util.Map;
import java.util.HashMap;
public abstract class CustomizableEntity {
private Map customProperties;
public Map getCustomProperties() {
if (customProperties == null)
customProperties = new HashMap();
return customProperties;
}
public void setCustomProperties(Map customProperties) {
this.customProperties = customProperties;
}
public Object getValueOfCustomField(String name) {
return getCustomProperties().get(name);
}
public void setValueOfCustomField(String name, Object value) {
getCustomProperties().put(name, value);
}
}
Step 1 - base class CustomizableEntity
Inherit our class Contact from this base class:
package com.enterra.customfieldsdemo.domain;
import com.enterra.customfieldsdemo.domain.CustomizableEntity;
public class Contact extends CustomizableEntity {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Step 2 - Class Contact inherited from CustomizableEntity.
We should not forget about the mapping file for this class:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping auto-import="true" default-access="property" default-cascade="none" default-lazy="true">
<class abstract="false" name="com.enterra.customfieldsdemo.domain.Contact" table="tbl_contact">
<id column="fld_id" name="id">
<generator class="native"/>
</id>
<property name="name" column="fld_name" type="string"/>
<dynamic-component insert="true" name="customProperties" optimistic-lock="true" unique="false" update="true">
</dynamic-component>
</class>
</hibernate-mapping>
Step 3 - Mapping Class Contact.
Please note that properties id and name are done as all ordinary properties, however for customProperties we use a tag . Documentation on Hibernate 3.2.0GA says that the point of a dynamic-component is:
"The semantics of a mapping are identical to . The advantage of this kind of mapping is the ability to determine the actual properties of the bean at deployment time, just by editing the mapping document. Runtime manipulation of the mapping document is also possible, using a DOM parser. Even better, you can access (and change) Hibernate's configuration-time metamodel via the Configuration object."
Based on this regulation from Hibernate documentation we will be building this function mechanism.
HibernateUtil and hibernate.cfg.xml
After we are defined with the domain model of our application we have to create necessary conditions for Hibernate framework functioning. For this we have to create a configuration file hibernate.cfg.xml and class to work with the core Hibernate functions.
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration
PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="show_sql">true</property>
<property name="dialect">
org.hibernate.dialect.MySQLDialect</property>
<property name="cglib.use_reflection_optimizer">true</property>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/custom_fields_test</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password"></property>
<property name="hibernate.c3p0.max_size">50</property>
<property name="hibernate.c3p0.min_size">0</property>
<property name="hibernate.c3p0.timeout">120</property>
<property name="hibernate.c3p0.max_statements">100</property>
<property name="hibernate.c3p0.idle_test_period">0</property>
<property name="hibernate.c3p0.acquire_increment">2</property>
<property name="hibernate.jdbc.batch_size">20</property>
<property name="hibernate.hbm2ddl.auto">update</property>
</session-factory>
</hibernate-configuration>
Step 4 - Hibernate configuration file.
The file hibernate.cfg.xml does not contain anything noticeable except for this string:
<property name="hibernate.hbm2ddl.auto">update</property>
Step 5 - using auto-update.
Later we will explain in details on its purpose and tell more how we can go without it. There are several ways to implement class HibernateUtil. Our implementation will differ a bit from well known due to changes into Hibernate configuration.
package com.enterra.customfieldsdemo;
import org.hibernate.*;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.tool.hbm2ddl.SchemaUpdate;
import org.hibernate.cfg.Configuration;
import com.enterra.customfieldsdemo.domain.Contact;
public class HibernateUtil {
private static HibernateUtil instance;
private Configuration configuration;
private SessionFactory sessionFactory;
private Session session;
public synchronized static HibernateUtil getInstance() {
if (instance == null) {
instance = new HibernateUtil();
}
return instance;
}
private synchronized SessionFactory getSessionFactory() {
if (sessionFactory == null) {
sessionFactory = getConfiguration().buildSessionFactory();
}
return sessionFactory;
}
public synchronized Session getCurrentSession() {
if (session == null) {
session = getSessionFactory().openSession();
session.setFlushMode(FlushMode.COMMIT);
System.out.println("session opened.");
}
return session;
}
private synchronized Configuration getConfiguration() {
if (configuration == null) {
System.out.print("configuring Hibernate ... ");
try {
configuration = new Configuration().configure();
configuration.addClass(Contact.class);
System.out.println("ok");
} catch (HibernateException e) {
System.out.println("failure");
e.printStackTrace();
}
}
return configuration;
}
public void reset() {
Session session = getCurrentSession();
if (session != null) {
session.flush();
if (session.isOpen()) {
System.out.print("closing session ... ");
session.close();
System.out.println("ok");
}
}
SessionFactory sf = getSessionFactory();
if (sf != null) {
System.out.print("closing session factory ... ");
sf.close();
System.out.println("ok");
}
this.configuration = null;
this.sessionFactory = null;
this.session = null;
}
public PersistentClass getClassMapping(Class entityClass){
return getConfiguration().getClassMapping(entityClass.getName());
}
}
Step 6 - HibernateUtils class.
Alongside with usual methods like getCurrentSession(), getConfiguration(), which is necessary for regular work of the application based on Hibernate, we also have implemented such methods as: reset() and getClassMapping(Class entityClass). In the method getConfiguration(), we configure Hibernate and add class Contact into the configuration.
Method reset() has been used to close all used by Hibernate resources and clearing all of its settings:
public void reset() {
Session session = getCurrentSession();
if (session != null) {
session.flush();
if (session.isOpen()) {
System.out.print("closing session ... ");
session.close();
System.out.println("ok");
}
}
SessionFactory sf = getSessionFactory();
if (sf != null) {
System.out.print("closing session factory ... "); sf.close();
System.out.println("ok");
}
this.configuration = null;
this.sessionFactory = null;
this.session = null;
}
Step 7 - method reset()
Method getClassMapping(Class entityClass) returns object PersistentClass, that contains full information on mapping the related entity. In particular the manipulations with the object PersistentClass allow modifying the set of attributes of the entity class in the run-time.
public PersistentClass getClassMapping(Class entityClass){
return
getConfiguration().getClassMapping(entityClass.getName());
}
Step 8 - method getClassMapping(Class entityClass).
Manipulations with mapping
Once we have the business entity class (Contact) available and the main class to interact with Hibernate we can start working. We can create and save samples of the Contact class. We can even place some data into our Map customProperties, however we should be aware that this data (stored in Map customProperties) are not saved to the DB.
To have the data saved we should provide for the mechanism of creating custom fields in our classs and make it the way Hibernate knows how to work with them.
To provide for class mapping manipulation we should create some interface. Let's call it CustomizableEntityManager. Its name should reflect the purpose of the interface managing a business entity, its contents and attributes:
package com.enterra.customfieldsdemo;
import org.hibernate.mapping.Component;
public interface CustomizableEntityManager {
public static String CUSTOM_COMPONENT_NAME = "customProperties";
void addCustomField(String name);
void removeCustomField(String name);
Component getCustomProperties();
Class getEntityClass();
}
Step 9 - Interface CustomizableEntityManager
The main methods for the interface are: void addCustomField(String name) and void removeCustomField(String name). These should created and remove our custom field in the mapping of the corresponding class.
Below is the way to implement the interface:
package com.enterra.customfieldsdemo;
import org.hibernate.cfg.Configuration;
import org.hibernate.mapping.*;
import java.util.Iterator;
public class CustomizableEntityManagerImpl implements CustomizableEntityManager {
private Component customProperties;
private Class entityClass;
public CustomizableEntityManagerImpl(Class entityClass) {
this.entityClass = entityClass;
}
public Class getEntityClass() {
return entityClass;
}
public Component getCustomProperties() {
if (customProperties == null) {
Property property = getPersistentClass().getProperty(CUSTOM_COMPONENT_NAME);
customProperties = (Component) property.getValue();
}
return customProperties;
}
public void addCustomField(String name) {
SimpleValue simpleValue = new SimpleValue();
simpleValue.addColumn(new Column("fld_" + name));
simpleValue.setTypeName(String.class.getName());
PersistentClass persistentClass = getPersistentClass();
simpleValue.setTable(persistentClass.getTable());
Property property = new Property();
property.setName(name);
property.setValue(simpleValue);
getCustomProperties().addProperty(property);
updateMapping();
}
public void removeCustomField(String name) {
Iterator propertyIterator = customProperties.getPropertyIterator();
while (propertyIterator.hasNext()) {
Property property = (Property) propertyIterator.next();
if (property.getName().equals(name)) {
propertyIterator.remove();
updateMapping();
return;
}
}
}
private synchronized void updateMapping() {
MappingManager.updateClassMapping(this);
HibernateUtil.getInstance().reset();
// updateDBSchema();
}
private PersistentClass getPersistentClass() {
return HibernateUtil.getInstance().getClassMapping(this.entityClass);
}
}
Step 10 - implementing interface CustomizableEntityManager
First of all we should point out that when creating class CustomizableEntityManager we specify the business entity class the manager will operate. This class is passed as a parameter to designer CustomizableEntityManager:
private Class entityClass;
public CustomizableEntityManagerImpl(Class entityClass) {
this.entityClass = entityClass;
}
public Class getEntityClass() {
return entityClass;
}
Step 11 - class designer CustomizableEntityManagerImpl
Now we should get more interested in how to implement method void addCustomField(String name):
public void addCustomField(String name) {
SimpleValue simpleValue = new SimpleValue();
simpleValue.addColumn(new Column("fld_" + name));
simpleValue.setTypeName(String.class.getName());
PersistentClass persistentClass = getPersistentClass();
simpleValue.setTable(persistentClass.getTable());
Property property = new Property();
property.setName(name);
property.setValue(simpleValue);
getCustomProperties().addProperty(property);
updateMapping();
}
Step 12 - creating custom field.
As we can see from the implementation, Hibernate offers more options in working with properties of persistent objects and their representation in the DB. As per the essence of the method:
1) We create class SimpleValue that allow us to denote how the value of this custom field will be stored in the DB in which field and table of the DB:
SimpleValue simpleValue = new SimpleValue();
simpleValue.addColumn(new Column("fld_" + name));
simpleValue.setTypeName(String.class.getName());
PersistentClass persistentClass = getPersistentClass();
simpleValue.setTable(persistentClass.getTable());
Step 13 - creating new column of the table.
2) We create a property of the persistent object and add a dynamic component into it (!), that we have planned to be used for this purpose:
Property property = new Property()
property.setName(name)
property.setValue(simpleValue)
getCustomProperties().addProperty(property)
Step 14 - creating object property.
3) And finally we should make our application perform certain changes in the xml files and update the Hibernate configuration. This can be done via method updateMapping();
It is necessary to clarify the purpose of another two get-methods which have been used in the code above. The first method is getCustomProperties():
public Component getCustomProperties() {
if (customProperties == null) {
Property property = getPersistentClass().getProperty(CUSTOM_COMPONENT_NAME);
customProperties = (Component) property.getValue();
}
return customProperties;
}
Step 15 - getting CustomProperties as Component.
This method finds and returns object Component corresponding to the tag in the mapping of our business entity.
The second method is updateMapping():
private synchronized void updateMapping() {
MappingManager.updateClassMapping(this);
HibernateUtil.getInstance().reset();
// updateDBSchema();
}
Step 16 - method updateMapping().
The method is in charge for storing the updated mapping of the persistent class and updates the configuration status of Hibernate to make further changes that we make valid when the changes take effect.
By the way we should get back to the string:
<property name="hibernate.hbm2ddl.auto">update</property>
of the Hibernate configuration. If this string was missing we would have to launch executing updates of the DB schema using hibernate utilities. However using the setting allows us to avoid this.
Saving mapping
Modifications to mapping made in run-time do not save by themselves into the corresponding xml mapping file and to make the changes to get activated at next launch of the application we need to manually save changes to the corresponding mapping file.
To do this we will be using class MappingManager the main purpose of which is to save mapping of the designated business entity to its xml mapping file:
package com.enterra.customfieldsdemo;
import com.enterra.customfieldsdemo.domain.CustomizableEntity;
import org.hibernate.Session;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Property;
import org.hibernate.type.Type;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.Iterator;
public class MappingManager {
public static void updateClassMapping(CustomizableEntityManager entityManager) {
try {
Session session = HibernateUtil.getInstance().getCurrentSession();
Class<? extends CustomizableEntity> entityClass = entityManager.getEntityClass();
String file = entityClass.getResource(entityClass.getSimpleName() + ".hbm.xml").getPath();
Document document = XMLUtil.loadDocument(file);
NodeList componentTags = document.getElementsByTagName("dynamic-component");
Node node = componentTags.item(0);
XMLUtil.removeChildren(node);
Iterator propertyIterator = entityManager.getCustomProperties().getPropertyIterator();
while (propertyIterator.hasNext()) {
Property property = (Property) propertyIterator.next();
Element element = createPropertyElement(document, property);
node.appendChild(element);
}
XMLUtil.saveDocument(document, file);
} catch (Exception e) {
e.printStackTrace();
}
}
private static Element createPropertyElement(Document document, Property property) {
Element element = document.createElement("property");
Type type = property.getType();
element.setAttribute("name", property.getName());
element.setAttribute("column", ((Column)
property.getColumnIterator().next()).getName());
element.setAttribute("type",
type.getReturnedClass().getName());
element.setAttribute("not-null", String.valueOf(false));
return element;
}
}
Step 17 - the utility to update mapping of the persistent class.
The class literally performs the following:
Defines a location and loads xml mapping for the designated business entity into the DOM Document object for further manipulations with it;
Finds the element of this document . In particular here we store the custom fields and its contents we change;
Delete (!) all embedded elements from this element;
For any persistent property contained in our component that is in charge for the custom fields storage, we create a specific document element and define attributes for the element from the corresponding property;
Save this newly created mapping file.
When manipulating XML we use (as we can see from the code) class XMLUtil, that in general can be implemented in any way though it should correctly load and save the xml file.
Our implementation is given at the Step below:
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.dom.DOMSource;
import java.io.IOException;
import java.io.FileOutputStream;
public class XMLUtil {
public static void removeChildren(Node node) {
NodeList childNodes = node.getChildNodes();
int length = childNodes.getLength();
for (int i = length - 1; i > -1; i--)
node.removeChild(childNodes.item(i));
}
public static Document loadDocument(String file)
throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory factory =DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(file);
}
public static void saveDocument(Document dom, String file)
throws TransformerException, IOException {
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, dom.getDoctype().getPublicId());
transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, dom.getDoctype().getSystemId());
DOMSource source = new DOMSource(dom);
StreamResult result = new StreamResult();
FileOutputStream outputStream = new FileOutputStream(file);
result.setOutputStream(outputStream);
transformer.transform(source, result);
outputStream.flush();
outputStream.close();
}
}
Source: Please refer this article for more detail
I've got the following xsd tag:
<xs:complexType name="documentation">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="language" use="required"/>
</xs:extension>
</xs:simpleContent>
this generates (with jax-b):
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "documentation", propOrder = {
"value"
})
public class Documentation {
#XmlValue
protected String value;
#XmlAttribute(name = "language", required = true)
protected String language;
And I want some output like:
<documentation language="NL">SomeValue</documentation>
but Xstream generates:
<documentation language="NL">
<value>SomeValue</value>
</documentation>
how could I remove the value tags? I don't want them..
Code to generate the xml tags (this is just a snippet..):
private void createDocumentation(Description description, String docNL) {
List<Documentation> documentations = description.getDocumentation();
Documentation documentationNL = new Documentation();
documentationNL.setLanguage("NL");
documentationNL.setValue(docNL);
documentations.add(documentationNL);
}
private void createXmlFile(Description description) {
XStream xstream = new XStream(new DomDriver());
xstream.alias("description", Description.class);
xstream.alias("documentation", Documentation.class);
xstream.addImplicitCollection(Description.class, "documentation");
xstream.useAttributeFor(Documentation.class, "language");
String xml = xstream.toXML(description);
}
XStream provides a standard converter implementation called ToAttributedValueConverter that you can wire in for any simple-content-plus-attributes type like this:
#XStreamConverter(value = ToAttributedValueConverter.class, strings = { "value" })
public class Documentation {
protected String value;
protected String language;
}
The strings annotation element names the property that corresponds to the element content, all other properties will become attributes. If you want to declare the converter using xstream.registerConverter instead of using XStream annotations then you use
xstream.registerConverter(new ToAttributedValueConverter(Documentation.class,
xstream.getMapper(), xstream.getReflectionProvider(), xstream.getConverterLookup(),
"value"));
(the Mapper, ReflectionProvider and ConverterLookup objects get supplied to the converter automatically when you register it using annotations, but must be provided explicitly for registerConverter).
One option is to create a custom converter for your Documentation object.
Take a look at the XStream Converter tutorial
EDIT TS:
adding:
xstream.registerConverter(new DocumentationConverter());
and
public class DocumentationConverter implements Converter {
public boolean canConvert(Class clazz) {
return clazz.equals(Documentation.class);
}
public void marshal(Object value, HierarchicalStreamWriter writer,
MarshallingContext context) {
Documentation documentation = (Documentation) value;
writer.addAttribute("language", documentation.getLanguage());
writer.setValue(documentation.getValue());
}
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
Documentation documentation = new Documentation();
reader.moveDown();
documentation.setLanguage(reader.getAttribute("language"));
documentation.setValue(reader.getValue());
reader.moveUp();
return documentation;
}
}
did the job
Recently I have faced a problem which seems to be very common: how to represent an XML element with attributes and simple textual content, like this:
<elem attr="aval">elemval</elem>
using JAXB.
I've found many advices on how to do this, but every one of these advices involves manual editing of binding classes.
I have a set of schemas and I use XJC to convert these schemas to Java classes. However, it seems that it produces wrong code, i.e. it does not generate methods to set plain content, there are methods for setting attributes only.
Is it possible to fix this behavior of XJC? Extensive googling didn't help on this question.
Below is an XML schema that defines the XML structure for you use case.
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.org/schema"
xmlns:tns="http://www.example.org/schema" elementFormDefault="qualified">
<element name="elem">
<complexType>
<simpleContent>
<extension base="string">
<attribute name="attr" type="string" />
</extension>
</simpleContent>
</complexType>
</element>
</schema>
Generating a JAXB model from this XML schema will result in the following class:
package forum12859885;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlValue;
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "", propOrder = {
"value"
})
#XmlRootElement(name = "elem")
public class Elem {
#XmlValue
protected String value;
#XmlAttribute(name = "attr")
protected String attr;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getAttr() {
return attr;
}
public void setAttr(String value) {
this.attr = value;
}
}
I am using Jaxb2Marshaller to marshal Java beans through spring #ResponseBody annotation. For JSON marshaling was working fine. But for xml I was continuously getting HTTP 406 response. Little bit digging in Jaxb2Marshaller class reveals it checks for #XmlRootElement for bounded classes (see below snippet).
While generating java code from xsd my pojo does not contain #XmlRootElement and proper message converter was not identified by AnnotationMethodHandlerAdapter and finally result in 406.
Instead of auto generating java code from xsd I have created my own pojo class and used #XmlRootElement. Then marshaling works fine.
I want to understand why it is important of having #XmlRootElement check for bounded classes. Or any way to specify element as #XmlRootElement in xsd.
Code snippet from Jaxb2Marshaller:
public boolean supports(Class clazz) {
return supportsInternal(clazz, true);
}
private boolean supportsInternal(Class<?> clazz, boolean checkForXmlRootElement) {
if (checkForXmlRootElement && clazz.getAnnotation(XmlRootElement.class) == null) {
return false;
}
if (clazz.getAnnotation(XmlType.class) == null) {
return false;
}
if (StringUtils.hasLength(getContextPath())) {
String className = ClassUtils.getQualifiedName(clazz);
int lastDotIndex = className.lastIndexOf('.');
if (lastDotIndex == -1) {
return false;
}
String packageName = className.substring(0, lastDotIndex);
String[] contextPaths = StringUtils.tokenizeToStringArray(getContextPath(), ":");
for (String contextPath : contextPaths) {
if (contextPath.equals(packageName)) {
return true;
}
}
return false;
}
else if (!ObjectUtils.isEmpty(classesToBeBound)) {
return Arrays.asList(classesToBeBound).contains(clazz);
}
return false;
}
Edit:
Blaise answer helped me solve #XmlRootElement problem. But still if someone has any information about why check for XmlRootElement is required, will be a good info.
Why the #XmlRootElement Annotation is Checked For
Spring requires a root element when marshalling the object to XML. JAXB provides two mechanisms to do this:
The #XmlRootElement annotation
Wrapping the root object in an instance of JAXBElement.
Since the object is not wrapped in a JAXBElement Spring is ensuring that the other condition is met.
How Generate an #XmlRootElement
JAXB will generate an #XmlRootElement annotation for all global elements in an XML schema. The following will cause an #XmlElement:
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="foo">
<xsd:complexType>
...
</xsd:complextType>
</xsd:element>
</xsd:schema>
When #XmlRootElement is not Generated
An #XmlRootElement annotation will not be generated for global types.
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="foo" type="foo"/>
<xsd:complexType name="foo">
...
</xsd:complexType>
</xsd:schema>
Instead the global element(s) associated with the global types are captured in the ObjectFactory class (annotated with #XmlRegistry) in the form of #XmlElementDecl annotations. These annotations
package generated;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.namespace.QName;
#XmlRegistry
public class ObjectFactory {
private final static QName _Foo_QNAME = new QName("", "foo");
public Foo createFoo() {
return new Foo();
}
#XmlElementDecl(namespace = "", name = "foo")
public JAXBElement<Foo> createFoo(Foo value) {
return new JAXBElement<Foo>(_Foo_QNAME, Foo.class, null, value);
}
}
The #XmlElementDecl annotation provides similar information as #XmlRootElement and could be used for unmarshal operations. JAX-RS implementations probably do not leverage #XmlElementDecl however since marshal operations would require the object to be wrapped in a JAXBElement object to provide the root element name/namespace.
it's an known issue:
https://jira.springsource.org/browse/SPR-7931
"Checking for #XmlRootElement annotation should be made optional in Jaxb2Marshaller"
You can use JaxbElement for classes that does not have #XmlRootElement annotation. #XmlRootElement annotation is placed only to non referenced top level objects if you are generating your code from xsd
Edit
See #Blaise Doughan answer.
#XmlRootElement will be placed only if there is no reference to that type in another type.