/*
* Copyright 2012 Adaptrex, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.adaptrex.core.persistence.jpa;
import com.adaptrex.core.ext.data.DataConfig;
import com.adaptrex.core.ext.data.Filter;
import com.adaptrex.core.ext.data.Sorter;
import com.adaptrex.core.ext.data.Store;
import com.adaptrex.core.persistence.AdaptrexPersistence;
import com.adaptrex.core.persistence.AdaptrexSession;
import com.adaptrex.core.utilities.StringUtilities;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.*;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.Metamodel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JpaPersistence implements AdaptrexPersistence {
private EntityManagerFactory factory;
private Map<String, Class<?>> entityClassCache = new HashMap<String, Class<?>>();
private Map<String, List<String>> memberAnnotationCache = new HashMap<String, List<String>>();
private Map<String, String> fieldTypeCache = new HashMap<String, String>();
private Logger log = LoggerFactory.getLogger(JpaPersistence.class);
private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
public JpaPersistence(EntityManagerFactory factory) {
this.factory = factory;
}
@Override
public Class<?> getEntityClass(String className) {
try {
if (entityClassCache.containsKey(className)) {
return entityClassCache.get(className);
}
EntityManager em = factory.createEntityManager();
Metamodel meta = em.getMetamodel();
for (EntityType<?> et : meta.getEntities()) {
if (et.getJavaType().getName().endsWith("." + className)) {
Class<?> clazz = et.getJavaType();
entityClassCache.put(className, clazz);
return clazz;
}
}
} catch (Exception e) {
log.warn("Error", e);
}
return null;
}
/* *******************************************************************
*
* Get Single Entity
*
*/
@Override
public Object getEntity(AdaptrexSession session, String entityName, Integer id) {
EntityManager em = (EntityManager) session.getPersistence().getNativeSession();
return em.find(getEntityClass(entityName), id);
}
@Override
public Object getEntity(AdaptrexSession session, Class<?> clazz, Integer id) {
EntityManager em = (EntityManager) session.getPersistence().getNativeSession();
return em.find(clazz, id);
}
/* *******************************************************************
*
* Save Single Entity
*
*/
@Override
public Object saveEntity(AdaptrexSession session, Object entity) {
EntityManager em = (EntityManager) session.getPersistence().getNativeSession();
EntityTransaction tx = em.getTransaction();
tx.begin();
entity = em.merge(entity);
em.flush();
tx.commit();
return entity;
}
/* *******************************************************************
*
* Delete Single Entity
*
*/
@Override
public Object deleteEntity(AdaptrexSession session, Object entity) {
EntityManager em = (EntityManager) session.getPersistence().getNativeSession();
em.getTransaction().begin();
em.remove(entity);
em.getTransaction().commit();
return entity;
}
/* *******************************************************************
*
* Entity List
*
*/
@SuppressWarnings("unchecked")
@Override
public List<Object> getEntityList(AdaptrexSession session, Store store) {
DataConfig config = store.getConfig();
List<Object> list = new ArrayList<Object>();
try {
EntityManager em = (EntityManager) session.getPersistence().getNativeSession();
Map<String, Object> parameters = new HashMap<String, Object>(config.getParams());
Class<?> clazz = config.getClazz();
String className = clazz.getSimpleName();
String alias = className.toLowerCase().substring(0, 1);
/*
* Core JPQL
*/
String jpql = "SELECT " + alias + " FROM " + className + " " + alias;
/*
* Where
*/
String where = "";
if (config.getWhere() != null) {
where += config.getWhere();
}
/*
* Filter
*/
for (Filter filter : config.getFilters()) {
if (!where.isEmpty()) {
where += " AND ";
}
String val = String.valueOf(filter.getValue());
String prop = filter.getProperty();
String param = prop.replace(".", "_");
/*
* Handle between inclusive
*/
if (val.contains(">=<")) {
String[] filterParts = val.split(">=<");
where += "(" + alias + "." + prop + " BETWEEN :"
+ param + "_low AND :" + param + "_high)"
+ "";
parameters.put(param + "_low", filterParts[0]);
parameters.put(param + "_high", filterParts[1]);
/*
* Handle "OR"
*/
} else if (val.contains("||")) {
String whereOr = "";
int i = 1;
for (String orPart : val.split("\\|\\|")) {
whereOr += whereOr.isEmpty() ? "(" : " OR ";
whereOr += alias + "." + prop + " = :" + param + i;
parameters.put(param + i, orPart);
i++;
}
whereOr += ")";
where += whereOr;
} else {
String eqType = val.contains("%") ? " like " : " = ";
if (eqType.contains("like")) {
where += "lower(" + alias + "." + prop + ")" + eqType + ":" + param;
} else {
where += alias + "." + prop + eqType + ":" + param;
}
parameters.put(param, val);
}
}
/*
* Add where clause
*/
if (!where.isEmpty()) {
jpql += " WHERE " + where;
}
if (config.getSorters().size() > 0) {
String sortClause = "";
for (Sorter sorter : config.getSorters()) {
if (!sortClause.isEmpty()) {
sortClause += ",";
}
sortClause += alias + "." + sorter.getProperty() + " " + sorter.getDirection();
}
jpql += " ORDER BY " + sortClause;
}
log.debug("JPQL: " + jpql);
Query q = em.createQuery(jpql, clazz);
/*
* Add params
* TODO: This is tedius... we want to automatically determine the
* type for the field we're trying to set the parameter for
*/
for (String key : parameters.keySet()) {
Object paramObj = parameters.get(key);
Class<?> paramType = q.getParameter(key).getParameterType();
String typeName = paramType.getSimpleName().toLowerCase();
try {
if (typeName.equals("boolean")) {
String stringVal = (String) paramObj;
paramObj = stringVal.equals("1") || stringVal.equals("true") || stringVal.equals("Y");
} else if (typeName.equals("date")) {
paramObj = dateFormat.parse((String) paramObj);
} else {
Method m = paramType.getDeclaredMethod("valueOf", String.class);
paramObj = m.invoke(null, paramObj);
}
} catch (Exception e) {
}
log.debug(" Param: " + key + " = " + paramObj);
q.setParameter(key, paramObj);
}
store.setTotalCount(q.getResultList().size());
if (config.getPageSize() != null) {
q.setMaxResults(config.getPageSize());
}
if (config.getStart() != null) {
q.setFirstResult(config.getStart());
}
list = q.getResultList();
} catch (Exception e) {
log.warn("Error", e);
}
return list;
}
/* ************************************************************************************
*
* Everything below this is experimental. Possibly moved under a separate interface
*
*/
@Override
public boolean isIdField(Class<?> clazz, String fieldName) {
return hasMemberAnnotation(clazz, fieldName, "Id");
}
/*
* TODO: Do we want to move ManyToMany to a separate method?
*/
@Override
public boolean isOneToMany(Class<?> clazz, String fieldName) {
return hasMemberAnnotation(clazz, fieldName, "OneToMany")
|| hasMemberAnnotation(clazz, fieldName, "ManyToMany");
}
@Override
public boolean isManyToOne(Class<?> clazz, String fieldName) {
return hasMemberAnnotation(clazz, fieldName, "ManyToOne");
}
@Override
public Object getFieldValue(Object entity, String fieldName) {
Method getter = null;
Class<?> clazz = entity.getClass();
try {
getter = clazz.getMethod("get" + capitalize(fieldName));
} catch (Exception e) {
}
if (getter == null) {
try {
getter = clazz.getMethod("is" + capitalize(fieldName));
} catch (Exception e) {
}
}
if (getter == null) {
return null;
}
try {
return getter.invoke(entity);
} catch (Exception e) {
log.warn("Error", e);
return null;
}
}
@Override
public String getFieldType(Class<?> clazz, String fieldName) {
String fieldType = fieldTypeCache.get(clazz.getName() + "." + fieldName);
if (fieldType != null) return fieldType;
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
} catch (Exception e) {}
if (field == null) return null;
String columnType = field.getType().getSimpleName().toLowerCase();
if (columnType.equals("integer") || columnType.equals("long")) {
columnType = "int";
} else if (columnType.equals("double")) {
columnType = "float";
} else if (columnType.equals("time")) {
columnType = "time";
} else if (columnType.equals("calendar") || columnType.equals("date")) {
columnType = "date";
}
if (columnType.equals("date")) {
Temporal a = field.getAnnotation(Temporal.class);
if (a == null) {
try {
Method getter = clazz.getMethod("get" + StringUtilities.capitalize(fieldName));
if (getter != null) {
a = getter.getAnnotation(Temporal.class);
}
} catch (Exception e) {}
}
if (a != null && a.value().equals(TemporalType.TIME)) {
columnType = "time";
}
}
fieldTypeCache.put(clazz.getName() + "." + fieldName, columnType);
return columnType;
}
private boolean hasMemberAnnotation(Class<?> clazz, String member, String annotationName) {
List<String> memberAnnotations = memberAnnotationCache.get(clazz.getName() + "." + member);
if (memberAnnotations == null) {
memberAnnotations = getMemberAnnotations(clazz, member);
}
return memberAnnotations.contains(annotationName);
}
/*
* This gets a list of all annotations on a class field (or it's getter)
*/
private List<String> getMemberAnnotations(Class<?> clazz, String member) {
List<String> list = memberAnnotationCache.get(clazz.getName() + "." + member);
if (list != null) {
return list;
}
list = new ArrayList<String>();
try {
Field field = clazz.getDeclaredField(member);
if (field != null) {
for (Annotation annotation : field.getDeclaredAnnotations()) {
list.add(annotation.annotationType().getSimpleName());
}
}
} catch (Exception e) {
}
try {
Method getter = clazz.getMethod("get" + StringUtilities.capitalize(member));
if (getter != null) {
for (Annotation annotation : getter.getDeclaredAnnotations()) {
list.add(annotation.annotationType().getSimpleName());
}
}
} catch (Exception e) {
}
memberAnnotationCache.put(clazz.getName() + "." + member, list);
return list;
}
private static String capitalize(String string) {
return string.substring(0, 1).toUpperCase() + string.substring(1);
}
@Override
public Object getFactory() {
return this.factory;
}
@Override
public Object getNativeSession() {
return this.factory.createEntityManager();
}
@Override
public void closeNativeSession(Object entityManager) {
((EntityManager) entityManager).close();
}
}