Trying to serialize an object compactly using Castor - java

I'm using Castor to write out a map of user ID's to time intervals. I'm using it to save and resume progress in a lengthy task, and I'm trying to make the XML as compact as possible. My map is from string userID's to a class that contains the interval timestamps, along with additional transient data that I don't need to serialize.
I'm able to use a nested class mapping:
...
<field name="userIntervals" collection="map">
<bind-xml name="u">
<class name="org.exolab.castor.mapping.MapItem">
<field name="key" type="string"><bind-xml name="n" node="attribute"/></field>
<field name="value" type="my.package.TimeInterval"/>
</class>
</bind-xml>
</field>
...
<class name="my.package.TimeInterval">
<map-to xml="ti"/>
<field name="intervalStart" type="long"><bind-xml name="s" node="attribute"/></field>
<field name="intervalEnd" type="long"><bind-xml name="e" node="attribute"/></field>
</class>
...
And get output that looks like:
<u n="36164639"><value s="1292750896000" e="1292750896000"/></u>
What I'd like is the name, start, and end of the user in a single node like this.
<u n="36164639" s="1292750896000" e="1292750896000"/>
But I can't seem to finagle it so the start and end attributes in the "value" go in the same node as the "key". Any ideas would be greatly appreciated.

Nash,
I think to arrange the castor mapping is bit tricky.
If you want to have structure like
<u n="36164639" s="1292750896000" e="1292750896000"/>
Then you need to create a new pojo file where it will be having
all the three fields Key,intervalStart,intervalEnd.
And let the File name as KeyTimeInterval
And map it like the below.
<field name="userIntervals" collection="map">
<class name="org.exolab.castor.mapping.MapItem">
<field name="u" type="my.package.KeyTimeInterval">
<bind-xml name="u" node="element"/>
</field>
</class>
</field>
<class name="my.package.KeyTimeInterval">
<field name="key" type="String">
<bind-xml name="n" node="attribute"/></field>
<field name="intervalStart" type="long">
<bind-xml name="s" node="attribute"/></field>
<field name="intervalEnd" type="long">
<bind-xml name="e" node="attribute"/></field>
</class>

I think you should be able to use location on s and e. Try this:-
...
<class name="my.package.TimeInterval">
<map-to xml="ti"/>
<field name="intervalStart" type="long">
<bind-xml name="s" location="u" node="attribute"/>
</field>
<field name="intervalEnd" type="long">
<bind-xml name="e" location="u" node="attribute"/>
</field>
</class>

Am answering my own question here, since there is a solution that does exactly what I want, and there's actually an error in the explanation at http://www.castor.org/xml-mapping.html#Sample-3:-Using-the-container-attribute - the container attribute is exactly what's needed here.
Changing one line in the mapping:
<field name="value" type="my.package.TimeInterval" container="true"/>
did exactly what I wanted, it didn't create a subelement for the value, just mapped the fields into the existing parent element. Since then, I've used this quite a few times to map multiple-value classes into their parent.
The error of course is the documentation states you do this by setting the container attribute to false. Of course, it should be true.

Related

Key-value mapping in BeanIO fixed-length record

I have the following specification for a fixed-length data file (refer to record-C type of specification, page 4)
a second part, having a length of 1,800 characters, consisting of a table of 75 elements to be used for the display of the only data present in the communication; each of these elements is constituted by a field-code
of 8 characters and by a field-value of 16 characters
It means that the first 89 characters (omitted in the above summary) are plain old fixed-length and then, for the remaining 1800, I have to take them into groups of key-value pairs each counting up to 24 characters. Blank spaces are trimmed and empty pairs are not considered in the process.
Ideally, my bean may be constructed like
public class RecordC{
private List<Pair<String, String>> table = new ArrayList<>(MAX_TABLE_SIZE); //I don't want to use Map **yet**
}
Something can be e.g. Apache Common's Pair<String,String> or anything suitable for KVP mapping.
I understand that I can create a whole TypeHandler that takes the full 1800 bytes but I wanted to exploit the power of BeanIO.
Here is what I have done so far
<record name="RECORD_C" class="it.csttech.ftt.data.beans.ftt2017.RecordC" order="3" minOccurs="1" maxOccurs="1" maxLength="2000">
<field name="tipoRecord" rid="true" at="0" ignore="true" required="true" length="1" lazy="true" literal="C" />
<field name="cfContribuente" at="1" length="16" align="left" trim="true" lazy="true" />
<field name="progressivoModulo" at="17" length="8" padding="0" align="right" trim="true" lazy="true" />
<field name="spazioDisposizioneUtente" at="25" length="3" align="left" trim="true" lazy="true" />
<field name="spazioUtente" at="53" length="20" align="left" trim="true" lazy="true" />
<field name="cfProduttoreSoftware" at="73" length="16" align="left" trim="true" lazy="true" />
<segment name="table" collection="list" lazy="true" class="org.apache.commons.lang3.tuple.ImmutablePair">
<field name="key" type="java.lang.String" at="0" length="8" trim="true" lazy="true" setter="#1" />
<field name="value" type="java.lang.String" at="8" length="16" trim="true" lazy="true" setter="#2" />
</segment>
<field name="terminatorA" at="1897" length="1" rid="true" literal="A" ignore="true" />
</record>
Unfortunately this does not work in testing. I get only a single record in the list, decoded at positions [0-7] and [8-23] instead of expected [89-113][114-???][....][....]
Question is: how do I in BeanIO declare repeating fixed-length fields?
I have currently resolved my unmarshalling problem by removing all at attributes in the RecordC specifications. As I found out, the "at" attribute is absolute to the record and not relative to the repeating segment. However this forced me to add some ignored fields in the unmarshalling at the sole cost of a few ignores.
I will test the marshalling against the official controller once I have data

