2

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>
defect
  • 51
  • 9
  • Did you managed to solve the problem? I'm currently facing the exact same issue and don't know how to solve it... – 3dDi92 Nov 15 '16 at 16:58
  • No I didn't solve it until now. I was busy with other tasks but will try to solve it again when I have some spare time. In the meantime we will simply avoid using the $expand keyword in our project which is a pity. – defect Nov 15 '16 at 18:00
  • I already found a workaround but this one, somehow only works for test data which is generated by the backend. In your case you would have to define a specific method in your company class to add a single page to the List pages. After a new page has been persist you basically have to call the parent dataset (the company) and add the new persist page. Nevertheless it only works for my test data and not in productive version and I thought that this would be handled by the JPA framework... – 3dDi92 Nov 16 '16 at 08:34
  • @3dDi92: I've found a workaround that works for the moment but needs some tweaking. It appears that EclipseLink is caching the data and there's no refresh of the company object when the page is created. The result is that the company object used by olingo is a cached one. For a test workaround I've disabled the cache of the company object by adding the Annotation @Cache(type=CacheType.NONE) to the company class. Now I need to look for a better way to have a refresh done when a page is added using olingo. – defect Nov 17 '16 at 15:32
  • Thanks for the tip. I will have a look at it and test it. I'm not sure if this really solves the problem. – 3dDi92 Nov 18 '16 at 14:05
  • @3dDi92: Meanwhile I switched to the more encouraged use of @Cache(isolation=CacheIsolationType.ISOLATED) for the company. Still the $expand is working. My preferred way would be to somehow "invalidate" the company when a new page has been created or an existing one has been updated for that company. But in the meantime this might be a sufficient solution for me. – defect Nov 18 '16 at 14:33

1 Answers1

1

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 ;-))

defect
  • 51
  • 9