package play.db.jpa;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Level;
import org.apache.log4j.config.PropertyGetter;
import org.hibernate.CallbackException;
import org.hibernate.EmptyInterceptor;
import org.hibernate.collection.PersistentCollection;
import org.hibernate.ejb.Ejb3Configuration;
import org.hibernate.type.Type;
import play.Invoker.InvocationContext;
import play.Logger;
import play.Play;
import play.PlayPlugin;
import play.classloading.ApplicationClasses.ApplicationClass;
import play.data.binding.Binder;
import play.data.binding.NoBinding;
import play.data.binding.ParamNode;
import play.data.binding.RootParamNode;
import play.db.DB;
import play.db.Model;
import play.exceptions.JPAException;
import play.exceptions.UnexpectedException;
import play.utils.Utils;
import javax.persistence.*;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.util.*;
/**
* JPA Plugin
*/
public class JPAPlugin extends PlayPlugin {
public static boolean autoTxs = true;
@Override
public Object bind(RootParamNode rootParamNode, String name, Class clazz, java.lang.reflect.Type type, Annotation[] annotations) {
// TODO need to be more generic in order to work with JPASupport
if (JPABase.class.isAssignableFrom(clazz)) {
ParamNode paramNode = rootParamNode.getChild(name, true);
String[] keyNames = new JPAModelLoader(clazz).keyNames();
ParamNode[] ids = new ParamNode[keyNames.length];
// Collect the matching ids
int i = 0;
for (String keyName : keyNames) {
ids[i++] = paramNode.getChild(keyName, true);
}
if (ids != null && ids.length > 0) {
try {
EntityManager em = JPA.em();
String q = "from " + clazz.getName() + " o where";
for (String keyName : keyNames) {
q += " o." + keyName + " = ? and " ;
}
if (q.length() > 4) {
q = q.substring(0, q.length() - 4);
}
Query query = em.createQuery(q);
// The primary key can be a composite.
Class[] pk = new JPAModelLoader(clazz).keyTypes();
int j = 0;
for (ParamNode id : ids) {
if (id.getValues() == null || id.getValues().length == 0) {
// We have no ids, it is a new entity
return GenericModel.create(rootParamNode, name, clazz, annotations);
}
query.setParameter(j + 1, Binder.directBind(id.getOriginalKey(), annotations, id.getValues()[0], pk[j++], null));
}
Object o = query.getSingleResult();
return GenericModel.edit(rootParamNode, name, o, annotations);
} catch (NoResultException e) {
// ok
} catch (Exception e) {
throw new UnexpectedException(e);
}
}
return GenericModel.create(rootParamNode, name, clazz, annotations);
}
return null;
}
@Override
public Object bindBean(RootParamNode rootParamNode, String name, Object bean) {
if (bean instanceof JPABase) {
return GenericModel.edit(rootParamNode, name, bean, null);
}
return null;
}
@Override
public void enhance(ApplicationClass applicationClass) throws Exception {
new JPAEnhancer().enhanceThisClass(applicationClass);
}
@Override
public void onApplicationStart() {
if (JPA.entityManagerFactory == null) {
List<Class> classes = Play.classloader.getAnnotatedClasses(Entity.class);
if (classes.isEmpty() && Play.configuration.getProperty("jpa.entities", "").equals("")) {
return;
}
final String dataSource = Play.configuration.getProperty("hibernate.connection.datasource");
if (StringUtils.isEmpty(dataSource) && DB.datasource == null) {
throw new JPAException("Cannot start a JPA manager without a properly configured database", new NullPointerException("No datasource configured"));
}
Ejb3Configuration cfg = new Ejb3Configuration();
if (DB.datasource != null) {
cfg.setDataSource(DB.datasource);
}
if (!Play.configuration.getProperty("jpa.ddl", Play.mode.isDev() ? "update" : "none").equals("none")) {
cfg.setProperty("hibernate.hbm2ddl.auto", Play.configuration.getProperty("jpa.ddl", "update"));
}
cfg.setProperty("hibernate.dialect", getDefaultDialect(Play.configuration.getProperty("db.driver")));
cfg.setProperty("javax.persistence.transaction", "RESOURCE_LOCAL");
// Explicit SAVE for JPABase is implemented here
// ~~~~~~
// We've hacked the org.hibernate.event.def.AbstractFlushingEventListener line 271, to flush collection update,remove,recreation
// only if the owner will be saved.
// As is:
// if (session.getInterceptor().onCollectionUpdate(coll, ce.getLoadedKey())) {
// actionQueue.addAction(...);
// }
//
// This is really hacky. We should move to something better than Hibernate like EBEAN
cfg.setInterceptor(new EmptyInterceptor() {
@Override
public int[] findDirty(Object o, Serializable id, Object[] arg2, Object[] arg3, String[] arg4, Type[] arg5) {
if (o instanceof JPABase && !((JPABase) o).willBeSaved) {
return new int[0];
}
return null;
}
@Override
public boolean onCollectionUpdate(Object collection, Serializable key) throws CallbackException {
if (collection instanceof PersistentCollection) {
Object o = ((PersistentCollection) collection).getOwner();
if (o instanceof JPABase) {
return ((JPABase) o).willBeSaved;
}
} else {
System.out.println("HOO: Case not handled !!!");
}
return super.onCollectionUpdate(collection, key);
}
@Override
public boolean onCollectionRecreate(Object collection, Serializable key) throws CallbackException {
if (collection instanceof PersistentCollection) {
Object o = ((PersistentCollection) collection).getOwner();
if (o instanceof JPABase) {
return ((JPABase) o).willBeSaved;
}
} else {
System.out.println("HOO: Case not handled !!!");
}
return super.onCollectionRecreate(collection, key);
}
@Override
public boolean onCollectionRemove(Object collection, Serializable key) throws CallbackException {
if (collection instanceof PersistentCollection) {
Object o = ((PersistentCollection) collection).getOwner();
if (o instanceof JPABase) {
return ((JPABase) o).willBeSaved;
}
} else {
System.out.println("HOO: Case not handled !!!");
}
return super.onCollectionRemove(collection, key);
}
});
if (Play.configuration.getProperty("jpa.debugSQL", "false").equals("true")) {
org.apache.log4j.Logger.getLogger("org.hibernate.SQL").setLevel(Level.ALL);
} else {
org.apache.log4j.Logger.getLogger("org.hibernate.SQL").setLevel(Level.OFF);
}
// inject additional hibernate.* settings declared in Play! configuration
cfg.addProperties((Properties) Utils.Maps.filterMap(Play.configuration, "^hibernate\\..*"));
try {
Field field = cfg.getClass().getDeclaredField("overridenClassLoader");
field.setAccessible(true);
field.set(cfg, Play.classloader);
} catch (Exception e) {
Logger.error(e, "Error trying to override the hibernate classLoader (new hibernate version ???)");
}
for (Class<?> clazz : classes) {
if (clazz.isAnnotationPresent(Entity.class)) {
cfg.addAnnotatedClass(clazz);
if (Logger.isTraceEnabled()) {
Logger.trace("JPA Model : %s", clazz);
}
}
}
String[] moreEntities = Play.configuration.getProperty("jpa.entities", "").split(", ");
for (String entity : moreEntities) {
if (entity.trim().equals("")) {
continue;
}
try {
cfg.addAnnotatedClass(Play.classloader.loadClass(entity));
} catch (Exception e) {
Logger.warn("JPA -> Entity not found: %s", entity);
}
}
for (ApplicationClass applicationClass : Play.classes.all()) {
if (applicationClass.isClass() || applicationClass.javaPackage == null) {
continue;
}
Package p = applicationClass.javaPackage;
Logger.info("JPA -> Adding package: %s", p.getName());
cfg.addPackage(p.getName());
}
String mappingFile = Play.configuration.getProperty("jpa.mapping-file", "");
if (mappingFile != null && mappingFile.length() > 0) {
cfg.addResource(mappingFile);
}
if (Logger.isTraceEnabled()) {
Logger.trace("Initializing JPA ...");
}
try {
JPA.entityManagerFactory = cfg.buildEntityManagerFactory();
} catch (PersistenceException e) {
throw new JPAException(e.getMessage(), e.getCause() != null ? e.getCause() : e);
}
JPQL.instance = new JPQL();
}
}
static String getDefaultDialect(String driver) {
String dialect = Play.configuration.getProperty("jpa.dialect");
if (dialect != null) {
return dialect;
} else if (driver.equals("org.h2.Driver")) {
return "org.hibernate.dialect.H2Dialect";
} else if (driver.equals("org.hsqldb.jdbcDriver")) {
return "org.hibernate.dialect.HSQLDialect";
} else if (driver.equals("com.mysql.jdbc.Driver")) {
return "play.db.jpa.MySQLDialect";
} else if (driver.equals("org.postgresql.Driver")) {
return "org.hibernate.dialect.PostgreSQLDialect";
} else if (driver.toLowerCase().equals("com.ibm.db2.jdbc.app.DB2Driver")) {
return "org.hibernate.dialect.DB2Dialect";
} else if (driver.equals("com.ibm.as400.access.AS400JDBCDriver")) {
return "org.hibernate.dialect.DB2400Dialect";
} else if (driver.equals("com.ibm.as400.access.AS390JDBCDriver")) {
return "org.hibernate.dialect.DB2390Dialect";
} else if (driver.equals("oracle.jdbc.driver.OracleDriver")) {
return "org.hibernate.dialect.Oracle9iDialect";
} else if (driver.equals("com.sybase.jdbc2.jdbc.SybDriver")) {
return "org.hibernate.dialect.SybaseAnywhereDialect";
} else if ("com.microsoft.jdbc.sqlserver.SQLServerDriver".equals(driver)) {
return "org.hibernate.dialect.SQLServerDialect";
} else if ("com.sap.dbtech.jdbc.DriverSapDB".equals(driver)) {
return "org.hibernate.dialect.SAPDBDialect";
} else if ("com.informix.jdbc.IfxDriver".equals(driver)) {
return "org.hibernate.dialect.InformixDialect";
} else if ("com.ingres.jdbc.IngresDriver".equals(driver)) {
return "org.hibernate.dialect.IngresDialect";
} else if ("progress.sql.jdbc.JdbcProgressDriver".equals(driver)) {
return "org.hibernate.dialect.ProgressDialect";
} else if ("com.mckoi.JDBCDriver".equals(driver)) {
return "org.hibernate.dialect.MckoiDialect";
} else if ("InterBase.interclient.Driver".equals(driver)) {
return "org.hibernate.dialect.InterbaseDialect";
} else if ("com.pointbase.jdbc.jdbcUniversalDriver".equals(driver)) {
return "org.hibernate.dialect.PointbaseDialect";
} else if ("com.frontbase.jdbc.FBJDriver".equals(driver)) {
return "org.hibernate.dialect.FrontbaseDialect";
} else if ("org.firebirdsql.jdbc.FBDriver".equals(driver)) {
return "org.hibernate.dialect.FirebirdDialect";
} else {
throw new UnsupportedOperationException("I do not know which hibernate dialect to use with "
+ driver + " and I cannot guess it, use the property jpa.dialect in config file");
}
}
@Override
public void onApplicationStop() {
if (JPA.entityManagerFactory != null) {
JPA.entityManagerFactory.close();
JPA.entityManagerFactory = null;
}
}
@Override
public void beforeInvocation() {
if(InvocationContext.current().getAnnotation(NoTransaction.class) != null ) {
//Called method or class is annotated with @NoTransaction telling us that
//we should not start a transaction
return ;
}
boolean readOnly = false;
Transactional tx = InvocationContext.current().getAnnotation(Transactional.class);
if (tx != null) {
readOnly = tx.readOnly();
}
startTx(readOnly);
}
@Override
public void afterInvocation() {
closeTx(false);
}
@Override
public void onInvocationException(Throwable e) {
closeTx(true);
}
@Override
public void invocationFinally() {
closeTx(true);
}
/**
* initialize the JPA context and starts a JPA transaction
*
* @param readonly true for a readonly transaction
* @param autoCommit true to automatically commit the DB transaction after each JPA statement
*/
public static void startTx(boolean readonly) {
if (!JPA.isEnabled()) {
return;
}
EntityManager manager = JPA.entityManagerFactory.createEntityManager();
manager.setFlushMode(FlushModeType.COMMIT);
manager.setProperty("org.hibernate.readOnly", readonly);
if (autoTxs) {
manager.getTransaction().begin();
}
JPA.createContext(manager, readonly);
}
/**
* clear current JPA context and transaction
* @param rollback shall current transaction be committed (false) or cancelled (true)
*/
public static void closeTx(boolean rollback) {
if (!JPA.isEnabled() || JPA.local.get() == null) {
return;
}
EntityManager manager = JPA.get().entityManager;
try {
if (autoTxs) {
// Be sure to set the connection is non-autoCommit mode as some driver will complain about COMMIT statement
try {
DB.getConnection().setAutoCommit(false);
} catch(Exception e) {
Logger.error(e, "Why the driver complains here?");
}
// Commit the transaction
if (manager.getTransaction().isActive()) {
if (JPA.get().readonly || rollback || manager.getTransaction().getRollbackOnly()) {
manager.getTransaction().rollback();
} else {
try {
if (autoTxs) {
manager.getTransaction().commit();
}
} catch (Throwable e) {
for (int i = 0; i < 10; i++) {
if (e instanceof PersistenceException && e.getCause() != null) {
e = e.getCause();
break;
}
e = e.getCause();
if (e == null) {
break;
}
}
throw new JPAException("Cannot commit", e);
}
}
}
}
} finally {
manager.close();
JPA.clearContext();
}
}
@Override
public Model.Factory modelFactory(Class<? extends Model> modelClass) {
if (modelClass.isAnnotationPresent(Entity.class)) {
return new JPAModelLoader(modelClass);
}
return null;
}
@Override
public void afterFixtureLoad() {
if (JPA.isEnabled()) {
JPA.em().clear();
}
}
public static class JPAModelLoader implements Model.Factory {
private Class<? extends Model> clazz;
private Map<String, Model.Property> properties;
public JPAModelLoader(Class<? extends Model> clazz) {
this.clazz = clazz;
}
public Model findById(Object id) {
try {
if (id == null) {
return null;
}
return JPA.em().find(clazz, id);
} catch (Exception e) {
// Key is invalid, thus nothing was found
return null;
}
}
@SuppressWarnings("unchecked")
public List<Model> fetch(int offset, int size, String orderBy, String order, List<String> searchFields, String keywords, String where) {
String q = "from " + clazz.getName();
if (keywords != null && !keywords.equals("")) {
String searchQuery = getSearchQuery(searchFields);
if (!searchQuery.equals("")) {
q += " where (" + searchQuery + ")";
}
q += (where != null ? " and " + where : "");
} else {
q += (where != null ? " where " + where : "");
}
if (orderBy == null && order == null) {
orderBy = "id";
order = "ASC";
}
if (orderBy == null && order != null) {
orderBy = "id";
}
if (order == null || (!order.equals("ASC") && !order.equals("DESC"))) {
order = "ASC";
}
q += " order by " + orderBy + " " + order;
Query query = JPA.em().createQuery(q);
if (keywords != null && !keywords.equals("") && q.indexOf("?1") != -1) {
query.setParameter(1, "%" + keywords.toLowerCase() + "%");
}
query.setFirstResult(offset);
query.setMaxResults(size);
return query.getResultList();
}
public Long count(List<String> searchFields, String keywords, String where) {
String q = "select count(*) from " + clazz.getName() + " e";
if (keywords != null && !keywords.equals("")) {
String searchQuery = getSearchQuery(searchFields);
if (!searchQuery.equals("")) {
q += " where (" + searchQuery + ")";
}
q += (where != null ? " and " + where : "");
} else {
q += (where != null ? " where " + where : "");
}
Query query = JPA.em().createQuery(q);
if (keywords != null && !keywords.equals("") && q.indexOf("?1") != -1) {
query.setParameter(1, "%" + keywords.toLowerCase() + "%");
}
return Long.decode(query.getSingleResult().toString());
}
public void deleteAll() {
JPA.em().createQuery("delete from " + clazz.getName()).executeUpdate();
}
public List<Model.Property> listProperties() {
List<Model.Property> properties = new ArrayList<Model.Property>();
Set<Field> fields = new LinkedHashSet<Field>();
Class<?> tclazz = clazz;
while (!tclazz.equals(Object.class)) {
Collections.addAll(fields, tclazz.getDeclaredFields());
tclazz = tclazz.getSuperclass();
}
for (Field f : fields) {
if (Modifier.isTransient(f.getModifiers())) {
continue;
}
if (f.isAnnotationPresent(Transient.class)) {
continue;
}
if (f.isAnnotationPresent(NoBinding.class)) {
NoBinding a = f.getAnnotation(NoBinding.class);
List<String> values = Arrays.asList(a.value());
if (values.contains("*")) {
continue;
}
}
Model.Property mp = buildProperty(f);
if (mp != null) {
properties.add(mp);
}
}
return properties;
}
public String keyName() {
return keyField().getName();
}
public Class<?> keyType() {
return keyField().getType();
}
public Class<?>[] keyTypes() {
Field[] fields = keyFields();
Class<?>[] types = new Class<?>[fields.length];
int i = 0;
for (Field field : fields) {
types[i++] = field.getType();
}
return types;
}
public String[] keyNames() {
Field[] fields = keyFields();
String[] names = new String[fields.length];
int i = 0;
for (Field field : fields) {
names[i++] = field.getName();
}
return names;
}
private Class<?> getCompositeKeyClass() {
Class<?> tclazz = clazz;
while (!tclazz.equals(Object.class)) {
// Only consider mapped types
if (tclazz.isAnnotationPresent(Entity.class)
|| tclazz.isAnnotationPresent(MappedSuperclass.class)) {
IdClass idClass = tclazz.getAnnotation(IdClass.class);
if (idClass != null)
return idClass.value();
}
tclazz = tclazz.getSuperclass();
}
throw new UnexpectedException("Invalid mapping for class " + clazz + ": multiple IDs with no @IdClass annotation");
}
private void initProperties() {
synchronized(this){
if(properties != null)
return;
properties = new HashMap<String,Model.Property>();
Set<Field> fields = getModelFields(clazz);
for (Field f : fields) {
if (Modifier.isTransient(f.getModifiers())) {
continue;
}
if (f.isAnnotationPresent(Transient.class)) {
continue;
}
Model.Property mp = buildProperty(f);
if (mp != null) {
properties.put(mp.name, mp);
}
}
}
}
private Object makeCompositeKey(Model model) throws Exception {
initProperties();
Class<?> idClass = getCompositeKeyClass();
Object id = idClass.newInstance();
PropertyDescriptor[] idProperties = PropertyUtils.getPropertyDescriptors(idClass);
if(idProperties == null || idProperties.length == 0)
throw new UnexpectedException("Composite id has no properties: "+idClass.getName());
for (PropertyDescriptor idProperty : idProperties) {
// do we have a field for this?
String idPropertyName = idProperty.getName();
// skip the "class" property...
if(idPropertyName.equals("class"))
continue;
Model.Property modelProperty = this.properties.get(idPropertyName);
if(modelProperty == null)
throw new UnexpectedException("Composite id property missing: "+clazz.getName()+"."+idPropertyName
+" (defined in IdClass "+idClass.getName()+")");
// sanity check
Object value = modelProperty.field.get(model);
if(modelProperty.isMultiple)
throw new UnexpectedException("Composite id property cannot be multiple: "+clazz.getName()+"."+idPropertyName);
// now is this property a relation? if yes then we must use its ID in the key (as per specs)
if(modelProperty.isRelation){
// get its id
if(!Model.class.isAssignableFrom(modelProperty.type))
throw new UnexpectedException("Composite id property entity has to be a subclass of Model: "
+clazz.getName()+"."+idPropertyName);
// we already checked that cast above
@SuppressWarnings("unchecked")
Model.Factory factory = Model.Manager.factoryFor((Class<? extends Model>) modelProperty.type);
if(factory == null)
throw new UnexpectedException("Failed to find factory for Composite id property entity: "
+clazz.getName()+"."+idPropertyName);
// we already checked that cast above
if(value != null)
value = factory.keyValue((Model) value);
}
// now affect the composite id with this id
PropertyUtils.setSimpleProperty(id, idPropertyName, value);
}
return id;
}
public Object keyValue(Model m) {
try {
if (m == null) {
return null;
}
// Do we have a @IdClass or @Embeddable?
if (m.getClass().isAnnotationPresent(IdClass.class)) {
return makeCompositeKey(m);
}
// Is it a composite key? If yes we need to return the matching PK
final Field[] fields = keyFields();
final Object[] values = new Object[fields.length];
int i = 0;
for (Field f : fields) {
final Object o = f.get(m);
if (o != null) {
values[i++] = o;
}
}
// If we have only one id return it
if (values.length == 1) {
return values[0];
}
return values;
} catch (Exception ex) {
throw new UnexpectedException(ex);
}
}
public static Set<Field> getModelFields(Class<?> clazz){
Set<Field> fields = new LinkedHashSet<Field>();
Class<?> tclazz = clazz;
while (!tclazz.equals(Object.class)) {
// Only add fields for mapped types
if(tclazz.isAnnotationPresent(Entity.class)
|| tclazz.isAnnotationPresent(MappedSuperclass.class))
Collections.addAll(fields, tclazz.getDeclaredFields());
tclazz = tclazz.getSuperclass();
}
return fields;
}
//
Field keyField() {
Class c = clazz;
try {
while (!c.equals(Object.class)) {
for (Field field : c.getDeclaredFields()) {
if (field.isAnnotationPresent(Id.class) || field.isAnnotationPresent(EmbeddedId.class)) {
field.setAccessible(true);
return field;
}
}
c = c.getSuperclass();
}
} catch (Exception e) {
throw new UnexpectedException("Error while determining the object @Id for an object of type " + clazz);
}
throw new UnexpectedException("Cannot get the object @Id for an object of type " + clazz);
}
Field[] keyFields() {
Class c = clazz;
try {
List<Field> fields = new ArrayList<Field>();
while (!c.equals(Object.class)) {
for (Field field : c.getDeclaredFields()) {
if (field.isAnnotationPresent(Id.class) || field.isAnnotationPresent(EmbeddedId.class)) {
field.setAccessible(true);
fields.add(field);
}
}
c = c.getSuperclass();
}
final Field[] f = fields.toArray(new Field[fields.size()]);
if (f.length == 0) {
throw new UnexpectedException("Cannot get the object @Id for an object of type " + clazz);
}
return f;
} catch (Exception e) {
throw new UnexpectedException("Error while determining the object @Id for an object of type " + clazz);
}
}
String getSearchQuery(List<String> searchFields) {
String q = "";
for (Model.Property property : listProperties()) {
if (property.isSearchable && (searchFields == null || searchFields.isEmpty() ? true : searchFields.contains(property.name))) {
if (!q.equals("")) {
q += " or ";
}
q += "lower(" + property.name + ") like ?1";
}
}
return q;
}
Model.Property buildProperty(final Field field) {
Model.Property modelProperty = new Model.Property();
modelProperty.type = field.getType();
modelProperty.field = field;
if (Model.class.isAssignableFrom(field.getType())) {
if (field.isAnnotationPresent(OneToOne.class)) {
if (field.getAnnotation(OneToOne.class).mappedBy().equals("")) {
modelProperty.isRelation = true;
modelProperty.relationType = field.getType();
modelProperty.choices = new Model.Choices() {
@SuppressWarnings("unchecked")
public List<Object> list() {
return JPA.em().createQuery("from " + field.getType().getName()).getResultList();
}
};
}
}
if (field.isAnnotationPresent(ManyToOne.class)) {
modelProperty.isRelation = true;
modelProperty.relationType = field.getType();
modelProperty.choices = new Model.Choices() {
@SuppressWarnings("unchecked")
public List<Object> list() {
return JPA.em().createQuery("from " + field.getType().getName()).getResultList();
}
};
}
}
if (Collection.class.isAssignableFrom(field.getType())) {
final Class<?> fieldType = (Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
if (field.isAnnotationPresent(OneToMany.class)) {
if (field.getAnnotation(OneToMany.class).mappedBy().equals("")) {
modelProperty.isRelation = true;
modelProperty.isMultiple = true;
modelProperty.relationType = fieldType;
modelProperty.choices = new Model.Choices() {
@SuppressWarnings("unchecked")
public List<Object> list() {
return JPA.em().createQuery("from " + fieldType.getName()).getResultList();
}
};
}
}
if (field.isAnnotationPresent(ManyToMany.class)) {
if (field.getAnnotation(ManyToMany.class).mappedBy().equals("")) {
modelProperty.isRelation = true;
modelProperty.isMultiple = true;
modelProperty.relationType = fieldType;
modelProperty.choices = new Model.Choices() {
@SuppressWarnings("unchecked")
public List<Object> list() {
return JPA.em().createQuery("from " + fieldType.getName()).getResultList();
}
};
}
}
}
if (field.getType().isEnum()) {
modelProperty.choices = new Model.Choices() {
@SuppressWarnings("unchecked")
public List<Object> list() {
return (List<Object>) Arrays.asList(field.getType().getEnumConstants());
}
};
}
modelProperty.name = field.getName();
if (field.getType().equals(String.class)) {
modelProperty.isSearchable = true;
}
if (field.isAnnotationPresent(GeneratedValue.class)) {
modelProperty.isGenerated = true;
}
if (field.isAnnotationPresent(Id.class) || field.isAnnotationPresent(EmbeddedId.class)) {
// Look if the target is an embeddable class
if (field.getType().isAnnotationPresent(Embeddable.class) || field.getType().isAnnotationPresent(IdClass.class) ) {
modelProperty.isRelation = true;
modelProperty.relationType = field.getType();
}
}
return modelProperty;
}
}
}