Context:: Getting error in solr-core 5.5.0 when overriding ClassicSimilarityFactory. Have pasted the logs and class. Overriding ClassicSimilarityFactory throws error- Context:: Getting error in solr-core 5.5.0 when overriding ClassicSimilarityFactory. Have pasted the logs and class. Overriding ClassicSimilarityFactory throws error-
package com.Others;
import org.apache.lucene.analysis.payloads.PayloadHelper;
import org.apache.lucene.search.similarities.ClassicSimilarity;
import org.apache.lucene.search.similarities.Similarity;
import org.apache.lucene.util.BytesRef;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.search.similarities.ClassicSimilarityFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PayloadSimilarityFactory extends ClassicSimilarityFactory {
#Override
public void init(SolrParams params) {
super.init(params);
}
#Override
public Similarity getSimilarity() {
return new PayloadSimilarity();
}
}
class PayloadSimilarity extends ClassicSimilarity {
private final static Logger logger = LoggerFactory.getLogger(PayloadSimilarity.class);
//Here's where we actually decode the payload and return it.
#Override
public float scorePayload(int doc, int start, int end, BytesRef payload) {
logger.info("PayloadSimilarity ---- " + payload);
if (payload == null)
return 1.0F;
return PayloadHelper.decodeFloat(payload.bytes, payload.offset);
}
}
Schema.xml file :
<fieldType name="payloads" class="solr.TextField" indexed="true" stored="true">
<analyzer>
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.DelimitedPayloadTokenFilterFactory" encoder="float" delimiter="|"/>
</analyzer>
<similarity class="com.Others.PayloadSimilarityFactory"/>
</fieldType>
</types>
Error Logs :
Error creating core [catalog]: Could not load conf for core catalog: Can't load schema /Users/z002cww/repo/jarvis-solr-config/config/catalog/conf/schema.xml: FieldType 'payloads' is configured with a similarity, but the global similarity does not support it: class org.apache.solr.search.similarities.ClassicSimilarityFactory
org.apache.solr.common.SolrException: Could not load conf for core catalog: Can't load schema /Users/z002cww/repo/jarvis-solr-config/config/catalog/conf/schema.xml: FieldType 'payloads' is configured with a similarity, but the global similarity does not support it: class org.apache.solr.search.similarities.ClassicSimilarityFactory
at org.apache.solr.core.ConfigSetService.getConfig(ConfigSetService.java:84)
at org.apache.solr.core.CoreContainer.create(CoreContainer.java:812)
at org.apache.solr.core.CoreContainer.access$000(CoreContainer.java:87)
at org.apache.solr.core.CoreContainer$1.call(CoreContainer.java:467)
You'll have to use the SchemaSimilarityFactory as the configured default similarity to support using per field similarities.
<similarity class="solr.SchemaSimilarityFactory"/>
See one of the answers to Solr Lucene Scorer and the mailinglist answer for the same question.
Related
This is my XML which I want to unmarshal (I do not have XSD for it). And I have to unmarshal it to get an array of names and Id's. So, names are getting generated just fine, but the issue is with the Id's. The Id's are set to null. Can anyone please help me identify the issue?
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" expressionLanguage="http://www.w3.org/1999/XPath" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn" typeLanguage="http://www.w3.org/2001/XMLSchema">
<bpmn:collaboration id="Collaboration_0b6zt1k" isClosed="false">
<bpmn:participant id="Participant_113vq9r" processRef="CallActivity_00qe833" />
</bpmn:collaboration>
<bpmn:process id="CallActivity_00qe833" isClosed="false" isExecutable="true" name="Some Text" processType="None">
<bpmn:serviceTask activiti:class="Assign PC" completionQuantity="1" id="ServiceTask_0ip6tj7" implementation="##WebService" isForCompensation="false" name="Some Text 1" startQuantity="1">
<bpmn:extensionElements>
<activiti:properties>
<activiti:property name="specKey" value="ServiceTask_0ip6tj7" />
</activiti:properties>
</bpmn:extensionElements>
<bpmn:incoming>SequenceFlow_0sa9y9o</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_1bd3qmp</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:serviceTask activiti:class="generateURL" completionQuantity="1" id="ServiceTask_11t11da" implementation="##WebService" isForCompensation="false" name="Some Text 2" startQuantity="1">
<bpmn:extensionElements>
<activiti:properties>
<activiti:property name="specKey" value="ServiceTask_11t11da" />
</activiti:properties>
</bpmn:extensionElements>
<bpmn:incoming>SequenceFlow_1bd3qmp</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_0cynzzs</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:serviceTask activiti:class="generateURL" completionQuantity="1" id="ServiceTask_11t11da" implementation="##WebService" isForCompensation="false" name="Some Text 3" startQuantity="1">
<bpmn:extensionElements>
<activiti:properties>
<activiti:property name="specKey" value="ServiceTask_11t11da" />
</activiti:properties>
</bpmn:extensionElements>
<bpmn:incoming>SequenceFlow_1bd3qmp</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_0cynzzs</bpmn:outgoing>
</bpmn:serviceTask>
</bpmn:process>
</bpmn:definitions>
This is my JAXB annotated class:
package XMLToObject;
import java.util.List;
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 org.eclipse.persistence.oxm.annotations.XmlPath;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class definitions {
#XmlPath("bpmn:process/bpmn:serviceTask/#name")
#XmlAttribute
List<String> name;
#XmlPath("bpmn:process/bpmn:serviceTask/#id")
#XmlAttribute
List<String> id;
#XmlAttribute
String typeLanguage;
public List<String> getname() {
return name;
}
public List<String> getid() {
return id;
}
public String gettypeLanguage() {
return typeLanguage;
}
}
This is my Java Class:
package XMLToObject;
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
/**
*
* #author Gauravb
*/
import java.io.File;
import java.util.Collection;
import java.util.List;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
public class XMLToObject {
public static void main(String[] args) {
try {
File file = new File("employee.xml");
JAXBContext jaxbContext = JAXBContext.newInstance(definitions.class);
System.out.println("JAXB....."+jaxbContext.toString());
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
definitions p = (definitions) jaxbUnmarshaller.unmarshal(file);
System.out.println("Task Name:"+p.getname());
System.out.println("Svc ID:"+p.getid());
System.out.println("Type Language:"+p.gettypeLanguage());
} catch (JAXBException e) {e.printStackTrace(); }
}
}
Now, this is the output I am getting:
run:
JAXB.....org.eclipse.persistence.jaxb.JAXBContext#18eed359
Task Name:[Some Text 1, Some Text 2, Some Text 3]
Svc ID:null
Type Language:http://www.w3.org/2001/XMLSchema
BUILD SUCCESSFUL (total time: 0 seconds)
My issue is that why I am getting Svc ID as "null" when the same is working for Task Name.
Please note:
1. I am using MOXy for JAXB
2. Please note that when I change the sequence then names are getting set to null:
#XmlPath("bpmn:process/bpmn:serviceTask/#id")
#XmlAttribute
List<String> id;
#XmlPath("bpmn:process/bpmn:serviceTask/#name")
#XmlAttribute
List<String> name;
I am getting this output:
run:
JAXB.....org.eclipse.persistence.jaxb.JAXBContext#18eed359
Task Name:null
Svc ID:[ServiceTask_0ip6tj7, ServiceTask_11t11da, ServiceTask_11t11da]
Type Language:http://www.w3.org/2001/XMLSchema
BUILD SUCCESSFUL (total time: 0 seconds)
Looks like mulltiple attributes on Moxy is not supported (What a shame :( I just started to love MOXy)
Attached is a link
With MOXy and XPath, is it possible to unmarshal two lists of attributes?
Is #UDT (http://docs.datastax.com/en/developer/java-driver/2.1/java-driver/reference/mappingUdts.html) supported by Spring-data-Cassandra 1.3.2.RELEASE? If not, how can I add workaround for this
Thanks
See the details here:
https://jira.spring.io/browse/DATACASS-172
I faced with the same issue and it sounds like it does not(
debug process shows me that spring data cassandra check for
#Table, #Persistent or #PrimaryKeyClass Annotation only and raise exception
in other case
>
Invocation of init method failed; nested exception is org.springframework.data.cassandra.mapping.VerifierMappingExceptions:
Cassandra entities must have the #Table, #Persistent or #PrimaryKeyClass Annotation
But I found the solution.
I figured out the approach that allows me to manage entities that include UDT and the ones that don't. In my application I use spring cassandra data project together with using of direct datastax core driver. The repositories that don't contain object with UDT use spring cassanta data approach and the objects that include UDT use custom repositories.
Custom repositories use datastax mapper and they work correctly with UDT
(they located in separate package, see notes below why it's needed):
package com.fyb.cassandra.custom.repositories.impl;
import java.util.List;
import java.util.UUID;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.cassandra.config.CassandraSessionFactoryBean;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.mapping.Mapper;
import com.datastax.driver.mapping.MappingManager;
import com.datastax.driver.mapping.Result;
import com.google.common.collect.Lists;
import com.fyb.cassandra.custom.repositories.AccountDeviceRepository;
import com.fyb.cassandra.dto.AccountDevice;
public class AccountDeviceRepositoryImpl implements AccountDeviceRepository {
#Autowired
public CassandraSessionFactoryBean session;
private Mapper<AccountDevice> mapper;
#PostConstruct
void initialize() {
mapper = new MappingManager(session.getObject()).mapper(AccountDevice.class);
}
#Override
public List<AccountDevice> findAll() {
return fetchByQuery("SELECT * FROM account_devices");
}
#Override
public void save(AccountDevice accountDevice) {
mapper.save(accountDevice);
}
#Override
public void deleteByConditions(UUID accountId, UUID systemId, UUID deviceId) {
final String query = "DELETE FROM account_devices where account_id =" + accountId + " AND system_id=" + systemId
+ " AND device_id=" + deviceId;
session.getObject().execute(query);
}
#Override
public List<AccountDevice> findByAccountId(UUID accountId) {
final String query = "SELECT * FROM account_devices where account_id=" + accountId;
return fetchByQuery(query);
}
/*
* Take any valid CQL query and try to map result set to the given list of appropriates <T> types.
*/
private List<AccountDevice> fetchByQuery(String query) {
ResultSet results = session.getObject().execute(query);
Result<AccountDevice> accountsDevices = mapper.map(results);
List<AccountDevice> result = Lists.newArrayList();
for (AccountDevice accountsDevice : accountsDevices) {
result.add(accountsDevice);
}
return result;
}
}
And the spring data related repos that resonsible for managing entities that don't include UDT objects looks like as follows:
package com.fyb.cassandra.repositories;
import org.springframework.data.cassandra.repository.CassandraRepository;
import com.fyb.cassandra.dto.AccountUser;
import org.springframework.data.cassandra.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.UUID;
#Repository
public interface AccountUserRepository extends CassandraRepository<AccountUser> {
#Query("SELECT * FROM account_users WHERE account_id=?0")
List<AccountUser> findByAccountId(UUID accountId);
}
I've tested this solution and it's works 100%.
In addition I've attached my POJO objects:
Pojo that uses only data stax annatation:
package com.fyb.cassandra.dto;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.datastax.driver.mapping.annotations.ClusteringColumn;
import com.datastax.driver.mapping.annotations.Column;
import com.datastax.driver.mapping.annotations.Frozen;
import com.datastax.driver.mapping.annotations.FrozenValue;
import com.datastax.driver.mapping.annotations.PartitionKey;
import com.datastax.driver.mapping.annotations.Table;
#Table(name = "account_systems")
public class AccountSystem {
#PartitionKey
#Column(name = "account_id")
private java.util.UUID accountId;
#ClusteringColumn
#Column(name = "system_id")
private java.util.UUID systemId;
#Frozen
private Location location;
#FrozenValue
#Column(name = "user_token")
private List<UserToken> userToken;
#Column(name = "product_type_id")
private int productTypeId;
#Column(name = "serial_number")
private String serialNumber;
}
Pojo without using UDT and using only spring data cassandra framework:
package com.fyb.cassandra.dto;
import java.util.Date;
import java.util.UUID;
import org.springframework.cassandra.core.PrimaryKeyType;
import org.springframework.data.cassandra.mapping.Column;
import org.springframework.data.cassandra.mapping.PrimaryKeyColumn;
import org.springframework.data.cassandra.mapping.Table;
#Table(value = "accounts")
public class Account {
#PrimaryKeyColumn(name = "account_id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
private java.util.UUID accountId;
#Column(value = "account_name")
private String accountName;
#Column(value = "currency")
private String currency;
}
Note, that the entities below use different annotations:
#PrimaryKeyColumn(name = "account_id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)and #PartitionKey
#ClusteringColumn and #PrimaryKeyColumn(name = "area_parent_id", ordinal = 2, type = PrimaryKeyType.CLUSTERED)
At first glance - it's uncomfortable, but it allows you to work with objects that includes UDT and that don't.
One important note. That two repos(that use UDT and don't should reside in different packages) cause Spring config looking for base packages with repos:
#Configuration
#EnableCassandraRepositories(basePackages = {
"com.fyb.cassandra.repositories" })
public class CassandraConfig {
..........
}
User Defined data type is now supported by Spring Data Cassandra. The latest release 1.5.0.RELEASE uses Cassandra Data stax driver 3.1.3 and hence its working now. Follow the below steps to make it working
How to use UserDefinedType(UDT) feature with Spring Data Cassandra :
We need to use the latest jar of Spring data Cassandra (1.5.0.RELEASE)
group: 'org.springframework.data', name: 'spring-data-cassandra', version: '1.5.0.RELEASE'
Make sure it uses below versions of the jar :
datastax.cassandra.driver.version=3.1.3
spring.data.cassandra.version=1.5.0.RELEASE
spring.data.commons.version=1.13.0.RELEASE
spring.cql.version=1.5.0.RELEASE
Create user defined type in Cassandra : The type name should be same as defined in the POJO class
Address data type
CREATE TYPE address_type (
id text,
address_type text,
first_name text,
phone text
);
Create column-family with one of the columns as UDT in Cassandra:
Employee table:
CREATE TABLE employee(
employee_id uuid,
employee_name text,
address frozen,
primary key (employee_id, employee_name)
);
In the domain class, define the field with annotation -CassandraType and DataType should be UDT:
#Table("employee") public class Employee {
-- othere fields--
#CassandraType(type = DataType.Name.UDT, userTypeName = "address_type")
private Address address;
}
Create domain class for the user defined type : We need to make sure that column name in the user defined type schema
has to be same as field name in the domain class.
#UserDefinedType("address_type") public class Address { #CassandraType(type = DataType.Name.TEXT)
private String id; #CassandraType(type = DataType.Name.TEXT) private String address_type; }
In the Cassandra Config, Change this :
#Bean public CassandraMappingContext mappingContext() throws Exception {
BasicCassandraMappingContext mappingContext = new BasicCassandraMappingContext();
mappingContext.setUserTypeResolver(new SimpleUserTypeResolver(cluster().getObject(), cassandraKeyspace));
return mappingContext;
}
User defined type should have the same name across everywhere. for e.g
#UserDefinedType("address_type")
#CassandraType(type = DataType.Name.UDT, userTypeName = "address_type")
CREATE TYPE address_type
The theme of my project is to give XML format of data and get Json format using google-gson and I have JAXB generated java POJOs from XML schema in which I have a variable of XMLGregorianCalendar datatype.
I give the following input of XML and get the json format from the gson.toJson() method;
<?xml version="1.0" encoding="UTF-8"?>
<EmpRequest xmlns="http://java.com/Employee">
<EmplIn>
<EmpID>12</EmpID>
<Empname>sara</Empname>
<Designation>SA</Designation>
<DOJ>2002-05-30T09:30:10+06:00</DOJ>
</EmplIn>
</EmpRequest>
But in the output, I got the following.
{"emplIn":{"empID":"12","empname":"sara","designation":"SA","doj":{}}}
I surfed google and got the suggestion of adding in the xml schema and changing the XmlGregorianCalendar datatype with string. But I dont want to achieve it from both the ways.
I mean how to get the proper output with the XmlGregorianCalendar datatype through fromJson and toJson methods of gson?
Thank you so much,
Harish Raj.
Hopefully, This can fix my issue of using google-gson.
(The following should be added in where we create the object of Gson)
Step 1:
Gson gson =
new GsonBuilder().registerTypeAdapter(XMLGregorianCalendar.class,
new XGCalConverter.Serializer()).registerTypeAdapter(XMLGregorianCalendar.class,
new XGCalConverter.Deserializer()).create();
Step 2: And we need to create the XGCalConverter Class as like the following.
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.lang.reflect.Type;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
public class XGCalConverter
{
public static class Serializer implements JsonSerializer
{
public Serializer()
{
super();
}
public JsonElement serialize(Object t, Type type,
JsonSerializationContext jsonSerializationContext)
{
XMLGregorianCalendar xgcal=(XMLGregorianCalendar)t;
return new JsonPrimitive(xgcal.toXMLFormat());
}
}
public static class Deserializer implements JsonDeserializer
{
public Object deserialize(JsonElement jsonElement, Type type,
JsonDeserializationContext jsonDeserializationContext)
{
try
{
return DatatypeFactory.newInstance().newXMLGregorianCalendar(jsonElement.getAsString());
}
catch(Exception ex)
{
ex.printStackTrace();
return null;
}
}
}
}
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.
You could use MOXy to handle both the XML and JSON binding aspects of this use case. As I mentioned in my comment MOXy supports the XMLGregorianCalendar type. The Metadata would look like:
EmpRequest
package forum7725188;
import javax.xml.bind.annotation.*;
#XmlRootElement(name="EmpRequest")
#XmlAccessorType(XmlAccessType.FIELD)
public class EmpRequest {
#XmlElement(name="EmplIn")
private EmplIn emplIn;
}
EmplIn
package forum7725188;
import javax.xml.bind.annotation.*;
import javax.xml.datatype.XMLGregorianCalendar;
#XmlAccessorType(XmlAccessType.FIELD)
public class EmplIn {
#XmlElement(name="EmpID")
private long empId;
#XmlElement(name="Empname")
private String name;
#XmlElement(name="Designation")
private String designation;
#XmlElement(name="DOJ")
private XMLGregorianCalendar doj;
}
package-info
#XmlSchema(namespace="http://java.com/Employee", elementFormDefault=XmlNsForm.QUALIFIED)
#XmlAccessorType(XmlAccessType.FIELD)
package forum7725188;
import javax.xml.bind.annotation.*;
Demo
You can configure the MOXy implementation of Marshaller to output JSON by setting the eclipselink.media-type property to be application/json.
package forum7725188;
import java.io.File;
import javax.xml.bind.*;
import javax.xml.namespace.QName;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(EmpRequest.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum7725188/input.xml");
EmpRequest empRequest = (EmpRequest) unmarshaller.unmarshal(xml);
JAXBElement<EmpRequest> jaxbElement = new JAXBElement<EmpRequest>(new QName(""), EmpRequest.class, empRequest);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty("eclipselink.media-type", "application/json");
marshaller.marshal(jaxbElement, System.out);
}
}
input.xml
<?xml version="1.0" encoding="UTF-8"?>
<EmpRequest xmlns="http://java.com/Employee">
<EmplIn>
<EmpID>12</EmpID>
<Empname>sara</Empname>
<Designation>SA</Designation>
<DOJ>2002-05-30T09:30:10+06:00</DOJ>
</EmplIn>
</EmpRequest>
Output
{"EmplIn" :
{"EmpID" : "12",
"Empname" : "sara",
"Designation" : "SA",
"DOJ" : "2002-05-30T09:30:10+06:00"}}
For More Information
http://blog.bdoughan.com/2011/08/binding-to-json-xml-geocode-example.html
http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html
http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html
http://blog.bdoughan.com/2011/09/mapping-objects-to-multiple-xml-schemas.html
I am using jersey to expose a service which uses jaxb annotated classes to configure the look of the json.
I am trying to include the type directive in each json element. I do this by providing a Provider as such:
import org.codehaus.jackson.JsonParser.Feature;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ObjectMapper.DefaultTyping;
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class CmsContextResolver implements ContextResolver<ObjectMapper> {
ObjectMapper mapper;
public CmsContextResolver() {
mapper = new ObjectMapper();
// #JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include =
// JsonTypeInfo.As.WRAPPER_OBJECT, property = "#type")
mapper.configure(Feature.INTERN_FIELD_NAMES, true);
mapper.enableDefaultTypingAsProperty(DefaultTyping.NON_FINAL, "#type");
}
#Override
public ObjectMapper getContext(Class<?> arg0) {
return mapper;
}
}
And this provider is definitely being picked up.
10 May 2011 3:53:18 PM com.sun.jersey.api.core.ScanningResourceConfig logClasses
INFO: Provider classes found:
class com.afrozaar.cms.service.CmsContextResolver
But it is making no difference. The format of the json is unaffected.
As far as I can tell the problem stems from the fact that jersey is not using jackson to serialize? or that jersey is ignoring my jackson configuration overrides...
I don't know why your code isn't working, but this is what I use:
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;
import org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider;
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class JsonProvider extends JacksonJaxbJsonProvider {
public JsonProvider() {
super();
setMapper( myConfiguredObjectMapper );
}
I'm upgrading a Java object that currently has XML representation in this spirit:
<myObjects>
<myObject uid="42" type="someEnum">
<name>Waldo</name>
<description>yada yada</description>
<myElement>some_string</myElement>
...
</myObject>
...
</myObjects>
myElement is optional - it can be null in Java / omitted in XML.
I'm adding a field that is only relevant if myElement has an actual value
(and to keep compatibility with previous XML, it's optional in itself)
I'm trying to avoid this:
<myElement>some_string</myElement>
<myAttr>foo</myAttr>
and prefer something like this:
<myElement myAttr="foo">some_string</myElement>
but banging my head for 2 days now on how to annotate it.
I thought of marking it with XmlTransient and let an XmlAnyElement catch it instead while unmarshalling - but it seems this will cause a problem when marshalling the object back from Java to XML.
I tried creating an XmlAdapter for the element - but the unmarshal method gets only the inner content ("some_string"). Am I missing something with this technique?
Is there a way to just get the element as a string ("<myElement myAttr=\"foo\">some_string</myElement>") and I will process it myself?
Do you recommend any other approach?
You can use the EclipseLink JAXB (MOXy) #XmlPath extension to easily accomplish this:
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 org.eclipse.persistence.oxm.annotations.XmlPath;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class MyObject {
#XmlAttribute
private int uid;
#XmlAttribute
private String type;
private String name;
private String description;
private String myElement;
#XmlPath("myElement/#myAttr")
private String myAttr;
}
This class will interact with the following XML:
<myObject uid="42" type="someEnum">
<name>Waldo</name>
<description>yada yada</description>
<myElement myAttr="foo">some_string</myElement>
</myObject>
Using the following demo code:
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(MyObject.class);
File file = new File("input.xml");
Unmarshaller unmarshaller = jc.createUnmarshaller();
MyObject myObject = (MyObject) unmarshaller.unmarshal(file);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(myObject, System.out);
}
}
To specify MOXy as your JAXB implementation you need to include a file called jaxb.properties in the same package as your model classes with the following entry:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
For more information on MOXy's XPath based mapping see:
http://bdoughan.blogspot.com/2010/09/xpath-based-mapping-geocode-example.html
http://bdoughan.blogspot.com/2010/07/xpath-based-mapping.html
The answer was dead simple: I'm so used to annotate with XmlElement and XmlAttribute, that I forgot about XmlValue!
The code for MyObject is the same as in Blaise Doughan answer, with one change:
myElement is now a class (ElemWithAttr) instead of String:
public class ElemWithAttr {
#XmlValue
public String content;
#XmlAttribute
public String myAttr;
}