Search code examples
javajpaodataolingo

Why is $expand not working correct in my JPA/Olingo project


I am currently working on a JPA/Olingo based odata service. The Olingo version used is 2.0.7. The JPA implementation used is eclipselink version 2.5.1. There are two entities connected through a OneToMany relationship (Company, Page). Requesting the company from the service (e.g. /odata/v2/Companies) without an $expand works fine. The same for requesting the pages. Requesting pages and expanding the CompanyDetails works fine as well. Somehow requesting the company and expanding the associated pages (e.g. /odata/v2/Companies?$expand=Pages) returns a zero size array for the pages allthough when calling the deferred link (e.g. /odata/v2/Companies('P')/Pages) in the company entity returns the array of pages as expected.

Here's my persistence.xml (ommiting other not yet tested entities):

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
    xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
    <persistence-unit name="s.h.backend"
        transaction-type="RESOURCE_LOCAL">
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
        <class>c.p.s.h.data.Company</class>

...

        <class>c.p.s.h.data.Page</class>

...

        <properties>
            <property name="eclipselink.ddl-generation"
value="create-tables" />
            <property name="eclipselink.logging.level" value="INFO" />
            <property name="eclipselink.jpql.parser"
value="org.eclipse.persistence.queries.ANTLRQueryBuilder" />
        </properties>
    </persistence-unit>
</persistence>

My Company class looks like this:

@Entity
@Table(name = "HUM_COMPANY")
public class Company {
    private static final Logger log =
LoggerFactory.getLogger(Company.class);

    @Id
    private String id;

    @Column
    private String datacenterUrl;

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "company", cascade =
CascadeType.ALL)
    @CascadeOnDelete
    private List<Page> pages;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(insertable = false, updatable = false)
    private Date modified;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(insertable = false, updatable = false)
    private Date created;

    @PrePersist
    public void prePersist() {
        Date now = new Date();
        created = now;
        modified = now;
    }

    @PreUpdate
    public void preUpdate() {
        modified = new Date();
    }

    public Date getModified() {
        return modified;
    }

    public void setModified(Date modified) {
        log.debug("Olingo trying to set date {}", modified);
    }

    public Date getCreated() {
        return created;
    }

    public void setCreated(Date created) {
        log.debug("Olingo trying to set date {}", created);
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getDatacenterUrl() {
        return datacenterUrl;
    }

    public void setDatacenterUrl(String datacenterUrl) {
        this.datacenterUrl = datacenterUrl;
    }

    public List<Page> getPages() {
        return pages;
    }

    public void setPages(List<Page> pages) {
        this.pages = pages;
    }
}

My Page class looks like this:

@Entity
@Table(name = "HUM_PAGE")
public class Page implements Serializable {
    private static final Logger log = LoggerFactory.getLogger(Page.class);

    /**
     *
     */
    private static final long serialVersionUID = 1L;


    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false, name = "page_name")
    private String name;

    @Column
    private String description;

    @OneToOne
    private Context context;

    @ManyToOne(cascade = CascadeType.REFRESH)
    @JoinColumn(name = "company_id", nullable = false)
    private Company company;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(nullable = false)
    private Date modified;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(nullable = false)
    private Date created;

    @PrePersist
    public void prePersist() {
        Date now = new Date();
        created = now;
        modified = now;
    }

    @PreUpdate
    public void preUpdate() {
        modified = new Date();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

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

    public Long getId() {
        return id;
    }

    public Date getModified() {
        return modified;
    }

    public Date getCreated() {
        return created;
    }

    public Company getCompany() {
        return company;
    }

    public void setCompany(Company company) {
        this.company = company;
    }

    public void setId(Long id) {
        log.debug("Olingo trying to set Id {}", id);
    }

    public void setModified(Date modified) {
        log.debug("Olingo trying to set date {}", modified);
    }

    public void setCreated(Date created) {
        log.debug("Olingo trying to set date {}", created);
    }

}

