/*
* Copyright 2012 The Solmix Project
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.gnu.org/licenses/
* or see the FSF site: http://www.fsf.org.
*/
package org.solmix.jpa;
import static org.solmix.commons.util.DataUtils.isNotNullAndEmpty;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Id;
import javax.persistence.Query;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.solmix.api.call.DSCall;
import org.solmix.api.call.DSCallCompleteCallback;
import org.solmix.api.datasource.DSRequest;
import org.solmix.api.datasource.DSRequestData;
import org.solmix.api.datasource.DSResponse;
import org.solmix.api.datasource.DSResponse.Status;
import org.solmix.api.datasource.DataSource;
import org.solmix.api.datasource.DataSourceData;
import org.solmix.api.datasource.DataSourceGenerator;
import org.solmix.api.exception.SlxException;
import org.solmix.api.jaxb.Efield;
import org.solmix.api.jaxb.Eoperation;
import org.solmix.api.jaxb.EserverType;
import org.solmix.api.jaxb.Tfield;
import org.solmix.api.jaxb.ToperationBinding;
import org.solmix.api.types.Texception;
import org.solmix.api.types.Tmodule;
import org.solmix.commons.collections.DataTypeMap;
import org.solmix.commons.util.Assert;
import org.solmix.commons.util.DataUtils;
import org.solmix.fmk.base.Reflection;
import org.solmix.fmk.datasource.BasicDataSource;
import org.solmix.fmk.datasource.BasicGenerator;
import org.solmix.fmk.datasource.DSResponseImpl;
import org.solmix.fmk.datasource.DefaultDataSourceManager;
import org.solmix.fmk.util.DataTools;
import org.solmix.runtime.SystemContext;
/**
* JPA datasource.
*
* @author solmix.f@gmail.com
* @version $Id$ 2011-6-6
*/
public class JPADataSource extends BasicDataSource implements DataSource, DSCallCompleteCallback
{
private final static Logger log = LoggerFactory.getLogger(JPADataSource.class.getName());
public static final String SERVICE_PID = "org.solmix.modules.jpa";
String entityName = null;
Class<?> entityClass = null;
private boolean useQualifiedClassName;
private boolean shouldRollBackTransaction;
protected EntityManagerHolder holder;
private int batchInsertSize = 100;
private EntityManager entityManager;
private Object transaction;
private EntityManagerFactoryProvider entityManagerFactoryProvider;
private String persistenceUnit;
private enum QueryType
{
ENTITY , NATIVE_QUERY , OTHER;
}
/**
* This Constructor used by inject,such as spring guice.Coding instance call
* {@link org.solmix.jpa.JPADataSource#instance(DataSourceData)}
*
* @throws SlxException
*/
public JPADataSource() throws SlxException
{
}
/**
* This construction is always called by injector,such as spring, blueprint etc. Please use
* {@link org.solmix.jpa.JPADataSource#instance(DataSourceData) instance } to init datasource.
*
* @param sc
*/
public JPADataSource(final SystemContext sc)
{
setSystemContext(sc);
}
/**
* {@inheritDoc}
*
* @see org.solmix.fmk.datasource.BasicDataSource#getServerType()
*/
@Override
public String getServerType() {
return EserverType.JPA.value();
}
/**
* @return the entityManagerFactoryProvider
*/
public EntityManagerFactoryProvider getEntityManagerFactoryProvider() {
return entityManagerFactoryProvider;
}
/**
* @param entityManagerFactoryProvider the entityManagerFactoryProvider to set
*/
public void setEntityManagerFactoryProvider(EntityManagerFactoryProvider entityManagerFactoryProvider) {
this.entityManagerFactoryProvider = entityManagerFactoryProvider;
}
@Override
public void init(DataSourceData data) throws SlxException {
super.init(data);
persistenceUnit = data.getTdataSource() == null ? null : data.getTdataSource().getPersistenceUnit();
if (persistenceUnit == null)
persistenceUnit = getConfig().getString(JpaCM.P_DEFAULT_UNIT, "default");
Assert.isNotNull(persistenceUnit, "Jpa persistenceUnit must be configured");
String entityBean = getContext().getTdataSource().getBean();
if (entityBean != null) {
try {
entityClass = BasicGenerator.loadClass(entityBean);
Entity e = entityClass.getAnnotation(Entity.class);
entityName = e == null ? null : e.name();
if (DataUtils.isNullOrEmpty(entityName))
entityName = entityClass.getName().substring(entityClass.getName().lastIndexOf(".") + 1);
entityName=new StringBuilder().append('_').append(entityName).toString();
} catch (Exception e) {
throw new SlxException(Tmodule.JPA, Texception.NO_FOUND, e);
}
}
}
protected EntityManagerFactory getEmf(DataSourceData data) throws SlxException {
DataTypeMap pconfig = getConfig().getSubtree(new StringBuilder().append(JpaCM.P_UNIT_PREFIX).append(persistenceUnit).toString());
if (pconfig.isEmpty()) {
return this.entityManagerFactoryProvider.createEntityManagerFactory(persistenceUnit);
} else {
return this.entityManagerFactoryProvider.createEntityManagerFactory(persistenceUnit, pconfig);
}
}
public void destroy() {
if (log.isTraceEnabled())
log.trace("JPADataSource:" + this.getContext().getName() + " destroying!");
}
/**
* @return the entityManager
* @throws SlxException
*/
public synchronized EntityManager getEntityManager() throws SlxException {
if (entityManager == null || !entityManager.isOpen()) {
entityManager = JPATransaction.getEntityManager(getEmf(data));
}
return entityManager;
}
/**
* @param entityManager the entityManager to set
*/
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
/**
* @return the batchInsertSize
*/
public int getBatchInsertSize() {
return batchInsertSize;
}
/**
* @param batchInsertSize the batchInsertSize to set
*/
public void setBatchInsertSize(int batchInsertSize) {
this.batchInsertSize = batchInsertSize;
}
@Override
public DataSource instance(DataSourceData data) throws SlxException {
JPADataSource ds = new JPADataSource(sc);
if (this.getEntityManagerFactoryProvider() != null) {
ds.setEntityManagerFactoryProvider(getEntityManagerFactoryProvider());
}
ds.init(data);
return ds;
}
/* protected void adaptProvider(DataSourceData context) throws SlxException {
String unit = context == null ? null : context.getTdataSource() == null ? null : context.getTdataSource().getPersistenceUnit();
if (unit == null) {
unit = getConfig().getString("default.persistenceUnit", "default");
}
Object[] objs = ServiceUtil.getOSGIServices(EntityManagerFactory.class.getName(), "(osgi.unit.name=" + unit + ")");
EntityManagerFactory factory = objs == null ? null : objs.length >= 1 ? (EntityManagerFactory) objs[0] : null;
}
*/
@Override
public DSResponse execute(DSRequest req) throws SlxException {
req.registerFreeResourcesHandler(this);
shouldRollBackTransaction = false;
holder = null;
Eoperation _opType = req.getContext().getOperationType();
DSResponse __return = null;
if (isJpaOperation(_opType)) {
DSResponse validationFailure = validateDSRequest(req);
if (validationFailure != null) {
return validationFailure;
}
//when used FetchType.LAZY,the transaction must be commit after data send to client.
if(DataUtils.booleanValue(req.getContext().getIsClientRequest())&&DataTools.isFetch(_opType)){
req.setFreeOnExecute(false);
}
// if DSRequest not have a DataSource with it,use this by default.
if (req.getDataSource() == null && req.getDataSourceName() == null) {
req.setDataSource(this);
}
req.setRequestStarted(true);
Object dsObject = null;
Object datasources = req.getContext().getDataSourceNames();
// may be have other datasource.if just one,used as SQL datasource.
if (datasources != null && (datasources instanceof List<?> && ((List<?>) datasources).size() > 1)) {
dsObject = datasources;
} else {
dsObject = this;
}
__return = executeJpaDataSource(req, dsObject);
} else {
__return = super.execute(req);
}
return __return;
}
private static JPADataSource[] getDataSources(List<?> list) throws SlxException {
List<JPADataSource> _return = new ArrayList<JPADataSource>();
if (list == null)
return null;
for (Object ds : list) {
if (ds instanceof JPADataSource) {
_return.add((JPADataSource) ds);
} else {
DataSource datasource = DefaultDataSourceManager.getDataSource((String) ds);
if (datasource instanceof JPADataSource) {
_return.add((JPADataSource) datasource);
} else {
log.warn("the datasource [" + ds.toString() + "] cannot processed by JPA DataSource.");
}
}
}
return _return.toArray(new JPADataSource[_return.size()]);
}
/**
* @param _opType
* @return
*/
private boolean isJpaOperation(Eoperation operationType) {
return DataTools.isFetch(operationType) || DataTools.isAdd(operationType) || DataTools.isRemove(operationType)
|| DataTools.isUpdate(operationType) || DataTools.isReplace(operationType);
}
@Override
protected String getPID() {
return SERVICE_PID;
}
@Override
public DSResponse executeRemove(DSRequest req) throws SlxException {
DSResponse __return = new DSResponseImpl(req,Status.STATUS_SUCCESS);
String pk = data.getPrimaryKey();
Tfield pkField = data.getField(pk);
if (pkField == null) {
log.error("field:[" + pk + "] is not defined in datasource");
throw new SlxException(Tmodule.JPA, Texception.DS_UPDATE_WITHOUT_PK, "field:[" + pk + "] is not defined in datasource");
}
Serializable id = (Serializable) req.getContext().getFieldValue(pk);
String xPath = pkField.getValueXPath();
if (xPath != null)
pk = xPath.replace('/', '.');
Object p = null;
try {
p = DataUtils.castValue(id, DataUtils.getPropertyType(entityClass, pk));
} catch (IntrospectionException e) {
e.printStackTrace();
}
Object record = entityManager.find(entityClass, p);
entityManager.remove(record);
__return.setRawData(req.getContext().getCriteria());
increaseOpCount();
return __return;
}
@Override
public DSResponse executeUpdate(DSRequest req) throws SlxException {
DSResponse __return = new DSResponseImpl(req,Status.STATUS_SUCCESS);
String pk = data.getPrimaryKey();
Tfield pkField = data.getField(pk);
if (pkField == null) {
log.error("field:[" + pk + "] is not defined in datasource");
throw new SlxException(Tmodule.JPA, Texception.DS_UPDATE_WITHOUT_PK, "field:[" + pk + "] is not defined in datasource");
}
Serializable id = (Serializable) req.getContext().getFieldValue(pk);
String xPath = pkField.getValueXPath();
if (xPath != null)
pk = xPath.replace('/', '.');
Object p = null;
try {
p = DataUtils.castValue(id, DataUtils.getPropertyType(entityClass, pk));
} catch (IntrospectionException e) {
e.printStackTrace();
}
Object record = entityManager.find(entityClass, p);
try {
DataUtils.setProperties(req.getContext().getValues(), record);
} catch (Exception e) {
e.printStackTrace();
}
__return.setRawData(record);
increaseOpCount();
return __return;
}
/**
* Jpa Transaction Object Key is <code>_slx_jpa_entityManager_key</code>
*/
@Override
public String getTransactionObjectKey() throws SlxException {
return (new StringBuilder()).append(JPATransaction.ENTITYMANAGER_ATTR_KEY).append("_").append(persistenceUnit).toString();
}
@Override
public void onSuccess(DSCall rpcmanager) throws SlxException {
Object obj = rpcmanager.getAttribute(getTransactionObjectKey());
if (obj == null) {
log.warn("Transaction Object is null !");
return;
}
if (!(obj instanceof EntityManagerHolder)) {
throw new SlxException(Tmodule.JPA, Texception.OBJECT_TYPE_NOT_ADAPTED,
"rpc manager does not hold instance of EntityManagerHolder for jpa datasource");
}
EntityManagerHolder holder = (EntityManagerHolder) obj;
try {
log.debug("committing transaction for" + holder.getOpCount() + "queued operation(s)");
JPATransaction.commitTansaction(holder.getTransaction());
} catch (Exception e) {
JPATransaction.rollbackTransaction(transaction);
log.error("Failed to commit transaction,Rolling back", e);
}
try {
JPATransaction.returnEntityManager(holder.getEntityManager());
} catch (Exception e) {
}
}
@Override
public void onFailure(DSCall rpcmanager, boolean flag) throws SlxException {
Object obj = rpcmanager.getAttribute(getTransactionObjectKey());
if (obj == null) {
log.warn("Transaction Object is null !");
return;
}
if (!(obj instanceof EntityManagerHolder)) {
throw new SlxException(Tmodule.JPA, Texception.OBJECT_TYPE_NOT_ADAPTED,
"rpc manager does not hold instance of EMHooker for jpa datasource");
}
EntityManagerHolder holder = (EntityManagerHolder) obj;
try {
if (log.isTraceEnabled())
log.trace("rolling back transaction for" + holder.getOpCount() + "queued operation(s)");
JPATransaction.rollbackTransaction(holder.getTransaction());
JPATransaction.returnEntityManager(holder.getEntityManager());
} catch (Exception e) {
throw new SlxException(Tmodule.JPA, Texception.JPA_JPAEXCEPTION, e);
}
}
@Override
public void freeResources() {
if (holder == null) {
if (shouldRollBackTransaction) {
if (log.isTraceEnabled())
log.trace("rolling back transaction!");
JPATransaction.rollbackTransaction(transaction);
} else {
try {
if (log.isTraceEnabled())
log.trace("committing transaction");
JPATransaction.commitTansaction(transaction);
} catch (Exception ex) {
JPATransaction.rollbackTransaction(transaction);
log.error("Failed to commit transaction,Rolling back", ex);
}
}
try {
JPATransaction.returnEntityManager(entityManager);
} catch (Exception ignore) {
}
}
super.freeResources();
}
/**
* @return the dataSourceGenerator
*/
@Override
public synchronized DataSourceGenerator getDataSourceGenerator() {
if (dataSourceGenerator == null)
dataSourceGenerator = new JPADataSourceGenerator(this);
return dataSourceGenerator;
}
/**
* @param dataSourceGenerator the dataSourceGenerator to set
*/
@Override
public void setDataSourceGenerator(DataSourceGenerator dataSourceGenerator) {
this.dataSourceGenerator = dataSourceGenerator;
}
public void increaseOpCount() {
if (holder != null)
holder.increaseOpCount();
}
public void markTrnsactionForRollBack(DSResponse resp) {
this.shouldRollBackTransaction = true;
if (resp != null)
resp.setStatus(Status.STATUS_FAILURE);
log.debug("mark transaction for roll back");
}
/**
* @return the useQualifiedClassName
*/
public boolean isUseQualifiedClassName() {
return useQualifiedClassName;
}
/**
* @param useQualifiedClassName the useQualifiedClassName to set
*/
public void setUseQualifiedClassName(boolean useQualifiedClassName) {
this.useQualifiedClassName = useQualifiedClassName;
}
/**
* Execute Sql operation,default is Fetch/Add/Replace/Update/Remove
*
* @param req
* @param dsObject
* @return
* @throws SlxException
*/
private DSResponse executeJpaDataSource(DSRequest req, Object dsObject) throws SlxException {
JPADataSource[] datasources;
if (dsObject instanceof JPADataSource) {
datasources = new JPADataSource[1];
datasources[0] = (JPADataSource) dsObject;
} else if ((dsObject instanceof String)) {
datasources = getDataSources(DataUtils.makeListIfSingle(dsObject));
} else if (dsObject instanceof List<?>) {
datasources = getDataSources((List<?>) dsObject);
} else {
throw new SlxException(Tmodule.DATASOURCE, Texception.DS_DSCONFIG_ERROR,
"in the app operation config, datasource must be set to a string or list");
}
if (req.getDSCall() != null && this.shouldAutoJoinTransaction(req)) {
log.debug("Auto get transaction object!");
Object obj = this.getTransactionObject(req);
if (!(holder instanceof EntityManagerHolder)) {
if (log.isWarnEnabled())
log.warn("JPA DataSource transaction holer should be a org.solmix.jpa.EntityManagerHolder instance,but is"
+ obj.getClass().getName() + " Assume the transaction object is invalid and set it to null");
holder = null;
} else {
holder = (EntityManagerHolder) obj;
}
if (holder == null) {
if (shouldAutoStartTransaction(req, false)) {
try {
transaction = JPATransaction.getTransaction(getEntityManager());
} catch (Exception e) {
log.error("Unexpected exception while initial entityManager", e);
}
log.debug("Creating EntityManager, starting transaction and setting it to DSCall.");
holder = new EntityManagerHolder(this, entityManager, transaction);
req.getDSCall().setAttribute(getTransactionObjectKey(), holder);
req.getDSCall().registerCallback(this);
} else {
try {
transaction = JPATransaction.getTransaction(getEntityManager());
} catch (Exception e) {
log.error("Unexpected exception while initial entityManager", e);
}
}
} else {
entityManager = holder.getEntityManager();
transaction = holder.getTransaction();
}
req.setJoinTransaction(true);
} else {
try {
transaction = JPATransaction.getTransaction(getEntityManager());
} catch (Exception e) {
log.error("Unexpected exception while initial entityManager", e);
throw new SlxException(Tmodule.JPA,Texception.JPA_NO_EMF,e);
}
}
DSRequestData __requestCX = req.getContext();
Eoperation _req = __requestCX.getOperationType();
DSResponse __return = null;
switch (_req) {
case ADD:
__return = addEntity(req, datasources);
break;
case FETCH:
__return = fetchEntity(req, datasources);
break;
case REMOVE:
__return = removeEntity(req, datasources);
break;
case UPDATE:
__return = updateEntity(req, datasources);
break;
default:
break;
}
return __return;
}
private DSResponse updateEntity(DSRequest req, JPADataSource[] datasources) throws SlxException {
JPADataSource __firstDS = datasources[0];
DSRequestData __requestCX = req.getContext();
DSResponse __return = new DSResponseImpl(req,Status.STATUS_SUCCESS);
Object criteria = req.getContext().getRawValues();
int batchsize = __requestCX.getBatchSize();
// batch size.
batchsize = batchsize < 100 ? 100 : batchsize;
if (isEntityPresent(criteria)) {
List<?> records = DataUtils.makeListIfSingle(criteria);
Object result = updateBean(batchsize, records);
__return.setRawData(result);
return __return;
}
// check jpql configured
ToperationBinding _op = __firstDS.getContext().getOperationBinding(req);
if (_op != null && _op.getQueryClauses() != null && _op.getQueryClauses().getCustomQL() != null) {
// velocity exp
return __return;
}
if (isEntityClass(entityClass)) {
List<?> records = req.getContext().getValueSets();
List<Object> beans = new ArrayList<Object>();
for (Object o : records) {
Object bean = instance(entityClass);
try {
DataUtils.setProperties((Map<?,?>) o, bean, false);
} catch (Exception e) {
String __msg = "invoke bean class:[" + bean.getClass().getName() + "] exception";
throw new SlxException(Tmodule.JPA, Texception.INVOKE_EXCEPTION, __msg);
}
beans.add(bean);
}
Object result = updateBean(batchsize, beans);
__return.setRawData(result);
return __return;
} else {
throw new SlxException(Tmodule.JPA, Texception.JPA_NO_ENTITY, "JPA DataSource no configured Entity bean");
}
}
private DSResponse removeEntity(DSRequest req, JPADataSource[] datasources) throws SlxException {
JPADataSource __firstDS = datasources[0];
DSRequestData __requestCX = req.getContext();
DSResponse __return = new DSResponseImpl(req,Status.STATUS_SUCCESS);
Object criteria = req.getContext().getRawCriteria();
int batchsize = __requestCX.getBatchSize();
// batch size.
batchsize = batchsize < 100 ? 100 : batchsize;
if (isEntityPresent(criteria)) {
List<?> records = DataUtils.makeListIfSingle(criteria);
Object result = removeBean(batchsize, records);
__return.setRawData(result);
return __return;
}
// check jpql configured
ToperationBinding _op = __firstDS.getContext().getOperationBinding(req);
if (_op != null && _op.getQueryClauses() != null && _op.getQueryClauses().getCustomQL() != null) {
Query query= entityManager.createQuery(_op.getQueryClauses().getCustomQL());
query.executeUpdate();
Map<String, Object> c =req.getContext().getCriteria();
if(c!=null){
}
return __return;
}
if (isEntityClass(entityClass)) {
List<?> records = req.getContext().getValueSets();
List<Object> beans = new ArrayList<Object>();
for (Object o : records) {
Object bean = instance(entityClass);
try {
DataUtils.setProperties((Map<?,?>) o, bean, false);
} catch (Exception e) {
String __msg = "invoke bean class:[" + bean.getClass().getName() + "] exception";
throw new SlxException(Tmodule.JPA, Texception.INVOKE_EXCEPTION, __msg);
}
beans.add(bean);
}
Object result = removeBean(batchsize, beans);
__return.setRawData(result);
return __return;
} else {
throw new SlxException(Tmodule.JPA, Texception.JPA_NO_ENTITY, "JPA DataSource no configured Entity bean");
}
}
private DSResponse fetchEntity(DSRequest req, JPADataSource[] datasources) throws SlxException {
Map<String, Object> parameters = new HashMap<String, Object>();
StringBuffer whereClause = new StringBuffer();
StringBuffer orderClause = new StringBuffer();
if (!isEntityClass(entityClass)) {
throw new SlxException(Tmodule.JPA, Texception.JPA_NO_ENTITY, "JPA DataSource no configured Entity bean");
}
DSResponse __return = new DSResponseImpl(req,Status.STATUS_SUCCESS);
Map<String, Object> criteria = req.getContext().getCriteria();
if (criteria != null) {
if (isAdvancedCriteria(criteria)) {
throw new SlxException(Tmodule.JPA, Texception.NO_SUPPORT, "JPA datasource has't supported advance criteria yet");
}
for (Object obj : criteria.keySet()) {
String fieldName = (String) obj;
Object value = criteria.get(obj);
Tfield _f = data.getField(fieldName);
if (_f == null) {
log.warn("field:[" + fieldName + "] specified in criteria is not defined in datasource");
} else {
try {
Efield _ft = _f.getType();
String xpath = _f.getValueXPath();
if (xpath != null)
fieldName.replace('/', '.');
if (value == null) {
if (isNotNullAndEmpty(whereClause))
whereClause.append(" AND ");
whereClause.append(entityName).append(".").append(fieldName).append(" is null");
} else if (value instanceof List) {
List<?> valueList = (List<?>) value;
if (valueList.size() > 0) {
if (!isNotNullAndEmpty(whereClause))
whereClause.append(" AND ");
whereClause.append("(");
Class<?> ftype = DataUtils.getPropertyType(entityClass, fieldName);
for (int i = 0; i < valueList.size(); i++) {
Object v = valueList.get(i);
if (!whereClause.toString().endsWith("("))
whereClause.append(" or ");
String pName = fieldName + i;
whereClause.append(entityName).append(".").append(fieldName).append(" = :").append(pName);
parameters.put(pName, DataUtils.castValue(v, ftype));
}
whereClause.append(")");
}
} else {
if (DataUtils.isNotNullAndEmpty(whereClause))
whereClause.append(" AND ");
if (_ft == Efield.TEXT || _ft == Efield.IMAGE || _ft == Efield.PASSWORD || _ft == Efield.LINK) {
String matchStyle = null;
try {
matchStyle = req.getContext().getRoperation().getTextMatchStyle();
} catch (NullPointerException ignore) {
}
if ("startsWith".equals(matchStyle)) {
whereClause.append(entityName).append(".").append(fieldName).append(" like :").append(fieldName);
parameters.put(fieldName, value != null ? value.toString() : "" + "%");
} else if ("substring".equals(matchStyle)) {
whereClause.append(entityName).append(".").append(fieldName).append(" like :").append(fieldName);
parameters.put(fieldName, "%" + value != null ? value.toString() : "" + "%");
} else {
whereClause.append(entityName).append(".").append(fieldName).append(" = :").append(fieldName);
parameters.put(fieldName, value);
}
} else {
whereClause.append(entityName).append(".").append(fieldName).append(" = :").append(fieldName);
parameters.put(fieldName, DataUtils.castValue(value, DataUtils.getPropertyType(entityClass, fieldName)));
}// END field?
}
} catch (IntrospectionException ix) {
throw new SlxException(Tmodule.JPA, Texception.DEFAULT, ix);
}
}// END _f == null
}// end criteria loop
}
/**************************************************
* process store
**************************************************/
List<?> sort = req.getContext().getSortByFields();
if (sort != null) {
for (Object obj : sort) {
String fieldName = (String) obj;
Tfield _f = this.data.getField(fieldName);
if (_f == null) {
log.warn("field:[" + fieldName + "] specified in sortBy is not defined in datasource");
} else {
if (isNotNullAndEmpty(orderClause))
orderClause.append(" , ");
String xpath = _f.getValueXPath();
if (isNotNullAndEmpty(xpath))
fieldName = xpath.replace('/', '.');
orderClause.append(entityName).append(".").append(fieldName);
}
}// END SORT FIELD LOOP
}
// query JPAQL string.
StringBuffer jpaQuery = new StringBuffer().append("SELECT ").append(entityName).append(" FROM ").append(
useQualifiedClassName ? entityClass.getName() : entityClass.getSimpleName()).append(" ").append(entityName);
// result count JPAQL string.
StringBuffer jpaCountQ = new StringBuffer().append("SELECT COUNT (").append(
data.getPrimaryKey() == null ? entityName : entityName + "." + data.getPrimaryKey()).append(") FROM ").append(
useQualifiedClassName ? entityClass.getName() : entityClass.getSimpleName()).append(" ").append(entityName);
if (DataUtils.isNotNullAndEmpty(whereClause)) {
jpaQuery.append(" WHERE ").append(whereClause);
jpaCountQ.append(" WHERE ").append(whereClause);
}
if (DataUtils.isNotNullAndEmpty(orderClause)) {
jpaQuery.append(" ORDER BY ").append(orderClause);
}
if (log.isTraceEnabled())
log.trace("JPA-Query String:" + jpaQuery);
Query query = entityManager.createQuery(jpaQuery.toString());
for (String key : parameters.keySet()) {
Object value = parameters.get(key);
query.setParameter(key, value);
if (log.isTraceEnabled())
log.trace("Query Parameter:[" + value + "]");
}
// Control Page.
int totalRows = -1;
boolean __canPage = true;
//
ToperationBinding __bind = null;
try {
__bind = getContext().getOperationBinding(req);
} catch (NullPointerException e) {
}
if (!req.getContext().isPaged() ){
__canPage = false;
}else if(getConfig().getBoolean("customReturnsAllRows", false)
&&DataUtils.isNotNullAndEmpty(DataSourceData.getCustomSQL(__bind))){
__canPage = false;
if(log.isTraceEnabled())
log.trace("Paging disabled for full custom queries. "
+ "Fetching all rows.Set sql.customReturnsAllRows: "
+ "false in config to change this behavior");
}
if (__canPage) {
int end = req.getContext().getEndRow();
int start = req.getContext().getStartRow();
int batch = req.getContext().getBatchSize();
if (end != -1 && end - start > batch) {
batch = end - start;
req.getContext().setBatchSize(batch);
}
Query queryCount = entityManager.createQuery(jpaCountQ.toString());
for (String key : parameters.keySet()) {
Object value = parameters.get(key);
queryCount.setParameter(key, value);
if (log.isTraceEnabled())
log.trace("Query Parameter:[" + value + "]");
}
long _$=System.currentTimeMillis();
Integer rowCount = Integer.valueOf(Integer.parseInt(queryCount.getSingleResult().toString()));
if(log.isTraceEnabled()){
getEventWork().createAndFireTimeEvent(System.currentTimeMillis()-_$, "Query Fetch count used time");
}
totalRows = rowCount != null ? rowCount.intValue() : 0;
query.setFirstResult(start);
query.setMaxResults(batch);
}
List<?> results = null;
if (totalRows == 0)
results = Collections.emptyList();
else
results = query.getResultList();
if (totalRows == -1L)
totalRows = results.size();
__return.setTotalRows(totalRows);
Integer startRow = 0;
Integer endRow = 0;
if (totalRows != 0L) {
startRow = req.getContext().getStartRow() == null ? 0 : req.getContext().getStartRow();
endRow = startRow + results.size();
}
__return.setStartRow(startRow);
__return.setEndRow(endRow);
__return.setRawData(results);
increaseOpCount();
return __return;
}
private DSResponse addEntity(DSRequest req, JPADataSource[] datasources) throws SlxException {
JPADataSource __firstDS = datasources[0];
DSRequestData __requestCX = req.getContext();
DSResponse __return = new DSResponseImpl(req.getDataSource(),Status.STATUS_SUCCESS);
Object values = req.getContext().getRawValues();
int batchsize = __requestCX.getBatchSize();
// batch size.
batchsize = batchsize < 100 ? 100 : batchsize;
// java entity bean values.
if (isEntityPresent(values)) {
List<?> records = DataUtils.makeListIfSingle(values);
Object result = persistBean(batchsize, records);
__return.setRawData(result);
return __return;
}
// check jpql configured
ToperationBinding _op = __firstDS.getContext().getOperationBinding(req);
if (_op != null && _op.getQueryClauses() != null && _op.getQueryClauses().getCustomQL() != null) {
// velocity exp
return __return;
}
if (isEntityClass(entityClass)) {
List<?> records = req.getContext().getValueSets();
List<Object> beans = new ArrayList<Object>();
for (Object o : records) {
Object bean = instance(entityClass);
try {
DataUtils.setProperties((Map<?,?>) o, bean, false);
} catch (Exception e) {
String __msg = "invoke bean class:[" + bean.getClass().getName() + "] exception";
throw new SlxException(Tmodule.JPA, Texception.INVOKE_EXCEPTION, __msg);
}
beans.add(bean);
}
Object result = persistBean(batchsize, beans);
__return.setRawData(result);
return __return;
} else {
throw new SlxException(Tmodule.JPA, Texception.JPA_NO_ENTITY, "JPA DataSource no configured Entity bean");
}
}
private Object instance(Class<?> clz) throws SlxException {
try {
return Reflection.newInstance(clz);
} catch (Exception e) {
throw new SlxException(Tmodule.JPA, Texception.CAN_NOT_INSTANCE, e);
}
}
private Object persistBean(int batchsize, List<?> records) {
// batch update.
int i = 0;
for (Object o : records) {
entityManager.persist(o);
i++;
if (i % batchsize == 0) {
entityManager.flush();
entityManager.clear();
}
}
entityManager.flush();
entityManager.clear();
return records;
}
private Object updateBean(int batchsize, List<?> records) throws SlxException {
// batch update.
int i = 0;
List<Object> _return = new ArrayList<Object>();
for (Object o : records) {
Object attached = findAttachedBean(o);
try {
DataUtils.setProperties(DataUtils.getProperties(o, true), attached);
} catch (Exception e) {
e.printStackTrace();
}
entityManager.merge(attached);
_return.add(attached);
i++;
if (i % batchsize == 0) {
entityManager.flush();
entityManager.clear();
}
}
entityManager.flush();
entityManager.clear();
return _return;
}
private Object removeBean(int batchsize, List<?> records) throws SlxException {
// batch update.
int i = 0;
List<Object> _return = new ArrayList<Object>();
for (Object o : records) {
Object attached = findAttachedBean(o);
entityManager.remove(attached);
_return.add(attached);
i++;
if (i % batchsize == 0) {
entityManager.flush();
entityManager.clear();
}
}
entityManager.flush();
entityManager.clear();
return _return;
}
/**
* @param o
* @return
* @throws SlxException
*/
private Object findAttachedBean(Object o) throws SlxException {
try {
Field[] declaredFields = o.getClass().getDeclaredFields();
Map<String, PropertyDescriptor> propDes = DataUtils.getPropertyDescriptors(o.getClass());
Object id = null;
for (Field field : declaredFields) {
int modifier = field.getModifiers();
String propertyName = field.getName();
if (Modifier.isStatic(modifier))
continue;
if (field.getAnnotation(Id.class) != null) {
id = DataUtils.getProperty(propertyName, o);
break;
} else {// AccessType=PROPERTY
PropertyDescriptor propDesc = propDes.get(propertyName);
Method read = propDesc.getReadMethod();
if (read != null && read.getAnnotation(Id.class) != null) {
id = read.invoke(o, new Object[0]);
break;
}
}
}
if (id != null) {
return entityManager.find(o.getClass(), id);
} else {
throw new SlxException(Tmodule.JPA, Texception.JPA_JPAEXCEPTION, "JPA Entity Bean no declear ID field or Method");
}
} catch (Exception e) {
throw new SlxException(Tmodule.JPA, Texception.INVOKE_EXCEPTION, "can not find id");
}
}
private boolean isEntityClass(Class<?> clz) {
if (clz == null)
return false;
return clz.isAnnotationPresent(Entity.class);
}
private boolean isEntityPresent(Object o) {
if (o == null)
return false;
if (o instanceof List<?>) {
List<?> criterias = (List<?>) o;
if (criterias.size() > 0) {
Object getOne = criterias.get(0);
return isEntityPresent(getOne);
}
} else if (o.getClass().isArray()) {
Class<?> ctype = o.getClass().getComponentType();
return ctype.isAnnotationPresent(Entity.class);
} else {
return o.getClass().isAnnotationPresent(Entity.class);
}
return false;
}
}