Unmarshalling list of objects using castor gives java.lang.IllegalArgumentException: object is not an instance of declaring class

I'm using castor 1.3.3-rc1 and I've been puzzled with this problem. Have read the manuals few times and I believe I've done everything right here, but I keep getting :
java.lang.IllegalArgumentException: object is not an instance of declaring class{File: [not available]; line: 4; column: 43}
when unmarshalling my xml.
These are my java classes:
public class ReportConfiguration {
private List<ColumnMapping> columnMappings;
// getters and setters omitted
}
public class ColumnMapping {
private int index;
private String label;
private String sumTotal;
// getters and setters omitted
}
This is my xml data file which will be unmarshalled into java classes above
<reportConfiguration>
<columnMappings>
<columnMapping index="0" label="Login"/>
<columnMapping index="1" label="Group"/>
<columnMapping index="2" label="Profit" sumTotal="yes"/>
</columnMappings>
</reportConfiguration>
And this is my castor mapping file
<mapping>
<class name="my.company.ReportConfiguration">
<map-to xml="reportConfiguration"/>
<field name="columnMappings" collection="arraylist" type="my.company.ColumnMapping">
<bind-xml name="columnMappings"/>
</field>
</class>
<class name="my.company.ColumnMapping">
<map-to xml="columnMapping"/>
<field name="index" type="integer" required="true">
<bind-xml name="index" node="attribute"/>
</field>
<field name="label" type="string" required="true">
<bind-xml name="label" node="attribute"/>
</field>
<field name="sumTotal" type="string">
<bind-xml name="sumTotal" node="attribute"/>
</field>
</class>
</mapping>
I used Spring OXM, created a org.springframework.oxm.castor.CastorMarshaller instance on my application context, and injected an Unmarshaller instance as dependency. When unmarshalling I just do something like this:
ReportConfiguration config = (ReportConfiguration) unmarshaller.unmarshall(new StreamSource(inputStream));
Can anyone spot what did I do wrong / how else I can debug this problem ?
Ah actually I found the answer. I need to supply container="false" attribute on the castor mapping :
<field name="columnMappings" collection="arraylist" type="my.company.ColumnMapping" container="false">
<bind-xml name="columnMappings"/>
</field>
This is what castor manual says:
container Indicates whether the field should be treated as a
container, i.e. only it's fields should be persisted, but not the
containing class itself. In this case, the container attribute should
be set to true (supported in Castor XML only).
I think the default is true -- in which case castor hopes to find multiple instance of <columnMapping> directly under <reportConfiguration>, not contained inside a <columnMappings>
A more helpful error message could be presented.

What is the syntax of package.jdo file for a self referential table

