Java Ninja Chronicles By Norris Shelton

Things I learned in the pursuit of code

I have a list in an object. I need to output this list in XML. Normally this would be accomplished by the following:

@XmlElementWrapper(name = "licenseList")
@XmlElement(name = "license")
public List<WatchLicense> getLicenses() {
    return licenses;
}

This produces the following output

<licenseList>
    <license>data</license>
    <license>data</license>
    <license>data</license>
</licenseList>

This is great, until I have the need to add an attribute to licenseList. You can’t add an attribute to a generated wrapper via annotations only. Enter the xml type adapter. There are three pieces. The defined type that is marshallable (LicenseListType), the annotation of the unmarshallable element with the xml type adapter and then the adapter itself (LicenseListAdapter).

First off is the new type. There are several things here. First off, it is a normal wrapper of the type that used to have to be used before @XmlElementWrapper. You used to have to add this to your class directly as a property. I didn’t want to because the names of the elements and their location had meaning to my views. Second is the addition of the getHashCode() method. This is the attribute that I needed to add. It uses the generated hashCode method.

package com.appriss.jxp.watchapi.jaxb;

import com.appriss.jxp.watchapi.WatchLicense;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.springframework.util.AutoPopulatingList;

import javax.xml.bind.annotation.XmlAttribute;

import java.util.List;

/**
 * Wrapper for list of license.  Normally, this would be handled by adding @XmlElementWrapper(name = "licenseList") to
 * the getter.  In this case, we also need to add a hashCode attribute the value and JAXB doesn't support an element
 * wrapper with an attribute.
 * @author nshelton
 */
public class LicenseListType {

    @XmlAttribute(name = "hashCode")
    public int getHashCode() {
        return hashCode();
    }

    private List<WatchLicense> license = new AutoPopulatingList<WatchLicense>(WatchLicense.class);

    public List<WatchLicense> getLicense() {
        return license;
    }

    public void setLicense(List<WatchLicense> license) {
        this.license = license;
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder().append(license).toHashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof LicenseListType) {
            final LicenseListType other = (LicenseListType) obj;
            return new EqualsBuilder().append(license, other.license).isEquals();
        } else {
            return false;
        }
    }
}

Next up is the adapter class which will directly use this new type. The adapter’s job is to translate from one unmarshallable object to a marshallable object.

package com.appriss.jxp.watchapi.jaxb;

import com.appriss.jxp.watchapi.WatchLicense;

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

import java.util.LinkedList;
import java.util.List;

/**
 * License list type adapter to convert between a list of watch licenses and a license list type.  This would normally
 * be handled by @XmlElementWrapper, but we also need to specify an attribute and JAXB doesn't support attributes on an
 * annotation generated wrapper.
 * @author nshelton
 */
public class LicenseListAdapter extends XmlAdapter<LicenseListType, List<WatchLicense>> {

    /**
     * Convert a value type to a bound type.
     * @param licenseListType The value to be converted. Can be null.
     * @throws Exception if there's an error during the conversion. The caller is responsible for reporting the error to
     *                   the user through {@link javax.xml.bind.ValidationEventHandler}.
     */
    @Override
    public List<WatchLicense> unmarshal(LicenseListType licenseListType) throws Exception {
        List<WatchLicense> watchLicenses = new LinkedList<WatchLicense>();
        watchLicenses.addAll(licenseListType.getLicense());
        return watchLicenses;
    }

    /**
     * Convert a bound type to a value type.
     * @param watchLicenses The value to be converted. Can be null.
     * @throws Exception if there's an error during the conversion. The caller is responsible for reporting the error to
     *                   the user through {@link javax.xml.bind.ValidationEventHandler}.
     */
    @Override
    public LicenseListType marshal(List<WatchLicense> watchLicenses) throws Exception {
        LicenseListType licenseListType = new LicenseListType();
        licenseListType.setLicense(watchLicenses);
        return licenseListType;
    }
}

The magic happens in the marshall and unmarshall methods. In the marshall method, it accepts the unmarshallable class and returns a marshallable class. In the unmarshaller, it accepts the marshallable class and returns the unmarshallable class.

Now all that is left to do is to define the adapter class for a property. The @XmlJavaTypeAdapter annotation is used to identify the class that will be used to marshall/unmarshall this element.

@XmlJavaTypeAdapter(LicenseListAdapter.class)
public List<WatchLicense> getLicenses() {
    return licenses;
}

August 22nd, 2012

Posted In: Java, JAXB, json

Tags: , , ,

2 Comments

JAXB by default would output the value of a getter for property “oldName” as data. Specify @XmlElement if you want to use a different name.

@XmlElement(name ="newName")
public String getOldName() {
     return oldName;
}

