/*
* 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.Config;
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.utilities.StringUtilities;
import java.io.InputStream;
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.Persistence;
import javax.persistence.Query;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.Metamodel;
import javax.xml.parsers.SAXParserFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
public class JpaPersistence implements AdaptrexPersistence {
private Map<String, Class<?>> entityClassCache = new HashMap<String, Class<?>>();
private Logger log = LoggerFactory.getLogger(JpaPersistence.class);
private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
private EntityManagerFactory entityManagerFactory;
/*
* Set up the entity manager factory. Since JPA only allows a single persistence unit
* (even if it's a compound unit), we don't need to worry about support for multiple factories
*/
public JpaPersistence() {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
InputStream inputStream = cl.getResourceAsStream("META-INF/persistence.xml");
if (inputStream == null) {
log.warn("Could not load default entity manager factory: persistence.xml could not be found");
return;
}
try {
SAXParserFactory.newInstance().newSAXParser().parse(inputStream, new DefaultHandler() {
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
if (!qName.equalsIgnoreCase("persistence-unit") || entityManagerFactory != null) {
return;
}
String persistenceUnitName = attributes.getValue("name");
log.debug("Creating JPA Persistence Unit: " + persistenceUnitName);
entityManagerFactory = Persistence.createEntityManagerFactory(persistenceUnitName);
}
});
} catch (Exception e) {
log.warn("Error", e);
}
}
@Override
public void shutdown() {
entityManagerFactory.close();
}
@Override
public Class<?> getEntityClass(String className) {
try {
if (entityClassCache.containsKey(className)) {
return entityClassCache.get(className);
}
EntityManager em = entityManagerFactory.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;
}
@Override
public EntityManager getEntityManager() {
try {
return entityManagerFactory.createEntityManager();
} catch (Exception e) {
log.warn("Error", e);
return null;
}
}
/* *******************************************************************
*
* Get Single Entity
*
*/
@Override
public Object getEntity(String entityName, Integer id) {
EntityManager em = getEntityManager();
return em.find(getEntityClass(entityName), id);
}
@Override
public Object getEntity(Class<?> clazz, Integer id) {
EntityManager em = getEntityManager();
return em.find(clazz, id);
}
/* *******************************************************************
*
* Save Single Entity
*
*/
@Override
public Object saveEntity(Object entity) {
EntityManager em = getEntityManager();
em.getTransaction().begin();
entity = em.merge(entity);
em.flush();
em.getTransaction().commit();
return entity;
}
/* *******************************************************************
*
* Delete Single Entity
*
*/
@Override
public Object deleteEntity(String entityName, Integer id) {
EntityManager em = getEntityManager();
Object entity= em.find(getEntityClass(entityName), id);
em.getTransaction().begin();
em.remove(entity);
em.getTransaction().commit();
return entity;
}
/* *******************************************************************
*
* Entity List
*
*/
@SuppressWarnings("unchecked")
@Override
public List<Object> getEntityList(Store store) {
Config config = store.getConfig();
List<Object> list = new ArrayList<Object>();
EntityManager em = entityManagerFactory.createEntityManager();
Map<String,Object> parameters = new HashMap<String,Object>(config.getParams());
try {
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();
/*
* Handle between inclusive
*/
if (val.contains(">=<")) {
String[] filterParts = val.split(">=<");
where += "(" + alias + "." + filter.getProperty() + " BETWEEN :" +
prop + "_low AND :" + prop + "_high)" +
"";
parameters.put(prop + "_low", filterParts[0]);
parameters.put(prop + "_high", filterParts[1]);
/*
* Handle "OR"
*/
} else if (String.valueOf(filter.getValue()).contains("||")) {
String whereOr = "";
int i = 1;
for (String orPart : String.valueOf(filter.getValue()).split("\\|\\|")) {
whereOr += whereOr.isEmpty() ? "(" : " OR ";
whereOr += alias + "." + filter.getProperty() + " = :" + filter.getProperty() + i;
parameters.put(filter.getProperty() + i, orPart);
i++;
}
whereOr += ")";
where += whereOr;
} else {
String eqType = String.valueOf(filter.getValue()).contains("%") ? " like " : " = ";
if (eqType.contains("like")) {
where += "lower(" + alias + "." + filter.getProperty() + ")" + eqType + ":" + filter.getProperty();
} else {
where += alias + "." + filter.getProperty() + eqType + ":" + filter.getProperty();
}
parameters.put(filter.getProperty(), filter.getValue());
}
}
/*
* 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);
}
em.close();
return list;
}
/* ************************************************************************************
*
* Everything below this is experimental. Possibly moved under a separate interface
*
*/
@Override
public boolean isIdField(Class<?> clazz, String fieldName) {
return getMemberAnnotation(clazz, fieldName, "Id") != null;
}
/*
* TODO: Do we want to move ManyToMany to a separate method?
*/
@Override
public boolean isOneToMany(Class<?> clazz, String fieldName) {
return getMemberAnnotation(clazz, fieldName, "OneToMany") != null
|| getMemberAnnotation(clazz, fieldName, "ManyToMany") != null;
}
@Override
public boolean isManyToOne(Class<?> clazz, String fieldName) {
return getMemberAnnotation(clazz, fieldName, "ManyToOne") != null;
}
@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;
}
}
/*
* This gets a list of all annotations on a class field (or it's getter)
*/
private List<Annotation> getMemberAnnotations(Class<?> clazz, String member) {
Annotation[] annotations;
try {
Field field = clazz.getDeclaredField(StringUtilities.uncapitalize(member));
if (field != null) {
annotations = field.getDeclaredAnnotations();
if (annotations.length > 0) {
return Arrays.asList(annotations);
}
}
} catch (Exception e) {}
try {
Method getter = clazz.getMethod("get" + member);
if (getter == null) getter = clazz.getMethod("is" + member);
if (getter != null) {
annotations = getter.getDeclaredAnnotations();
if (annotations.length > 0) {
return Arrays.asList(annotations);
}
}
} catch (Exception e) {}
return new ArrayList<Annotation>();
}
/*
* This returns a specific annotation on a class field (or it's getter)
*/
private Annotation getMemberAnnotation(Class<?> clazz, String member, String annotationName) {
for (Annotation annotation : getMemberAnnotations(clazz, member)) {
if (annotation.annotationType().getSimpleName().equals(annotationName)) {
return annotation;
}
}
return null;
}
private static String capitalize(String string) {
return string.substring(0,1).toUpperCase() + string.substring(1);
}
}