I have a self referential table that I am mapping with kodo jdo 4 (supplied from weblogic server 10.3.4.) The code I have will get through the enhancer, but when I try to use it I get the error:
<openjpa-1.1.1-SNAPSHOT-r422266:965591 fatal user error> kodo.jdo.FatalUserException: Missing table name for field "com.[...].jdo.Branch.branches". This field cannot reside in the owning class table.
at org.apache.openjpa.jdbc.meta.FieldMapping.mapJoin(FieldMapping.java:529)
the table is:
CREATE TABLE branch
(
id VARCHAR2(10) NOT NULL,
parentId VARCHAR2(10) NOT NULL,
[other fields deleted ...]
CONSTRAINT branch_pk PRIMARY KEY(id),
CONSTRAINT branch_fk_parent FOREIGN KEY(parentId) REFERENCES branch(id)
);
the class is :
public class Branch implements MenuPart, Serializable
{
private Branch parent;
private Set<Branch> branches = new HashSet<Branch> ();
private String id;
private Set<Leaf> leafs = new HashSet<Leaf> ();
private long ordering;
private String title;
//methods removed.
}
the package.jdo file is:
<class name="Branch" objectid-class="BranchId" table="SCHEMA.BRANCH">
<version strategy="none"/>
<field name="parent" table="SCHEMA.BRANCH">
<column name="SCHEMA.BRANCH.PARENTID" target="ID" />
</field>
<field name="branches" table="SCHEMA.BRANCH" >
<collection element-type="Branch"/>
<join>
<column name="SCHEMA.BRANCH.PARENTID" target="ID" />
</join>
</field>
<field name="id" column="ID" primary-key="true"/>
<field name="leafs" table="SCHEMA.LEAF">
<collection element-type="Leaf"/>
<join>
<column name="ID" target="BRANCHID" />
</join>
</field>
<field name="ordering" column="ORDERING"/>
<field name="title" column="TITLE"/>
</class>
I have gotten the functionality to work in kodo jdo 3.4 but now the syntax is different and the friendly manual isn't that helpful that I can find.
Perhaps its objecting to you defining the join-table for that relation to be the table for the class itself (which is obviously wrong).
Obviously Kodo is not fully JDO-compliant, and is dead as a project, so if you hit issues then you've got problems ...
Can you try
<class name="Branch" objectid-class="BranchId" table="SCHEMA.BRANCH">
<version strategy="none"/>
<field name="parent" table="SCHEMA.BRANCH">
<column name="SCHEMA.BRANCH.PARENTID" target="ID" />
</field>
<field name="branches" table="SCHEMA.BRANCH" >
<collection element-type="Branch"/>
<extension vendor-name="kodo" key="inverse-owner" value="parent"/>
</field>
<field name="id" column="ID" primary-key="true"/>
</class>
here is what finally worked
<class name="Branch" objectid-class="BranchId" table="EBIGP.BRANCH">
<version strategy="none"/>
<field name="parent">
<column name="EBIGP.BRANCH.PARENTID" target="ID" />
</field>
<field name="branches" mapped-by="parent" />
<field name="id" column="ID" primary-key="true"/>
<field name="leafs" default-fetch-group="true">
<collection element-type="Leaf"/>
<element column="BRANCHID"/>
</field>
<field name="ordering" column="ORDERING"/>
<field name="title" column="TITLE"/>
</class>
Apparently all it needed was a mapped-by="parent" attribute.

xml to java object using castor