Thanks to @XmlElement, it will be outputted as data.

This is especially useful when you have a list of objects. Normally you would have something like:

public List<String> getValues() {
     return values;
}

You would need a way to get the collection of data. This can be accomplished by adding @XmlElementWrapper.

@XmlElementWrapper(name = "valueList")
public List<String> getValues() {
     return values;
}

This would create the following output

<valueList>
     <values>value</values>
     <values>value</values>
     <values>value</values>
     <values>value</values>
</valueList>

This is correct, but I don’t like to have a plural name for a single value container. I could change the name of my property to be value, but that looks weird when I iterate over value in my code. A better solution is to specify a wrapper and an element.

@XmlElementWrapper(name = "valueList")
@XmlElement(name = "value")
public List<String> getValues() {
     return values;
}

This would create the following output

<valueList>
     <value>value</value>
     <value>value</value>
     <value>value</value>
     <value>value</value>
</valueList>

Everybody is happy. My code looks like it should and the xml looks like it should.

August 21st, 2012

Posted In: Java, JAXB, xml

Leave a Comment

It’s pretty hard to troubleshoot generated XML. JAXB offers the ability to output it’s generated XML with a formatted option. After you create the marshaller but before you marshall, tell the marshaller to output formatted xml by:

JAXBContext jaxbContext = JAXBContext.newInstance(Animal.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter stringWriter = new StringWriter();
marshaller.marshal(watchForm, stringWriter);

August 21st, 2012

Posted In: Java, JAXB, xml

Leave a Comment

It’s not uncommon to use superclasses to contain code that is used in multiple sub-classes. This prevents code duplication and allows the code to be maintained in one place instead of several. The normal way that you marshall JAXB classes will have your duplicating the marshaller. It is possible to use the superclass and only specify it once. If you have class Animal and it is extended by Cat and Dog. How would you marshall them without having to duplicate code?

JAXBContext jaxbContext = JAXBContext.newInstance(Animal.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter stringWriter = new StringWriter();
marshaller.marshal(watchForm, stringWriter);

You specify the parent class when you get the JAXB context instance. This will get you an error message saying that the particular instance (Dog or Cat) that you are trying to marshall isn’t known to this context. This is worked around by adding @XmlSeeAlso({Cat.class, Dog.class}) to Animal. This tells the JAXB context to also load their definitions.

August 21st, 2012

Posted In: Java, JAXB, xml

Leave a Comment

UPDATE: THANKS to attel in the comments below, THIS NOW WORKS. I was using the response context object, when I should have been using the request context.

I have a case where the test system is going to be different than the production system. I started with the following in my Springframework config file:

<!--
     Define a cxf endpoint based on client stub generated from a wsdl. It
     is important to provide serviceName and endpointName so the wsdl is
     not needed at runtime. As far as I know the serviceName and
     endpointName do not have to have a special convention but it is good
     practice to use the service namespace and Service Interface name in
     the names
 -->
<jaxws:client id="justiceXchangeService"
              serviceName="namespace:service name"
              endpointName="namespace:endpoint name"
              address="my test service address"
              serviceClass="some.package.ServiceClass">
    <jaxws:properties>
        <!--all validation is turned off-->
        <entry key="set-jaxb-validation-event-handler" value="false"/>
    </jaxws:properties>
</jaxws:client>

This would allow me to inject the client by:

    @SuppressWarnings("SpringJavaAutowiringInspection")
    @Autowired
    private ServiceClass serviceClass;

This worked until I had to dynamically specify the service address. I found some documentation that said I should do it like:

        BindingProvider bindingProvider = (BindingProvider) serviceClass;
        bindingProvider.getRequestContext()
                       .put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "different service address");

UPDATE: THANKS to attel in the comments below, THIS NOW WORKS. I was using the response context object, when I should have been using the request context.

Unfortunately when I checked my logs, I saw that the address in the original request was still the address declared in the spring config file. Changing the binding didn’t work.

I finally ran across using a proxy. Now my spring context looks like:



This caused some changes in my code. The client is now injected via:

    @Autowired
    private JaxWsProxyFactoryBean jaxWsProxyFactoryBean;

The dynamic service is address is specifyied and the proxy used by:

        jaxWsProxyFactoryBean.setAddress("https://avsdsmv.dfa.arkansas.gov/Appriss/JXC");
        ServiceClass serviceClass = (serviceClass) jaxWsProxyFactoryBean.create();
        serviceClass.wsCall(...);

August 15th, 2012

Posted In: CXF, Java, JAXB, SOAP, Spring

Tags: , , , , ,

4 Comments

LinkedIn Auto Publish Powered By : XYZScripts.com