My team has been running into a recurring problem while serializing objects to XML: the class properties get updated, but the #Xml.propOrder annotation values do not (people forget), which causes the following error:
Property baz appears in #XmlType.propOrder, but no such property exists.
How can I automate a unit test to check all classes that declare #Xml.propOrder for undeclared fields or typos in the annotation value?
E.g:
#XmlRootElement
#XmlType(name = "FooBar", propOrder = { "bar", "baz", "foo" })
public class FooBar {
private String foo;
private int bar;
// getters and setters here...
}
IntelliJ sometimes can pick up the error during linting, but some team members use Eclipse, so code gets comitted, maven does not spew any warnings, and wrong code goes to test/QA. Also, our team cannot also change the build script, it's controlled and standardized company-wide.
I cobbled together a test that uses Guava to load information about all classes under a package name, load them, check if they have the #XmlType annotation, and if so, compares the values of the propOrder element with the class properties obtained through reflection.
I'm not totally happy with it, I wanted to use the same mechanism that Java uses to validate the annotations. But hey, it works.
package com.mycompany.datamodel;
import com.google.common.reflect.ClassPath;
import com.google.common.reflect.ClassPath.ClassInfo;
import org.apache.commons.lang.StringUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import javax.xml.bind.annotation.XmlType;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;
import static org.junit.Assert.assertTrue;
#RunWith(Parameterized.class)
public class PropOrderTest {
#Parameter
public Class clazz;
#Parameters(name = "Test #XmlType.propOrder: {0}")
public static Collection<Class> data() throws IOException {
ClassPath classPath = ClassPath.from(Thread.currentThread().getContextClassLoader());
String packageName = PropOrderTest.class.getPackage().getName();
Set<ClassInfo> allClasses = classPath.getTopLevelClassesRecursive(packageName);
List<Class> annotatedClasses = new ArrayList<Class>();
for (ClassInfo info : allClasses) {
Class clazz = info.load();
if (clazz.isAnnotationPresent(XmlType.class)) {
annotatedClasses.add(clazz);
}
}
return annotatedClasses;
}
#Test
public void testPropOder() throws IOException {
XmlType xmlType = (XmlType) clazz.getAnnotation(XmlType.class);
Set<String> propOrder = new HashSet<String>(Arrays.asList(xmlType.propOrder()));
// remove empty string returned when propOrder is not declared
propOrder.remove("");
List<String> fieldNames = getFieldNames();
propOrder.removeAll(fieldNames);
assertTrue(formatMessage(propOrder), propOrder.isEmpty());
}
private List<String> getFieldNames() {
Field[] fields = clazz.getDeclaredFields();
List<String> names = new ArrayList<String>(fields.length);
for (Field field : fields) {
names.add(field.getName());
}
return names;
}
private String formatMessage(Collection<String> propOrder) {
String message = null;
String props = "'" + StringUtils.join(propOrder.toArray(), "', '") + "'";
if (propOrder.size() > 1) {
message = "Properties %s appear in #XmlType.propOrder, but no such properties exist in class %s (%s.java:1).";
} else {
message = "Property %s appears in #XmlType.propOrder, but no such property exists in class %s (%s.java:1).";
}
return String.format(message, props, clazz.getName(), clazz.getSimpleName());
}
}
Related
I'm using a subType property in Jackson, and I want to using this property when deserializing json.
package com.gaosoft.ai.kg.commons.sphinx.strategy;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.sankuai.ai.kg.commons.sphinx.model.FAQRecord;
import com.sankuai.ai.kg.commons.sphinx.model.FAQRequest;
import org.springframework.beans.factory.BeanFactory;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "strategyType"
)
#JsonSubTypes({
#JsonSubTypes.Type(value = StrategyEmpty.class, name = "empty"),
#JsonSubTypes.Type(value = StrategyNormal.class, name = "normal"),
#JsonSubTypes.Type(value = StrategyDummy.class, name = "dummy")
}
)
public abstract class Strategy implements Serializable {
private String strategyName;
private String strategyType;
private Map<String, Object> args = new HashMap<>();
public String getStrategyType() {
return strategyType;
}
public void setStrategyType(String strategyType) {
this.strategyType = strategyType;
}
public Map<String, Object> getArgs() {
return args;
}
public void setArgs(Map<String, Object> args) {
this.args = args;
}
public String getStrategyName() {
return strategyName;
}
public void setStrategyName(String strategyName) {
this.strategyName = strategyName;
}
public abstract void init(BeanFactory beanFactory);
public abstract List<FAQRecord> fetchFAQ(FAQRequest request);
}
Like my code says, there are 3 subtype of abstract class Strategy, and I want to retain the subclass type name in strategyType property.
Is there a way to fill strategyType when using jackson in this way?
(Sorry about my poor English)
I think what you're asking for is the #JsonTypeInfo#visible property:
Note on visibility of type identifier: by default, deserialization (use during reading of JSON) of type identifier is completely handled by Jackson, and is not passed to deserializers. However, if so desired, it is possible to define property visible = true in which case property will be passed as-is to deserializers (and set via setter or field) on deserialization.
So in your example,
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "strategyType",
visible = true
)
That said, this seems like a design smell. Is it truly valid that you can set a StrategyEmpty's strategyType to dummy? If not, and StrategyEmpty should always have a strategyType of empty, then why not just have an abstract getStrategyType() that each subclass implements with a hardcoded value?
I would like a Java class containing JavaFX Property objects (DoubleProperty, StringProperty, IntegerProperty) to be written into an XML file using JAXB's marshall() method call. However, this class contains lots of data that I do not want written into the XML. This class is expected to be modified by developers often, so I prefer to mark the class "#XmlAccessorType(XmlAccessType.NONE)" and then add #XmlElement tags to anything I want written into the XML (so a developer doesn't add some new member variables into this class and then accidentally alter the XML file's format).
I see examples such as http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-using-xsitype.html, but none of them have "#XmlAccessorType(XmlAccessType.NONE) " for their main class. When I add this tag to my class, I get either runtime Exceptions (because JAXB cannot create a new object) or an empty XML tag in the output (because JAXB created a default object of ome kind but didn't put the desired value into it). I have experimentes with dozens of #Xml* tag combinations but I cannot find one that works.
Note that I cannot annotate any of the DoubleProperty/SimpleDoubleProperty classes because they are part of the standard Java API.
Here is a code example, demonstrating the troubles with getting the bankAccountBalance property into the XML file. (you can ignore the other data - I started with Blaise Doughan's code as a starting-point for this example).
package Demo2;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlRootElement;
public class Demo2 {
public static void main(String[] args) throws Exception {
Customer customer = new Customer();
Address address = new Address();
address.setStreet("1 A Street");
customer.setContactInfo(address);
customer.setBankAccountBalance(123.45);
JAXBContext jc = JAXBContext.newInstance(Customer.class, Address.class, PhoneNumber.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(customer, System.out);
}
}
abstract class ContactInfo {
}
class Address extends ContactInfo {
private String street;
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
}
class PhoneNumber extends ContactInfo {
}
#XmlRootElement
#XmlAccessorType(XmlAccessType.NONE)
class Customer {
#XmlElement
private ContactInfo contactInfo;
// This tag runs OK but always outputs an empty XML tag ("<bankAccountBalance/>") regardless of the real value.
// #XmlElement
// This tag causes the following error:
// Exception in thread "main" com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
// Invalid #XmlElementRef : Type "class javafx.beans.property.DoubleProperty" or any of its subclasses are not known to this context.
// #XmlElementRef
// This tag causes the following error:
// Exception in thread "main" com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
// Invalid #XmlElementRef : Type "class javafx.beans.property.SimpleDoubleProperty" or any of its subclasses are not known to this context.
// #XmlElementRef(type=SimpleDoubleProperty.class)
private DoubleProperty bankAccountBalance;
public Customer() {
bankAccountBalance = new SimpleDoubleProperty(0);
}
public ContactInfo getContactInfo() {
return contactInfo;
}
public void setContactInfo(ContactInfo contactInfo) {
this.contactInfo = contactInfo;
}
public double getBankAccountBalance() {
return bankAccountBalance.get();
}
public void setBankAccountBalance(double bankAccountBalance) {
this.bankAccountBalance.set(bankAccountBalance);
}
}
Simply annotate the getter and not the DoubleProperty field, which nicely bypasses the javafx class. Don't forget to setValue so you see the result.
#XmlElement
public double getBankAccountBalance() {
return bankAccountBalance.get();
}
I am digging on Jackson 2 and I want to know where and how the getter-method name gets converted into a property name.
I have tried:
PropertyName foo = new PropertyName("getKarli");
System.out.println(foo.getSimpleName());
I and I have found BeanProperty.Std() but this one have a lot of wired constructors. The api is bigger then expected :-) Is there a Jackson class and method where I can just pass the method and get back the correct property text used in the json?
EDIT:
I have also tried this one but that gives me a NullPointer
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyName;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class Test {
public String getKarli() {
return null;
}
public static void main(String[] a) throws Exception {
node.remove("geheim");
System.out.println(node.toString());
Annotated aa = new AnnotatedMethod(Test.class.getMethod("getKarli"), null, null);
System.out.println(
new ObjectMapper().getSerializationConfig().getAnnotationIntrospector().findNameForSerialization(aa)
);
// new BeanProperty.Std()
}
}
Found it.
String name = BeanUtil.okNameForRegularGetter(p, p.getName(), true);
if(name == null) name = BeanUtil.okNameForIsGetter(p, p.getName(), true);
I need to make a binding of my object FileDocument, which contains a reference to another object, Metadata. Metadata is -- I hope-- can have a dynamic name depending on a value on its attribute.
I have heard and used XmlAdapter (also for the Metadata class), but only for the Map case. I don't really understand how to make it work for this case.
Snippet for FileDocument:
#XmlAccessorType(XmlAccessType.FIELD)
public class FileDocument{
//...
protected List<Metadata> metadata;
//...
}
Snippet for Metadata:
#XmlType(name = "metadata")
//#XmlRootElement(name = "metaCollection")
public class Metadata {
//...
#XmlPath(".")
#XmlJavaTypeAdapter(MetaAdapter.class)
Map<String, String> map;
//I'd like to have each element of metadata depend on this attribute.
String source;
}
My desired output is something like
{
"someKeyInFileDocument" : "someValueInFileDocument",
"metadata.source1" : {
"some key inside this metadata" : "some value inside this metadata",
"more!": "more!"
},
"metadata.source2" : {
"yes, the above key" : "looks similar but also different as the above",
"this is another key!" : "inside this source2 thing"
}
}
You can use EclipseLink JAXB (MOXy)'s #XmlVariableNode extension for this use case:
Java Model
FileDocument
We will use the #XmlVariableNode annotation on the metadata field. This tells MOXy that instead of using a fixed name for the element/key that the name should be taken from the specified field/property on the referenced object.
import java.util.List;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.XmlVariableNode;
#XmlAccessorType(XmlAccessType.FIELD)
public class FileDocument {
#XmlVariableNode("source")
protected List<Metadata> metadata;
}
Metadata
We will use the #XmlTransient annotation on the source field to prevent it from being marshalled (see: http://blog.bdoughan.com/2012/04/jaxb-and-unmapped-properties.html).
import javax.xml.bind.annotation.*;
#XmlAccessorType(XmlAccessType.FIELD)
public class Metadata {
#XmlTransient
String source;
}
Demo Code
You can run the demo code below to see that everything works.
Demo
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
public class Demo {
public static void main(String[] args) throws Exception {
Map<String, Object> properties = new HashMap<String, Object>(2);
properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json");
properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
JAXBContext jc = JAXBContext.newInstance(new Class[] {FileDocument.class}, properties);
Metadata m1 = new Metadata();
m1.source = "metadata.source1";
Metadata m2 = new Metadata();
m2.source = "metadata.source2";
List<Metadata> metadata = new ArrayList<Metadata>();
metadata.add(m1);
metadata.add(m2);
FileDocument fd = new FileDocument();
fd.metadata = metadata;
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(fd, System.out);
}
}
Output
{
"metadata.source1" : {
},
"metadata.source2" : {
}
}
For More Information
You can read more about the #XmlVariableNode extension on my blog:
http://blog.bdoughan.com/2013/06/moxys-xmlvariablenode-json-schema.html
public static UserDetail UserDetail.findUserDetail(Long id) {
if (id == null) return null;
return entityManager().find(UserDetail.class, id);
}
We are using spring Roo. Above is Roo generated finder method. Partial stack trace is as follows:
Caused by: org.hibernate.WrongClassException: Object with id: 1501237 was not of the specified subclass: com.***.***.user.UserDetail (Discriminator: FacebookUserDetail)
Has anyone come across this exception?
EDIT
This question and following questions are related to same issue.
Java class file truncated
I have two projects. My one project (say project2) depends on another project(project2). Both projects are maven project and project1 is listed in dependancies of project2. When I compile project2, all the class files from project1 should be copied to project2 (I imagine). But, I see that the file size of one of the class files in project1 is different than file size of class file for the same class in project2. If I decompile the files I get following results.
Decompiled FacebookUserDetail.class from project1:
package com.***.domain.user.external;
import com.***.domain.user.UserDetailType;
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.TypedQuery;
import org.aspectj.lang.JoinPoint;
import org.aspectj.runtime.internal.CFlowCounter;
import org.aspectj.runtime.reflect.Factory;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.beans.factory.aspectj.AbstractDependencyInjectionAspect;
import org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect;
import org.springframework.mock.staticmock.AnnotationDrivenStaticEntityMockingControl;
#Configurable
#Entity
public class FacebookUserDetail extends ExternalUserDetail
{
public FacebookUserDetail()
{
JoinPoint localJoinPoint = Factory.makeJP(ajc$tjp_1, this, this); if ((!AnnotationBeanConfigurerAspect.ajc$if$bb0((Configurable)getClass().getAnnotation(Configurable.class))) && (AbstractDependencyInjectionAspect.ajc$if$6f1(localJoinPoint))) AnnotationBeanConfigurerAspect.aspectOf().ajc$afterReturning$org_springframework_beans_factory_aspectj_AbstractDependencyInjectionAspect$2$1ea6722c(this);
}
public static FacebookUserDetail findFacebookUserDetailByFacebookId(String facebookId)
{
String str = facebookId; JoinPoint localJoinPoint = Factory.makeJP(ajc$tjp_0, null, null, str); if ((AnnotationDrivenStaticEntityMockingControl.ajc$cflowCounter$1.isValid()) && (AnnotationDrivenStaticEntityMockingControl.hasAspect())) return (FacebookUserDetail)findFacebookUserDetailByFacebookId_aroundBody1$advice(str, localJoinPoint, AnnotationDrivenStaticEntityMockingControl.aspectOf(), null, ajc$tjp_0, localJoinPoint); return findFacebookUserDetailByFacebookId_aroundBody0(str, localJoinPoint);
}
public UserDetailType getExternalUserDetailType()
{
return UserDetailType.FACEBOOK;
}
static
{
ajc$preClinit(); }
public static long countFacebookUserDetails() { return FacebookUserDetail_Roo_Entity.ajc$interMethod$com_nim_domain_user_external_FacebookUserDetail_Roo_Entity$com_nim_domain_user_external_FacebookUserDetail$countFacebookUserDetails(); }
public static List<FacebookUserDetail> findAllFacebookUserDetails() { return FacebookUserDetail_Roo_Entity.ajc$interMethod$com_nim_domain_user_external_FacebookUserDetail_Roo_Entity$com_nim_domain_user_external_FacebookUserDetail$findAllFacebookUserDetails(); }
public static FacebookUserDetail findFacebookUserDetail(Long paramLong) { return FacebookUserDetail_Roo_Entity.ajc$interMethod$com_nim_domain_user_external_FacebookUserDetail_Roo_Entity$com_nim_domain_user_external_FacebookUserDetail$findFacebookUserDetail(paramLong); }
public static List<FacebookUserDetail> findFacebookUserDetailEntries(int paramInt1, int paramInt2) { return FacebookUserDetail_Roo_Entity.ajc$interMethod$com_nim_domain_user_external_FacebookUserDetail_Roo_Entity$com_nim_domain_user_external_FacebookUserDetail$findFacebookUserDetailEntries(paramInt1, paramInt2); }
public static TypedQuery<FacebookUserDetail> findFacebookUserDetailsByUserIdEquals(String paramString) { return FacebookUserDetail_Roo_Finder.ajc$interMethod$com_nim_domain_user_external_FacebookUserDetail_Roo_Finder$com_nim_domain_user_external_FacebookUserDetail$findFacebookUserDetailsByUserIdEquals(paramString); }
public String toString() { return FacebookUserDetail_Roo_ToString.ajc$interMethod$com_nim_domain_user_external_FacebookUserDetail_Roo_ToString$com_nim_domain_user_external_FacebookUserDetail$toString(this); }
}
Decompiled FacebookUserDetail.class from project2
package com.***.domain.user.external;
import com.***.domain.user.UserDetailType;
import org.aspectj.lang.JoinPoint;
import org.aspectj.runtime.internal.CFlowCounter;
import org.aspectj.runtime.reflect.Factory;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.beans.factory.aspectj.AbstractDependencyInjectionAspect;
import org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect;
import org.springframework.mock.staticmock.AnnotationDrivenStaticEntityMockingControl;
public class FacebookUserDetail extends ExternalUserDetail
{
public FacebookUserDetail()
{
JoinPoint localJoinPoint = Factory.makeJP(ajc$tjp_1, this, this); if ((!AnnotationBeanConfigurerAspect.ajc$if$bb0((Configurable)getClass().getAnnotation(Configurable.class))) && (AbstractDependencyInjectionAspect.ajc$if$6f1(localJoinPoint))) AnnotationBeanConfigurerAspect.aspectOf().ajc$afterReturning$org_springframework_beans_factory_aspectj_AbstractDependencyInjectionAspect$2$1ea6722c(this);
}
public static FacebookUserDetail findFacebookUserDetailByFacebookId(String facebookId)
{
String str = facebookId; JoinPoint localJoinPoint = Factory.makeJP(ajc$tjp_0, null, null, str); if ((AnnotationDrivenStaticEntityMockingControl.ajc$cflowCounter$1.isValid()) && (AnnotationDrivenStaticEntityMockingControl.hasAspect())) return (FacebookUserDetail)findFacebookUserDetailByFacebookId_aroundBody1$advice(str, localJoinPoint, AnnotationDrivenStaticEntityMockingControl.aspectOf(), null, ajc$tjp_0, localJoinPoint); return findFacebookUserDetailByFacebookId_aroundBody0(str, localJoinPoint);
}
public UserDetailType getExternalUserDetailType()
{
return UserDetailType.FACEBOOK;
}
static
{
ajc$preClinit();
}
}
My question is: What are possible reasons for truncated class file in project2?
As far as I understand from the error you have the following scenario:
you request an entity of type UserDetail with that ID (which should have the DTYPE/discriminator column value equal to FacebookUserDetail or other that extend UserDetail), but in your DB the DTYPE is another. You have to correct your DB for that.
Or it could also be, that FacebookUserDetail is not recognized as being a DTYPE of the same hierarchy. Try debugging a bit, e.g testing what is returned if you search for a FacebookUserDetail instance of the same ID.
It looks like your super class and subclasse didn't share the same id in the database for the requested record 1501237
It is obvious you have an inheritance problem, take a look at http://en.wikibooks.org/wiki/Java_Persistence/Inheritance