package play.db.jpa;
import play.Play;
import play.data.binding.BeanWrapper;
import play.data.binding.Binder;
import play.data.binding.ParamNode;
import play.data.validation.Validation;
import play.exceptions.UnexpectedException;
import play.mvc.Scope.Params;
import javax.persistence.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.*;
/**
* A super class for JPA entities
*/
@MappedSuperclass
@SuppressWarnings("unchecked")
public class GenericModel extends JPABase {
/**
* This method is deprecated. Use this instead:
*
* public static <T extends JPABase> T create(ParamNode rootParamNode, String name, Class<?> type, Annotation[] annotations)
*/
@Deprecated
public static <T extends JPABase> T create(Class<?> type, String name, Map<String, String[]> params, Annotation[] annotations) {
ParamNode rootParamNode = ParamNode.convert(params);
return (T)create(rootParamNode, name, type, annotations);
}
public static <T extends JPABase> T create(ParamNode rootParamNode, String name, Class<?> type, Annotation[] annotations) {
try {
Constructor c = type.getDeclaredConstructor();
c.setAccessible(true);
Object model = c.newInstance();
return (T) edit(rootParamNode, name, model, annotations);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* This method is deprecated. Use this instead:
*
* public static <T extends JPABase> T edit(ParamNode rootParamNode, String name, Object o, Annotation[] annotations)
*
* @return
*/
@Deprecated
public static <T extends JPABase> T edit(Object o, String name, Map<String, String[]> params, Annotation[] annotations) {
ParamNode rootParamNode = ParamNode.convert(params);
return (T)edit( rootParamNode, name, o, annotations);
}
@SuppressWarnings("deprecation")
public static <T extends JPABase> T edit(ParamNode rootParamNode, String name, Object o, Annotation[] annotations) {
ParamNode paramNode = rootParamNode.getChild(name, true);
// #1195 - Needs to keep track of whick keys we remove so that we can restore it before
// returning from this method.
List<ParamNode.RemovedNode> removedNodesList = new ArrayList<ParamNode.RemovedNode>();
try {
BeanWrapper bw = new BeanWrapper(o.getClass());
// Start with relations
Set<Field> fields = new HashSet<Field>();
Class clazz = o.getClass();
while (!clazz.equals(Object.class)) {
Collections.addAll(fields, clazz.getDeclaredFields());
clazz = clazz.getSuperclass();
}
for (Field field : fields) {
boolean isEntity = false;
String relation = null;
boolean multiple = false;
//
if (field.isAnnotationPresent(OneToOne.class) || field.isAnnotationPresent(ManyToOne.class)) {
isEntity = true;
relation = field.getType().getName();
}
if (field.isAnnotationPresent(OneToMany.class) || field.isAnnotationPresent(ManyToMany.class)) {
Class fieldType = (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
isEntity = true;
relation = fieldType.getName();
multiple = true;
}
if (isEntity) {
ParamNode fieldParamNode = paramNode.getChild(field.getName(), true);
Class<Model> c = (Class<Model>) Play.classloader.loadClass(relation);
if (JPABase.class.isAssignableFrom(c)) {
String keyName = Model.Manager.factoryFor(c).keyName();
if (multiple && Collection.class.isAssignableFrom(field.getType())) {
Collection l = new ArrayList();
if (SortedSet.class.isAssignableFrom(field.getType())) {
l = new TreeSet();
} else if (Set.class.isAssignableFrom(field.getType())) {
l = new HashSet();
}
String[] ids = fieldParamNode.getChild(keyName, true).getValues();
if (ids != null) {
// Remove it to prevent us from finding it again later
fieldParamNode.removeChild(keyName, removedNodesList);
for (String _id : ids) {
if (_id.equals("")) {
continue;
}
Query q = JPA.em().createQuery("from " + relation + " where " + keyName + " = ?");
q.setParameter(1, Binder.directBind(rootParamNode.getOriginalKey(), annotations, _id, Model.Manager.factoryFor((Class<Model>) Play.classloader.loadClass(relation)).keyType(), null));
try {
l.add(q.getSingleResult());
} catch (NoResultException e) {
Validation.addError(name + "." + field.getName(), "validation.notFound", _id);
}
}
bw.set(field.getName(), o, l);
}
} else {
String[] ids = fieldParamNode.getChild(keyName, true).getValues();
if (ids != null && ids.length > 0 && !ids[0].equals("")) {
Query q = JPA.em().createQuery("from " + relation + " where " + keyName + " = ?");
q.setParameter(1, Binder.directBind(rootParamNode.getOriginalKey(), annotations, ids[0], Model.Manager.factoryFor((Class<Model>) Play.classloader.loadClass(relation)).keyType(), null));
try {
Object to = q.getSingleResult();
edit(paramNode, field.getName(), to, field.getAnnotations());
// Remove it to prevent us from finding it again later
paramNode.removeChild( field.getName(), removedNodesList);
bw.set(field.getName(), o, to);
} catch (NoResultException e) {
Validation.addError(fieldParamNode.getOriginalKey(), "validation.notFound", ids[0]);
// Remove only the key to prevent us from finding it again later
// This how the old impl does it..
fieldParamNode.removeChild(keyName, removedNodesList);
if (fieldParamNode.getAllChildren().size()==0) {
// remove the whole node..
paramNode.removeChild( field.getName(), removedNodesList);
}
}
} else if (ids != null && ids.length > 0 && ids[0].equals("")) {
bw.set(field.getName(), o, null);
// Remove the key to prevent us from finding it again later
fieldParamNode.removeChild(keyName, removedNodesList);
}
}
}
}
}
ParamNode beanNode = rootParamNode.getChild(name, true);
Binder.bindBean(beanNode, o, annotations);
return (T) o;
} catch (Exception e) {
throw new UnexpectedException(e);
} finally {
// restoring changes to paramNode
ParamNode.restoreRemovedChildren( removedNodesList );
}
}
/**
* This method is deprecated. Use this instead:
*
* public <T extends GenericModel> T edit(ParamNode rootParamNode, String name)
*/
@Deprecated
public <T extends GenericModel> T edit(String name, Map<String, String[]> params) {
ParamNode rootParamNode = ParamNode.convert(params);
return (T)edit(rootParamNode, name, this, null);
}
public <T extends GenericModel> T edit(ParamNode rootParamNode, String name) {
edit(rootParamNode, name, this, null);
return (T) this;
}
public boolean validateAndSave() {
if (Validation.current().valid(this).ok) {
save();
return true;
}
return false;
}
public boolean validateAndCreate() {
if (Validation.current().valid(this).ok) {
return create();
}
return false;
}
/**
* store (ie insert) the entity.
*/
public <T extends JPABase> T save() {
_save();
return (T) this;
}
/**
* store (ie insert) the entity.
*/
public boolean create() {
if (!em().contains(this)) {
_save();
return true;
}
return false;
}
/**
* Refresh the entity state.
*/
public <T extends JPABase> T refresh() {
em().refresh(this);
return (T) this;
}
/**
* Merge this object to obtain a managed entity (usefull when the object comes from the Cache).
*/
public <T extends JPABase> T merge() {
return (T) em().merge(this);
}
/**
* Delete the entity.
* @return The deleted entity.
*/
public <T extends JPABase> T delete() {
_delete();
return (T) this;
}
public static <T extends JPABase> T create(String name, Params params) {
throw new UnsupportedOperationException("Please annotate your JPA model with @javax.persistence.Entity annotation.");
}
/**
* Count entities
* @return number of entities of this class
*/
public static long count() {
throw new UnsupportedOperationException("Please annotate your JPA model with @javax.persistence.Entity annotation.");
}
/**
* Count entities with a special query.
* Example : Long moderatedPosts = Post.count("moderated", true);
* @param query HQL query or shortcut
* @param params Params to bind to the query
* @return A long
*/
public static long count(String query, Object... params) {
throw new UnsupportedOperationException("Please annotate your JPA model with @javax.persistence.Entity annotation.");
}
/**
* Find all entities of this type
*/
public static <T extends JPABase> List<T> findAll() {
throw new UnsupportedOperationException("Please annotate your JPA model with @javax.persistence.Entity annotation.");
}
/**
* Find the entity with the corresponding id.
* @param id The entity id
* @return The entity
*/
public static <T extends JPABase> T findById(Object id) {
throw new UnsupportedOperationException("Please annotate your JPA model with @javax.persistence.Entity annotation.");
}
/**
* Prepare a query to find entities.
* @param query HQL query or shortcut
* @param params Params to bind to the query
* @return A JPAQuery
*/
public static JPAQuery find(String query, Object... params) {
throw new UnsupportedOperationException("Please annotate your JPA model with @javax.persistence.Entity annotation.");
}
/**
* Prepare a query to find *all* entities.
* @return A JPAQuery
*/
public static JPAQuery all() {
throw new UnsupportedOperationException("Please annotate your JPA model with @javax.persistence.Entity annotation.");
}
/**
* Batch delete of entities
* @param query HQL query or shortcut
* @param params Params to bind to the query
* @return Number of entities deleted
*/
public static int delete(String query, Object... params) {
throw new UnsupportedOperationException("Please annotate your JPA model with @javax.persistence.Entity annotation.");
}
/**
* Delete all entities
* @return Number of entities deleted
*/
public static int deleteAll() {
throw new UnsupportedOperationException("Please annotate your JPA model with @javax.persistence.Entity annotation.");
}
/**
* A JPAQuery
*/
public static class JPAQuery {
public Query query;
public String sq;
public JPAQuery(String sq, Query query) {
this.query = query;
this.sq = sq;
}
public JPAQuery(Query query) {
this.query = query;
this.sq = query.toString();
}
public <T> T first() {
try {
List<T> results = query.setMaxResults(1).getResultList();
if (results.isEmpty()) {
return null;
}
return results.get(0);
} catch (Exception e) {
throw new JPAQueryException("Error while executing query <strong>" + sq + "</strong>", JPAQueryException.findBestCause(e));
}
}
/**
* Bind a JPQL named parameter to the current query.
*/
public JPAQuery bind(String name, Object param) {
if (param.getClass().isArray()) {
param = Arrays.asList((Object[]) param);
}
if (param instanceof Integer) {
param = ((Integer) param).longValue();
}
query.setParameter(name, param);
return this;
}
/**
* Retrieve all results of the query
* @return A list of entities
*/
public <T> List<T> fetch() {
try {
return query.getResultList();
} catch (Exception e) {
throw new JPAQueryException("Error while executing query <strong>" + sq + "</strong>", JPAQueryException.findBestCause(e));
}
}
/**
* Retrieve results of the query
* @param max Max results to fetch
* @return A list of entities
*/
public <T> List<T> fetch(int max) {
try {
query.setMaxResults(max);
return query.getResultList();
} catch (Exception e) {
throw new JPAQueryException("Error while executing query <strong>" + sq + "</strong>", JPAQueryException.findBestCause(e));
}
}
/**
* Set the position to start
* @param position Position of the first element
* @return A new query
*/
public <T> JPAQuery from(int position) {
query.setFirstResult(position);
return this;
}
/**
* Retrieve a page of result
* @param page Page number (start at 1)
* @param length (page length)
* @return a list of entities
*/
public <T> List<T> fetch(int page, int length) {
if (page < 1) {
page = 1;
}
query.setFirstResult((page - 1) * length);
query.setMaxResults(length);
try {
return query.getResultList();
} catch (Exception e) {
throw new JPAQueryException("Error while executing query <strong>" + sq + "</strong>", JPAQueryException.findBestCause(e));
}
}
}
// ----- THIS CODE IS DEPRECATED AND WILL BE REMOVED IN NEXT VERSIONs
@PostLoad
@SuppressWarnings("deprecation")
public void _setupAttachment() {
Class c = this.getClass();
while (!c.equals(Object.class)) {
for (Field field : c.getDeclaredFields()) {
if (FileAttachment.class.isAssignableFrom(field.getType())) {
try {
field.setAccessible(true);
FileAttachment attachment = (FileAttachment) field.get(this);
if (attachment != null) {
attachment.model = this;
attachment.name = field.getName();
} else {
attachment = new FileAttachment();
attachment.model = this;
attachment.name = field.getName();
field.set(this, attachment);
}
} catch (Exception ex) {
throw new UnexpectedException(ex);
}
}
}
c = c.getSuperclass();
}
}
@PostPersist
@PostUpdate
@SuppressWarnings("deprecation")
public void _saveAttachment() {
Class c = this.getClass();
while (!c.equals(Object.class)) {
for (Field field : c.getDeclaredFields()) {
if (field.getType().equals(FileAttachment.class)) {
try {
field.setAccessible(true);
FileAttachment attachment = (FileAttachment) field.get(this);
if (attachment != null) {
attachment.model = this;
attachment.name = field.getName();
attachment.save();
}
} catch (Exception ex) {
throw new UnexpectedException(ex);
}
}
}
c = c.getSuperclass();
}
}
// -----
}