Skip to content
This repository has been archived by the owner on Nov 23, 2021. It is now read-only.

Commit

Permalink
Merge pull request #617 from hohwille/feature-id-ref
Browse files Browse the repository at this point in the history
IdRef feature for typesafe and expressive entity references
  • Loading branch information
hohwille authored Aug 9, 2018
2 parents fe3a073 + 630000a commit c2bd651
Show file tree
Hide file tree
Showing 53 changed files with 1,379 additions and 214 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.oasp.module.basic.common.api.reference;

import java.util.Objects;

/**
* Generic implementation of {@link Ref}.
*
* @param <ID> generic type of {@link #getId() ID}.
* @param <E> generic type of the referenced {@link net.sf.mmm.util.entity.api.Entity}.
*/
public class GenericIdRef<ID, E> implements Ref<ID, E> {

private static final long serialVersionUID = 1L;

private final ID id;

/**
* The constructor.
*
* @param id the {@link #getId() ID}.
*/
public GenericIdRef(ID id) {

super();
Objects.requireNonNull(id, "id");
this.id = id;
}

@Override
public ID getId() {

return this.id;
}

@Override
public int hashCode() {

return this.id.hashCode();
}

@Override
public boolean equals(Object obj) {

if (this == obj) {
return true;
}
if ((obj == null) || (getClass() != obj.getClass())) {
return false;
}
GenericIdRef<?, ?> other = (GenericIdRef<?, ?>) obj;
return Objects.equals(this.id, other.id);
}

@Override
public String toString() {

return this.id.toString();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package io.oasp.module.basic.common.api.reference;

import net.sf.mmm.util.entity.api.GenericEntity;

/**
* A {@link Ref} using {@link Long} values as {@link #getId() ID}.
*
* @param <E> generic type of the referenced {@link net.sf.mmm.util.entity.api.Entity}.
*/
public class IdRef<E> extends GenericIdRef<Long, E> {

private static final long serialVersionUID = 1L;

/**
* The constructor.
*
* @param id the {@link #getId() ID}.
*/
public IdRef(Long id) {

super(id);
}

/**
* @param <E> generic type of the referenced {@link GenericEntity}.
* @param entity the {@link GenericEntity} to reference.
* @return the {@link IdRef} pointing to the given {@link GenericEntity} or {@code null} if the {@link GenericEntity}
* or its {@link GenericEntity#getId() ID} is {@code null}.
*/
public static <E extends GenericEntity<Long>> IdRef<E> of(E entity) {

if (entity == null) {
return null;
}
return of(entity.getId());
}

/**
* @param <E> generic type of the referenced {@link GenericEntity}.
* @param id the {@link #getId() ID} to wrap.
* @return the {@link IdRef} pointing to an entity with the specified {@link #getId() ID} or {@code null} if the given
* {@code ID} was {@code null}.
*/
public static <E> IdRef<E> of(Long id) {

if (id == null) {
return null;
}
return new IdRef<>(id);
}

/**
* @param <E> generic type of the referenced {@link GenericEntity}.
* @param id the {@link #getId() ID} to wrap.
* @return the {@link IdRef} pointing to an entity with the specified {@link #getId() ID}.
*/
public static <E> IdRef<E> of(long id) {

return new IdRef<>(Long.valueOf(id));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.oasp.module.basic.common.api.reference;

import net.sf.mmm.util.lang.api.Datatype;

/**
* Interface for a reference to an {@link net.sf.mmm.util.entity.api.GenericEntity entity} via its {@link #getId() ID}.
* In most cases you want to use {@link IdRef}.
*
* @param <ID> generic type of {@link #getId() ID}.
* @param <E> generic type of the referenced {@link net.sf.mmm.util.entity.api.Entity}. For flexibility not technically
* bound to {@link net.sf.mmm.util.entity.api.Entity} so it can also be used for an external entity not
* satisfying any requirements.
*/
public interface Ref<ID, E> extends Datatype {

/**
* @return the ({@link net.sf.mmm.util.entity.api.GenericEntity#getId() ID} of the referenced
* {@link net.sf.mmm.util.entity.api.Entity}.
*/
ID getId();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package io.oasp.module.basic.common.api.reference;

import net.sf.mmm.util.entity.api.MutableGenericEntity;

import org.junit.Test;

import io.oasp.module.basic.common.api.to.AbstractEto;
import io.oasp.module.test.common.base.ModuleTest;

/**
* Test of {@link IdRef}.
*/
public class IdRefTest extends ModuleTest {

/** Test of {@link IdRef#of(net.sf.mmm.util.entity.api.GenericEntity)} */
@Test
public void testOfEntity() {

// given
long id = 4711L;
FooEto foo = new FooEto();
foo.setId(id);

// when
IdRef<Foo> fooId = IdRef.<Foo> of(foo); // with Java8 type-inference the additional <Foo> is not required

// then
assertThat(fooId).isNotNull();
assertThat(fooId.getId()).isEqualTo(foo.getId()).isEqualTo(id);
assertThat(fooId.toString()).isEqualTo(Long.toString(id));
assertThat(IdRef.of((Foo) null)).isNull();

Bar bar = new Bar();
bar.setFooId(fooId); // just a syntax/compilation check.
// bar.setBarId(fooId); // will produce compiler error what is desired in such case
IdRef<Bar> barId = IdRef.of(1234L);
bar.setBarId(barId); // this again will compile
}

/** Test of {@link IdRef#of(Long)} */
@Test
public void testOfLong() {

// given
long id = 4711L;

// when
IdRef<Foo> fooId = IdRef.of(id); // not type-safe but required in some cases

// then
assertThat(fooId).isNotNull();
assertThat(fooId.getId()).isEqualTo(id);
assertThat(fooId.toString()).isEqualTo(Long.toString(id));
assertThat(IdRef.of(Long.valueOf(id))).isEqualTo(fooId).isNotSameAs(fooId);
assertThat(IdRef.of((Long) null)).isNull();
}

private interface Foo extends MutableGenericEntity<Long> {

}

private class FooEto extends AbstractEto implements Foo {

private static final long serialVersionUID = 1L;

}

private class Bar {

void setFooId(IdRef<Foo> fooId) {

}

void setBarId(IdRef<Bar> fooId) {

}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.oasp.module.jpa.dataaccess.api;

import javax.persistence.EntityManager;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Internal access to {@link EntityManager}.
*/
class JpaEntityManagerAccess {

private static final Logger LOG = LoggerFactory.getLogger(JpaEntityManagerAccess.class);

private static EntityManager entityManager;

static void setEntityManager(EntityManager entityManager, boolean check) {

if ((JpaEntityManagerAccess.entityManager != null) && (JpaEntityManagerAccess.entityManager != entityManager)) {
if (check) {
throw new IllegalStateException("EntityManager has already been initialized!");
} else {
LOG.debug("EntityManager conflict: {} has been replaced with {}. This may only happen during tests.",
JpaEntityManagerAccess.entityManager, entityManager);
}
}
JpaEntityManagerAccess.entityManager = entityManager;
}

static boolean hasEntityManager() {

return (entityManager != null);
}

static EntityManager getEntityManager() {

if (entityManager == null) {
throw new IllegalStateException("EntityManager has not yet been initialized!");
}
return entityManager;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package io.oasp.module.jpa.dataaccess.api;

import java.util.Collection;

import net.sf.mmm.util.entity.api.GenericEntity;

import io.oasp.module.basic.common.api.reference.GenericIdRef;
import io.oasp.module.basic.common.api.reference.Ref;

/**
* Helper class for generic handling of {@link net.sf.mmm.util.entity.api.PersistenceEntity persistence entities} (based
* on {@link javax.persistence.EntityManager}). In some cases it is required to access JPA features in a static way.
* E.g. a common case is a setter in your {@link net.sf.mmm.util.entity.api.PersistenceEntity} for a
* {@link io.oasp.module.basic.common.api.reference.Ref reference} from an
* {@link io.oasp.module.basic.common.api.to.AbstractEto ETO} that can be archieved via the following code:
*
* <pre>
* &#64;Entity
* &#64;Table("Foo")
* public class FooEntity extends ApplicationPersistenceEntity implements Foo {
* &#64;OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
* &#64;JoinColumn(name = "bar")
* private BarEntity bar;
* ...
* &#64;Override
* public void setBarId({@link io.oasp.module.basic.common.api.reference.IdRef}{@literal <Bar>} barId) {
* this.bar = {@link JpaHelper}.{@link JpaHelper#asEntity(Ref, Class) asEntity}(barId, BarEntity.class);
* }
* }
* </pre>
*/
public class JpaHelper {

/**
* @param <E> generic type of the {@link net.sf.mmm.util.entity.api.PersistenceEntity entity}
* @param reference the {@link Ref} or {@code null}. Typically an
* {@link io.oasp.module.basic.common.api.reference.IdRef}.
* @param entityClass the {@link net.sf.mmm.util.entity.api.PersistenceEntity entity} {@link Class}.
* @return the {@link net.sf.mmm.util.entity.api.PersistenceEntity entity} of the specified {@link Class} with the
* {@link Ref#getId() ID} from the given {@link GenericIdRef} or {@code null} if the given {@link Ref} is
* {@code null}.
*/
public static <E> E asEntity(Ref<?, ? super E> reference, Class<E> entityClass) {

if (reference == null) {
return null;
} else {
return JpaEntityManagerAccess.getEntityManager().getReference(entityClass, reference.getId());
}
}

/**
* @param <E> generic type of the input {@link GenericEntity entities} (most commonly the entity interface).
* @param <P> generic type of the output {@link net.sf.mmm.util.entity.api.PersistenceEntity persistence entities}.
* @param input the {@link Collection} of {@link GenericEntity entities} (e.g.
* {@link io.oasp.module.basic.common.api.to.AbstractEto ETOs}) to use as input.
* @param entityClass the {@link Class} reflecting the {@link net.sf.mmm.util.entity.api.PersistenceEntity}.
* @param output die {@link Collection} where to {@link Collection#add(Object) add} the
* {@link net.sf.mmm.util.entity.api.PersistenceEntity persistent entities} corresponding to the input
* {@link GenericEntity entities}. Most probably {@link Collection#isEmpty() empty} but may also already
* contain entities so this method will add additional entities.
*/
@SuppressWarnings("unchecked")
public static <E extends GenericEntity<?>, P extends E> void asEntities(Collection<? extends E> input,
Class<P> entityClass, Collection<P> output) {

for (E eto : input) {
P entity;
if (entityClass.isInstance(eto)) {
entity = (P) eto;
} else {
entity = JpaEntityManagerAccess.getEntityManager().getReference(entityClass, eto.getId());
}
output.add(entity);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.oasp.module.jpa.dataaccess.api;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

/**
* Initializer bean for {@link EntityManager}. Will be auto configured via {@code oasp4j-starter-jpa}.
*/
public class JpaInitializer {

/**
* @param entityManager the {@link EntityManager} to inject.
*/
@PersistenceContext
protected void setEntityManager(EntityManager entityManager) {

JpaEntityManagerAccess.setEntityManager(entityManager, true);
}

/**
* @param entityManager the {@link EntityManager} to set.
* @param check - {@code true} to check that the {@link EntityManager} does not change on-the-fly (desired in
* productive code), {@code false} otherwise (may be desired in test-code).
*/
protected void setEntityManager(EntityManager entityManager, boolean check) {

JpaEntityManagerAccess.setEntityManager(entityManager, check);
}
}
5 changes: 5 additions & 0 deletions modules/jpa-dao/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
<groupId>io.oasp.java.modules</groupId>
<artifactId>oasp4j-jpa-basic</artifactId>
</dependency>
<dependency>
<groupId>io.oasp.java.modules</groupId>
<artifactId>oasp4j-beanmapping</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
Expand Down
Loading

0 comments on commit c2bd651

Please sign in to comment.