2
votes

I am having some trouble with custom enum marshaling with Moxy and JSON. My use case is that I have a large object model that includes enumerations that normally should provide a normal enumerated value, a "code", and a description. The source of this data has only the "code", so I need to be able to unmarshal instances of these enums using only the code (e.g.

{"companyCode":{"code":"PI"}}.

However, I should also be able to marshal and unmarshal all three fields:

{"companyCode":
  {"value":"Private",
  "code":"PI","description":
  "Private Ins"
  }
}


I am using an adapter that looks like this:

public class CodeEnumXmlAdapter<E extends Enum<E> & CodeEnum> extends XmlAdapter<CodeEnumImpl,E> {

    public static <T extends Enum<T> & CodeEnum> T getFromName(Class<T> clazz, String name) {
        if (name == null) return null;

        T[] values = clazz.getEnumConstants();

        for (T t : values) {
            if (name.equals(t.name())) {
                return t;
            }
        }

        return null;
    }
    public static <T extends Enum<T> & CodeEnum> T getFromCode(Class<T> clazz, String code) {
        if (code == null) return null;

        T[] values = clazz.getEnumConstants();

        for (T t : values) {
            if (code.equals(t.getCode())) {
                return t;
            }
        }

        return null;
    }
    public static <T extends Enum<T> & CodeEnum> T getFromString(Class<T> clazz, String aString) {
        if (aString == null) return null;

        T[] values = clazz.getEnumConstants();

        for (T t : values) {
            if (aString.equals(t.getCode()) || aString.equals(t.name()) || aString.equals(t.getDescription())) {
                return t;
            }
        }

        return null;
    }

    @Override
    public E unmarshal(CodeEnumImpl value) throws Exception {
        if (value == null) return null;

        String valueString = value.getValue();
        if (valueString == null)
            valueString = value.getCode();
        if (valueString == null)
            valueString = value.getDescription();
        if (valueString == null)
            return null;

        Type generic = ((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        return getFromString((Class<E>)generic, valueString);
    }

    @Override
    public CodeEnumImpl marshal(E value) throws Exception {
        return value == null ? null : new CodeEnumImpl(value);
    }
}

This converts from a an enum like this:

import org.apache.commons.lang3.StringUtils;

import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlJavaTypeAdapter(CompanyCode.Adapter.class)
public enum CompanyCode implements CodeEnum {

    // Changed "Commmercial" to "Client" based on inputs from ...Greg, Tamil
    Client("CM", "Client"), Medicare("MC", "Medicare"), Medicaid("MD",
            "Medicaid"), Private("PI", "Private Ins"), Patient("PT", "Patient");

    private String code;
    private String description;

    private CompanyCode(String code, String label) {
        this.code = code;
        this.description = label;
    }

    public String getDescription() {
        return description;
    }

    public String getCode() {
        return code;
    }

    public static CompanyCode fromCode(String code) {
        if (StringUtils.isEmpty(code)) {
            return null;
        }

        for (CompanyCode freq : values()) {
            if (freq.getCode().equalsIgnoreCase(code)) {
                return freq;
            }
        }
        throw new IllegalArgumentException("Invalid CompanyCode code: " + code);
    }

    public String toString() {
        return description;
    }

    public static class Adapter extends CodeEnumXmlAdapter<CompanyCode> {}
}

and uses and intermediate type like this:

import javax.xml.bind.annotation.XmlElement;

/**
 * Created by Jeffrey Hoffman on 6/24/2015.
 */
public class CodeEnumImpl  {
    String value;
    String description;
    String code;

    public CodeEnumImpl() {

    }
    public <E extends Enum<E> & CodeEnum> CodeEnumImpl(E value) {
        if (value != null) {
            this.value = value.name();
            this.description = value.getDescription();
            this.code = value.getCode();
        }
    }

    @XmlElement
    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    @XmlElement
    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    @XmlElement
    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    @Override
    public String toString() {
        return value == null ? null : value.toString();
    }
}

This is working fine with straight XML and JAXB. However, when I try to use Moxy, I get an exception like this:

Exception Description: The object [Private Ins], of class [class com.labcorp.phoenix.biz.enums.CompanyCode], could not be converted to [class java.lang.Object]. Internal Exception: Exception [EclipseLink-115] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DescriptorException Exception Description: No conversion value provided for the attribute [Private]. Mapping: org.eclipse.persistence.oxm.mappings.XMLDirectMapping[companyCode-->companyCode/text()] Descriptor: XMLDescriptor(com.labcorp.phoenix.eligibility.Root --> [DatabaseTable(root)]) at org.eclipse.persistence.exceptions.ConversionException.couldNotBeConverted(ConversionException.java:87) at org.eclipse.persistence.internal.jaxb.XMLJavaTypeConverter.convertObjectValueToDataValue(XMLJavaTypeConverter.java:178) at org.eclipse.persistence.oxm.mappings.XMLDirectMapping.convertObjectValueToDataValue(XMLDirectMapping.java:511) at org.eclipse.persistence.oxm.mappings.XMLDirectMapping.getFieldValue(XMLDirectMapping.java:330) at org.eclipse.persistence.internal.oxm.XMLDirectMappingNodeValue.marshalSingleValue(XMLDirectMappingNodeValue.java:62) at org.eclipse.persistence.internal.oxm.XMLDirectMappingNodeValue.marshal(XMLDirectMappingNodeValue.java:58) at org.eclipse.persistence.internal.oxm.NodeValue.marshal(NodeValue.java:102) at org.eclipse.persistence.internal.oxm.record.ObjectMarshalContext.marshal(ObjectMarshalContext.java:59) at org.eclipse.persistence.internal.oxm.XPathNode.marshal(XPathNode.java:393) at org.eclipse.persistence.internal.oxm.XPathNode.marshal(XPathNode.java:368) at org.eclipse.persistence.internal.oxm.XPathObjectBuilder.buildRow(XPathObjectBuilder.java:238) at org.eclipse.persistence.internal.oxm.TreeObjectBuilder.buildRow(TreeObjectBuilder.java:118) at org.eclipse.persistence.internal.oxm.TreeObjectBuilder.buildRow(TreeObjectBuilder.java:1) at org.eclipse.persistence.internal.oxm.XMLMarshaller.marshal(XMLMarshaller.java:743) at org.eclipse.persistence.internal.oxm.XMLMarshaller.marshal(XMLMarshaller.java:1124) at org.eclipse.persistence.internal.oxm.XMLMarshaller.marshal(XMLMarshaller.java:869) ... 7 more Caused by: Exception [EclipseLink-115] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DescriptorException

It seems like a bug in moxy, because my adapter converts to a non-enum type, so there should not be a nestedConverter that deals with enums.

1

1 Answers

2
votes

I managed to reproduce your issue with 2.5.0. It's most probably bug which has been fixed already. Unable to find the bug in Eclipse Bugzilla, but the same code works correctly with 2.6.0. Are you able to upgrade to latest MOXy?