What can I do to ignore the <envelope> and <body> tags in unmarshall process using Castor?
Xml examole:
<?xml version="1.0" encoding="UTF-8"?>
<envelope>
<header>
<message>consultaTelefonosVigentesSocios</message>
</header>
<body>
<datosTelefonosVigentesSocios>
<listaTelefonosVigentesSocios>
<nroInterlocutor>2000393451672</nroInterlocutor>
<nroContrato>S6125345450573001</nroContrato>
<nroTelefono>011-4454451-8293</nroTelefono>
<tipoTelefono>T</tipoTelefono>
<claseDireccion>Z001</claseDireccion>
<descClaseDireccion>Correspondencia</descClaseDireccion>
<marcaEstandar>X</marcaEstandar>
<nroInterlocutorAsociadoDomicilio>200053945351672</nroInterlocutorAsociadoDomicilio>
</listaTelefonosVigentesSocios>
<listaTelefonosVigentesSocios>
<nroInterlocutor>200053435391672</nroInterlocutor>
<nroContrato>S612535430573001</nroContrato>
<nroTelefono>011-44453551-8299</nroTelefono>
<tipoTelefono>T</tipoTelefono>
<claseDireccion>Z001</claseDireccion>
<descClaseDireccion>Correspondencia</descClaseDireccion>
<marcaEstandar/>
<nroInterlocutorAsociadoDomicilio>20005543391672</nroInterlocutorAsociadoDomicilio>
</listaTelefonosVigentesSocios>
</datosTelefonosVigentesSocios>
</body>
<fault>
<faultactor>servicios.page:consultaTelefonosVigentesSocios</faultactor>
</fault>
</envelope>
castor mapping file:
<?xml version="1.0"?>
<mapping>
<class
name="ar.com.telefonosSocioByNroContratoService.backend.service.TelefonosVigentesSocios">
<map-to xml="datosTelefonosVigentesSocios" />
<field name="listaTelefonosVigentesSocios"
type="ar.com.telefonosSocioByNroContratoService.backend.service.TelefonoVigenteSocio"
collection="arraylist">
<bind-xml name="listaTelefonosVigentesSocios" />
</field>
</class>
<class
name="ar.com.telefonosSocioByNroContratoService.backend.service.TelefonoVigenteSocio">
<map-to xml="listaTelefonosVigentesSocios" />
<field name="nroInterlocutor" type="java.lang.String">
<bind-xml name="nroInterlocutor" node="element" />
</field>
<field name="nroContrato" type="java.lang.String">
<bind-xml name="nroContrato" node="element" />
</field>
<field name="nroTelefono" type="java.lang.String">
<bind-xml name="nroTelefono" node="element" />
</field>
<field name="tipoTelefono" type="java.lang.String">
<bind-xml name="tipoTelefono" node="element" />
</field>
<field name="marcaEstandar" type="java.lang.String">
<bind-xml name="marcaEstandar" node="element" />
</field>
<field name="descClaseDireccion" type="java.lang.String">
<bind-xml name="descClaseDireccion" node="element" />
</field>
<field name="nroInterlocutorAsociadoDomicilio" type="java.lang.String">
<bind-xml name="nroInterlocutorAsociadoDomicilio" node="element" />
</field>
</class>
</mapping>
Test Class:
public class TelefonosSocioByNroContratoServiceTest {
#Test
public void testUsuarioIntranetListfromXML() throws Exception{
Mapping mapping= new Mapping();
ClassPathResource mappingResource =
new ClassPathResource("/ar/com/telefonosSocioByNroContratoService/backend/service/telefonosVigenteSocios.map.xml");
mapping.loadMapping(mappingResource.getURL());
ClassPathResource inputExample= new ClassPathResource("ar/com/test/castor/consultaTelefonosVigentesSocios.xml");
Reader reader = new FileReader(inputExample.getFile());
Unmarshaller unmarshaller = new Unmarshaller(TelefonosVigentesSocios.class);
unmarshaller.setMapping(mapping);
TelefonosVigentesSocios telefonosVigentesSocios = (TelefonosVigentesSocios) unmarshaller.unmarshal(reader);
reader.close();
Assert.assertNotNull(telefonosVigentesSocios);
Assert.assertNotNull(telefonosVigentesSocios.getListaTelefonosVigentesSocios());
Assert.assertTrue("se esperaba not empty telefonos",!telefonosVigentesSocios.getListaTelefonosVigentesSocios().isEmpty());
}
}
Instead of using an input stream, you could use an XMLStreamReader (StAX) as your input. Then advance the XMLStreamReader to the start element event for the content you mapped to. Then have Castor unmarshal from the XMLStreamReader.
If Castor does not support StAX then I can show you how to do it with JAXB. I lead the EclipseLink JAXB implementation (MOXy).

Many-to-Many relationship in DataNucleus (JDO) doesn't persist

I don't manage to persist a many-to-many link with DataNucleus using JDO. I have two classes Book and Shop. This is the orm mapping file:
<?xml version="1.0"?>
<!DOCTYPE orm PUBLIC
"-//Sun Microsystems, Inc.//DTD Java Data Objects Metadata 2.0//EN"
"http://java.sun.com/dtd/orm_2_0.dtd">
<orm>
<package name="com.mypackage.pojo">
<class name="Book" identity-type="datastore">
<datastore-identity>
<column name="BOOK_ID" />
</datastore-identity>
<field name="name">
<column length="100" jdbc-type="VARCHAR" />
</field>
<field name="shops" persistence-modifier="persistent"
table="BOOKS_SHOPS">
<collection element-type="com.mypackage.pojo.Shop" />
<join>
<column name="BOOK_ID" />
</join>
<element>
<column name="SHOP_ID" />
</element>
</field>
</class>
<class name="Shop" identity-type="datastore">
<datastore-identity>
<column name="SHOP_ID" />
</datastore-identity>
<field name="name">
<column length="50" jdbc-type="VARCHAR" />
</field>
<field name="books" persistence-modifier="persistent"
table="BOOKS_SHOPS">
<collection element-type="com.mypackage.pojo.Book" />
<join>
<column name="SHOP_ID" />
</join>
<element>
<column name="BOOK_ID" />
</element>
</field>
</class>
</package>
</orm>
I try to link a book to a shop and the other way around, like this:
shop.addBook(book);
book.addShop(shop);
Making these two objects persistent again doesn't do anything. Both before and after the little code snippet above, their ObjectState is detached-clean.
What could I be doing wrong?
I have a working solution, but have to admit I do not fully understand everything. Things work when the last field element is not defined as
<field name="books" persistence-modifier="persistent" table="BOOKS_SHOPS">
but as
<field name="books" persistence-modifier="persistent" mapped-by="shops">
This solved my problem.
See also the DataNucleus manual on JDO M-N Relationships. However, leaving the join and the element elements out, as is done in this example, didn't work for me. Another relevant link on the DataNucleus site is JDO Guides : M-N Relation. The code for this last example can be found on SourceForge. Unfortunately, this example also didn't work for me.
This is not really a good answer, but all that I have to offer for now...

Categories