I am extending the ODataJPAServiceFactory and override the initializeODataJPAContext method:

    @Override
    public ODataJPAContext initializeODataJPAContext() throws
ODataJPARuntimeException {
        ODataJPAContext oDataJPAContext = getODataJPAContext();
        try {

oDataJPAContext.setEntityManagerFactory(JpaEntityManagerFactory.getEntityManagerFactory());

oDataJPAContext.setPersistenceUnitName(JpaEntityManagerFactory.PERSISTENCE_UNIT_NAME);
            oDataJPAContext.setJPAEdmMappingModel("HumEdmMapping.xml");
        } catch (NamingException | SQLException e) {
            throw new ODataRuntimeException(e);
        }
        return oDataJPAContext;
    }

The EntityManagerFactory is created as follows:

    public static synchronized EntityManagerFactory
getEntityManagerFactory()
            throws NamingException, SQLException {
        if (entityManagerFactory == null) {
            InitialContext ctx = new InitialContext();
            DataSource ds = (DataSource) ctx.lookup(DATA_SOURCE_NAME);
            Map<String, Object> properties = new HashMap<String, Object>();
            properties.put(PersistenceUnitProperties.NON_JTA_DATASOURCE,
ds);
            entityManagerFactory = Persistence.createEntityManagerFactory(
                    PERSISTENCE_UNIT_NAME, properties);
        }
        return entityManagerFactory;
    }

My mapping file looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<JPAEDMMappingModel

xmlns="http://www.apache.org/olingo/odata2/jpa/processor/api/model/mapping">
    <PersistenceUnit name="s.h.backend">
        <JPAEntityTypes>
            <JPAEntityType name="Company">
                <EDMEntityType>Company</EDMEntityType>
                <EDMEntitySet>Companies</EDMEntitySet>
                <JPAAttributes>
                    <JPAAttribute name="created">Created</JPAAttribute>
                    <JPAAttribute
name="datacenterUrl">DatacenterUrl</JPAAttribute>
                    <JPAAttribute name="id">Id</JPAAttribute>
                    <JPAAttribute name="modified">Modified</JPAAttribute>
                </JPAAttributes>
                <JPARelationships>
                    <JPARelationship
name="pages">Pages</JPARelationship>               
                </JPARelationships>
            </JPAEntityType>
            <JPAEntityType name="Page">
                <EDMEntityType>Page</EDMEntityType>
                <EDMEntitySet>Pages</EDMEntitySet>
                <JPAAttributes>
                    <JPAAttribute name="created">Created</JPAAttribute>
                    <JPAAttribute name="name">Name</JPAAttribute>
                    <JPAAttribute
name="description">Description</JPAAttribute>
                    <JPAAttribute name="id">Id</JPAAttribute>
                    <JPAAttribute name="modified">Modified</JPAAttribute>
                </JPAAttributes>
                <JPARelationships>
                    <JPARelationship
name="company">Company</JPARelationship>               
                </JPARelationships>
            </JPAEntityType>

...

        </JPAEntityTypes>
        <JPAEmbeddableTypes>
        </JPAEmbeddableTypes>
    </PersistenceUnit>
</JPAEDMMappingModel>

Solution

  • I tested different solutions for this issue which were already discussed in the comments of my original post:

    • @Cache(type=CacheType.NONE): according to the documentation of EclispeLink (https://eclipse.org/eclipselink/api/2.0/org/eclipse/persistence/config/CacheType.html) the use of this annotation is discouraged.

    • registering a PostPersistListener class with the Page class using @EntityListeners and do an "invalidateAll" in the method annotated with @PostPersist. The result was not reliable.

    • @Cache(isolation=CacheIsolationType.ISOLATED): This annotation does what I need.

    So my problem has been solved for now. Thought it's a good idea to document it for anyone experiencing the same issue.. (And for me to remember it the next time ;-))