/*
* 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 java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.Metamodel;
import javax.xml.parsers.SAXParserFactory;
import org.apache.log4j.Logger;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
import com.adaptrex.core.ext.ExtConfig;
import com.adaptrex.core.ext.ExtTypeFormatter;
import com.adaptrex.core.ext.ModelInstance;
import com.adaptrex.core.persistence.PersistenceTools;
import com.adaptrex.core.persistence.api.AdaptrexEntityType;
import com.adaptrex.core.persistence.api.AdaptrexFieldType;
import com.adaptrex.core.persistence.api.AdaptrexPersistenceManager;
import com.adaptrex.core.persistence.api.AdaptrexStoreData;
import com.adaptrex.core.security.SecurityModel;
public class JPAPersistenceManager implements AdaptrexPersistenceManager {
private static Logger log = Logger.getLogger(JPAPersistenceManager.class);
private EntityManagerFactory factory;
private String name;
Metamodel metaModel;
/*
* Create an persistence manager with a specific entity manager factory
*/
public JPAPersistenceManager(final String factoryName) throws IOException {
if (factoryName == null) {
List<InputStream> inputStreams;
try {
inputStreams = loadResources(
"META-INF/persistence.xml",
Thread.currentThread().getContextClassLoader()
);
} catch (IOException e) {
log.warn("Could not find persistence.xml");
return;
}
final int numberOfFiles = inputStreams.size();
// if (inputStreams.size() > 1) {
// log.warn("Multiple persistence.xml descriptors found but no default has been configured");
// return;
// }
try {
DefaultHandler defaultHandler = new DefaultHandler() {
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
if (qName.equalsIgnoreCase("persistence-unit")) {
if (numberOfFiles == 1) {
name = attributes.getValue("name");
} else {
if (name.equals(factoryName)) {
name = attributes.getValue("name");
}
}
}
}
};
for (InputStream inputStream : inputStreams) {
SAXParserFactory.newInstance().newSAXParser().parse(inputStream, defaultHandler);
}
} catch (Exception e) {
log.warn("Could not parse persistence.xml");
e.printStackTrace();
}
} else {
this.name = factoryName;
}
this.factory = Persistence.createEntityManagerFactory(this.name);
this.metaModel = this.factory.getMetamodel();
try {
initialize();
} catch (Exception e) {
throw new RuntimeException(e);
}
log.info("Creating JPAPersistenceManager for Persistence Unit " + this.name);
}
public JPAPersistenceManager(EntityManagerFactory emf) {
this(emf, AdaptrexPersistenceManager.DEFAULT_NAME);
}
public JPAPersistenceManager(EntityManagerFactory emf, String factoryName) {
this.factory = emf;
this.name = factoryName;
this.metaModel = factory.getMetamodel();
try {
initialize();
} catch (Exception e) {
throw new RuntimeException(e);
}
log.info("Creating JPAPersistenceManager for Persistence Unit " + this.name);
}
private Map<String,JPAEntityType> adaptrexEntities;
private void initialize() throws Exception {
SecurityModel securityModel = PersistenceTools.getSecurityModel();
adaptrexEntities = new HashMap<String,JPAEntityType>();
for (EntityType<?> entityType : metaModel.getEntities()) {
JPAEntityType jpaEntity = new JPAEntityType(metaModel, entityType, securityModel);
adaptrexEntities.put(jpaEntity.getName(), jpaEntity);
}
}
@Override
public AdaptrexEntityType getAdaptrexEntity(String name) {
return adaptrexEntities.get(name);
}
@Override
public String getName() {
return this.name;
}
/**
* Returns an EntityManager for the EntityManagerFactory this persistence manager
* represents.
*/
@Override
public Object getNativeSession() {
return this.factory.createEntityManager();
}
EntityManager getEntityManager() {
return this.factory.createEntityManager();
}
/**
* Shuts down the EntityManagerFactory this persistence manager uses to generate its
* ObjectContexts. This should be called to clean up Cayenne when the container
* is shut down.
*/
@Override
public void shutdown() {
log.info("Shutting down EntityManagerFactory for " + this.name);
this.factory.close();
}
/**
* Get a persistent entity based on an id for a specific class.
*/
@Override
public Object getEntity(Class<?> clazz, Object id) {
EntityManager em = getEntityManager();
try {
return em.find(clazz, id);
} catch (Exception e) {
return null;
} finally {
closeEM(em);
}
}
/**
* Get a persistent entity based on a key value pair for a specific class.
*/
@Override
public Object getEntity(Class<?> clazz, String key, Object value) {
EntityManager em = getEntityManager();
try {
return this.getEntity(em, clazz, key, value);
} catch (Exception e) {
return null;
} finally {
closeEM(em);
}
}
/**
* Get a persistent entity based on an id for a specific class. This does
* not automatically close the EntityManager so it needs to be closed by
* some other method.
*/
private Object getEntity(Object session, Class<?> clazz, Object id) {
Integer integerId = (id instanceof String) ? Integer.valueOf((String) id) : (Integer) id;
try {
return ((EntityManager) session).find(clazz, integerId);
} catch (Exception e) {
return null;
}
}
/**
* Get a persistent entity based on a key value pair for a specific class.
* This does not automatically close the EntityManager so it needs to be
* closed by some other method.
*/
private Object getEntity(Object session, Class<?> clazz, String key, Object value) {
EntityManager em = (EntityManager) session;
try {
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<?> criteriaQuery = criteriaBuilder.createQuery(clazz);
criteriaQuery.where(criteriaBuilder.equal(criteriaQuery.from(clazz).get(key), value));
List<?> result = em.createQuery(criteriaQuery).getResultList();
return result.size() == 0 ? null : result.get(0);
} catch (Exception e) {
return null;
}
}
@Override
public AdaptrexStoreData getStoreData(ExtConfig extConfig) {
return new JPAStoreData(extConfig);
}
@Override
public ModelInstance getModelInstance(ExtConfig extConfig, Object id) {
EntityManager em = getEntityManager();
try {
Object entity = this.getEntity(em, extConfig.getEntityClass(), id);
return new ModelInstance(extConfig, entity);
} catch (Exception e) {} finally {
closeEM(em);
}
return null;
}
@Override
public ModelInstance getModelInstance(ExtConfig extConfig, String key, Object value) {
EntityManager em = getEntityManager();
try {
Object entity = this.getEntity(em, extConfig.getEntityClass(), key, value);
return new ModelInstance(extConfig, entity);
} catch (Exception e) {} finally {
closeEM(em);
}
return null;
}
@Override
public ModelInstance createModelInstance(ExtConfig extConfig, Map<String, Object> data) {
Object id = null;
EntityManager em = getEntityManager();
try {
AdaptrexEntityType adaptrexEntity = getAdaptrexEntity(extConfig.getClassName());
Class<?> clazz = extConfig.getEntityClass();
Object entity = clazz.newInstance();
this.updateEntity(em, clazz, entity, data, true);
id = adaptrexEntity.getEntityIdFrom(entity);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
closeEM(em);
}
/*
* Entity doesn't have associations properly loaded in the above entity
* manager. Why? Just grab a new model instance in a new entity manager
*/
return getModelInstance(extConfig, id);
}
@Override
public ModelInstance updateModelInstance(ExtConfig extConfig, Object id, Map<String, Object> data) throws Exception {
return this.updateModelInstance(extConfig, id, null, null, data);
}
@Override
public ModelInstance updateModelInstance(
ExtConfig extConfig, String key, Object value, Map<String, Object> data) throws Exception {
return this.updateModelInstance(extConfig, null, key, value, data);
}
private ModelInstance updateModelInstance(
ExtConfig extConfig, Object id, String key, Object value, Map<String, Object> data) throws Exception {
EntityManager em = getEntityManager();
try {
AdaptrexEntityType adaptrexEntity = getAdaptrexEntity(extConfig.getClassName());
Class<?> clazz = extConfig.getEntityClass();
Object entity = id != null ? this.getEntity(em, clazz, id) : this.getEntity(em, clazz, key, value);
this.updateEntity(em, clazz, entity, data, false);
id = id != null ? id : adaptrexEntity.getEntityIdFrom(entity);
} catch (Exception e) {
throw new Exception(e);
} finally {
closeEM(em);
}
/*
* Entity doesn't have associations properly loaded in the above entity
* manager. Why? Just grab a new model instance in a new entity manager
*/
return getModelInstance(extConfig, id);
}
@Override
public ModelInstance deleteModelInstance(ExtConfig extConfig, Object id) {
return this.deleteModelInstance(extConfig, id, null, null);
}
@Override
public ModelInstance deleteModelInstance(ExtConfig extConfig, String key, Object value) {
return this.deleteModelInstance(extConfig, null, key, value);
}
private ModelInstance deleteModelInstance(ExtConfig extConfig, Object id, String key, Object value) {
EntityManager em = getEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
Class<?> clazz = extConfig.getEntityClass();
Object entity = id != null ? this.getEntity(em, clazz, id) : this.getEntity(em, clazz, key, value);
ModelInstance model = new ModelInstance(extConfig, entity);
this.evictAssociated(em, entity);
em.remove(em.merge(entity));
tx.commit();
return model;
} catch (Exception e) {
tx.rollback();
log.warn("Error deleting model instance");
} finally {
closeEM(em);
}
return null;
}
/*
* Ripped from: http://goo.gl/CYwbl
*/
private static List<InputStream> loadResources(final String name,
final ClassLoader classLoader) throws IOException {
final List<InputStream> list = new ArrayList<InputStream>();
final Enumeration<URL> systemResources = classLoader.getResources(name);
while (systemResources.hasMoreElements()) {
list.add(systemResources.nextElement().openStream());
}
return list;
}
private void updateEntity(EntityManager em, Class<?> clazz, Object entity, Map<String,Object> data, boolean isNew) throws Exception {
AdaptrexEntityType adaptrexEntity = getAdaptrexEntity(clazz.getSimpleName());
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
for (String fieldName : data.keySet()) {
if (fieldName.equals(AdaptrexEntityType.ENTITY_ID_NAME)) continue;
Object newValue = data.get(fieldName);
AdaptrexFieldType adaptrexField = adaptrexEntity.getField(fieldName);
if (adaptrexField != null) {
adaptrexField.setValueFor(entity, ExtTypeFormatter.parse(newValue, adaptrexField.getFieldType()));
continue;
}
JPAAssociationType adaptrexAssociation = (JPAAssociationType) adaptrexEntity.getAssociationByIdField(fieldName);
if (adaptrexAssociation != null) {
adaptrexAssociation.setById(em, entity, newValue);
continue;
}
JPACollectionType adaptrexCollection = (JPACollectionType) adaptrexEntity.getCollectionByIdsField(fieldName);
if (adaptrexCollection != null) {
adaptrexCollection.setByIds(em, entity, newValue);
}
}
if (!em.contains(entity)) em.persist(entity);
tx.commit();
} catch (Exception e) {
tx.rollback();
throw new Exception(e);
}
}
private void evictAssociated(EntityManager em, Object entity) {
// Class<?> clazz = entity.getClass();
// for (Field field : clazz.getDeclaredFields()) {
//
// try {
// Method getter = clazz.getDeclaredMethod("get" + StringUtilities.capitalize(field.getName()));
//
// /*
// * Evict ManyToOne
// */
// if (this.isManyToOne(clazz, field.getName())) {
// Object associated = getter.invoke(entity);
// if (associated == null) continue;
// Class<?> assocClass = associated.getClass();
// this.factory.getCache().evict(
// assocClass, assocClass.getDeclaredMethod("getId").invoke(associated));
// }
//
// /*
// * Evict OneToMany or ManyToMany
// */
// if (this.isOneToMany(clazz, field.getName()) || this.isManyToMany(clazz, field.getName())) {
// Set<?> associatedSet = (Set<?>) getter.invoke(entity);
// Class<?> assocClass = null;
// for (Object associated : associatedSet) {
// if (assocClass == null) assocClass = associated.getClass();
// this.factory.getCache().evict(
// assocClass, assocClass.getDeclaredMethod("getId").invoke(associated));
// }
// }
//
// } catch (Exception e) {}
// }
}
private void closeEM(EntityManager em) {
if (em != null && em.isOpen()) em.close();
}
}