/*
* Copyright 2013 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.mybatis;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
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.exception.SlxException;
import org.solmix.api.jaxb.Eoperation;
import org.solmix.api.jaxb.EserverType;
import org.solmix.api.jaxb.ToperationBinding;
import org.solmix.api.types.Texception;
import org.solmix.api.types.Tmodule;
import org.solmix.commons.util.Assert;
import org.solmix.commons.util.DataUtils;
import org.solmix.fmk.datasource.BasicDataSource;
import org.solmix.fmk.datasource.DSResponseImpl;
import org.solmix.fmk.datasource.DefaultDataSourceManager;
import org.solmix.fmk.util.DataTools;
import org.solmix.runtime.SystemContext;
import org.solmix.sql.SQLDriver;
/**
*
* @author solmix.f@gmail.com
* @version $Id$ 2014年6月12日
*/
public class MybatisDataSource extends BasicDataSource implements DataSource,
DSCallCompleteCallback
{
private final static Logger log = LoggerFactory.getLogger(MybatisDataSource.class.getName());
public static final String SERVICE_PID = "org.solmix.modules.mybatis";
private SqlSessionFactoryProvider sqlSessionFactoryProvider;
private SqlSession session;
private SQLDriver sqlDriver;
private String environment;
private SqlSessionFactory sqlSessionFactory;
public MybatisDataSource(final SystemContext sc)
{
setSystemContext(sc);
}
/**
* {@inheritDoc}
*
* @see org.solmix.api.call.DSCallCompleteCallback#onSuccess(org.solmix.api.call.DSCall)
*/
@Override
public void onSuccess(DSCall call) throws SlxException {
MybatisTransaction.commitTransaction(call, environment,
sqlSessionFactory);
MybatisTransaction.endTransaction(call, environment, sqlSessionFactory);
}
/**
* {@inheritDoc}
*
* @see org.solmix.api.call.DSCallCompleteCallback#onFailure(org.solmix.api.call.DSCall,
* boolean)
*/
@Override
public void onFailure(DSCall call, boolean isFailed) throws SlxException {
if (isFailed) {
MybatisTransaction.rollbackTransaction(call, environment,
sqlSessionFactory);
} else {
MybatisTransaction.commitTransaction(call, environment,
sqlSessionFactory);
}
MybatisTransaction.endTransaction(call, environment, sqlSessionFactory);
}
@Override
public String getServerType() {
return EserverType.MYBATIS.value();
}
public void destroy() {
if (log.isTraceEnabled())
log.trace("MybatisDataSource:" + this.getContext().getName()
+ " destroying!");
}
@Override
public void init(DataSourceData data) throws SlxException {
super.init(data);
environment = data.getTdataSource() == null ? null
: data.getTdataSource().getDbName();
Assert.isNotNull(environment, "mybatis environment must be configured");
}
@Override
public DSResponse execute(DSRequest req) throws SlxException {
req.registerFreeResourcesHandler(this);
Eoperation _opType = req.getContext().getOperationType();
DSResponse __return = null;
if (isMybatisOperation(_opType)) {
DSResponse validationFailure = validateDSRequest(req);
if (validationFailure != null) {
return validationFailure;
}
// 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 = executeMybatisDataSource(req, dsObject);
} else {
__return = super.execute(req);
}
return __return;
}
/**
* Execute Mybatis support datasource operation.
*
* @param req Datasource Request
* @param dsObject Datasource
* @return response DataSource request response.
* @throws SlxException
*/
private DSResponse executeMybatisDataSource(DSRequest req, Object dsObject)
throws SlxException {
// adapte datasource
MybatisDataSource mybatis;
if (dsObject instanceof MybatisDataSource) {
mybatis = (MybatisDataSource) dsObject;
} else if (dsObject instanceof String) {
mybatis = getDataSource((String) dsObject);
} else {
throw new SlxException(
Tmodule.DATASOURCE,
Texception.DS_DSCONFIG_ERROR,
"in the app operation config, datasource must be set to a string or MybatisDataSource ");
}
// initial mybatis sqlsession.
boolean __userTransaction = false;
if (req.getDSCall() != null && this.shouldAutoJoinTransaction(req)) {
Object obj = this.getTransactionObject(req);
if (!(obj instanceof SqlSession)) {
if (log.isWarnEnabled())
log.warn("Mybatis DataSource transaction should be a org.apache.ibatis.session.SqlSession instance,but is"
+ obj.getClass().getName()
+ " Assume the transaction object is invalid and set it to null");
session = null;
} else {
session = (SqlSession) obj;
}
if (session == null) {
if (shouldAutoStartTransaction(req, false)) {
__userTransaction = true;
MybatisTransaction.startTransaction(req.getDSCall(),
environment, getSqlSessionFactory());
session = (SqlSession) getTransactionObject(req);
if (req != null && req.getDSCall() != null)
req.getDSCall().registerCallback(this);
}
}
req.setJoinTransaction(true);
} else {
session = getSqlSession(true);
}
// dispatch operation by operation type.
DSRequestData __requestCX = req.getContext();
Eoperation _req = __requestCX.getOperationType();
DSResponse __return = null;
try {
switch (_req) {
case ADD:
__return = executeAdd(req, mybatis);
break;
case FETCH:
__return = executefetch(req, mybatis);
break;
case REMOVE:
__return = executeRemove(req, mybatis);
break;
case UPDATE:
__return = executeUpdate(req, mybatis);
break;
default:
break;
}
} finally {
if (!__userTransaction) {
session.close();
}
}
return __return;
}
@Override
public String getTransactionObjectKey() throws SlxException {
return MybatisTransaction.getTransactionKey(environment);
}
/**
* @param req
* @param mybatis
* @return
* @throws SlxException
*/
private DSResponse executeUpdate(DSRequest req, MybatisDataSource mybatis)
throws SlxException {
DSResponse __return = new DSResponseImpl(req.getDataSource(),
Status.STATUS_SUCCESS);
String statement = getMybatisStatement(req);
Map<String, Object> parameter = new HashMap<String, Object>();
DataUtils.mapMerge(req.getContext().getCriteria(), parameter);
DataUtils.mapMerge(req.getContext().getValues(), parameter);
int result = session.update(statement, parameter);
__return.setAffectedRows(new Long(result));
__return.setRawData(result);
return __return;
}
/**
* @param req
* @param mybatis
* @return
* @throws SlxException
*/
private DSResponse executeRemove(DSRequest req, MybatisDataSource mybatis)
throws SlxException {
DSResponse __return = new DSResponseImpl(req.getDataSource(),
Status.STATUS_SUCCESS);
String statement = getMybatisStatement(req);
int result = session.delete(statement, req.getContext().getCriteria());
__return.setAffectedRows(new Long(result));
__return.setRawData(result);
return __return;
}
/**
* Mybatis NOT SUPPORT SHOW THE AFFECTED ROW.Tdatasource.simpleReturn=true.
*
* @param req
* @param mybatis
* @return
* @throws SlxException
*/
private DSResponse executeAdd(DSRequest req, MybatisDataSource mybatis)
throws SlxException {
DSResponse __return = new DSResponseImpl(req.getDataSource(),
Status.STATUS_SUCCESS);
String statement = getMybatisStatement(req);
int result = session.update(statement, req.getContext().getCriteria());
__return.setAffectedRows(new Long(result));
__return.setRawData(result);
return __return;
}
private String getMybatisStatement(DSRequest req) throws SlxException {
ToperationBinding __bind = getContext().getOperationBinding(req);
String mybatisStatement = null;
if (__bind != null && __bind.getQueryClauses() != null) {
mybatisStatement = __bind.getQueryClauses().getCustomQL();
}
if (mybatisStatement == null) {
throw new SlxException(Tmodule.MYBATIS, Texception.OBJECT_IS_NULL,
"configure error:mybatis statement is null");
}
return mybatisStatement;
}
/**
* @param req
* @param mybatis
* @return
*/
private DSResponse executefetch(DSRequest req, MybatisDataSource mybatis)
throws SlxException {
DSRequestData reqData = req.getContext();
DSResponse __return = new DSResponseImpl(req, Status.STATUS_SUCCESS);
// reqData.getRawCriteria();
ToperationBinding __bind = getContext().getOperationBinding(req);
String mybatisStatement = getMybatisStatement(req);
// Control Page.
int totalRows = -1;
boolean __canPage = true;
if (!reqData.isPaged()) {
__canPage = false;
} else if (getConfig().getBoolean("customReturnsAllRows", false)
&& DataUtils.isNotNullAndEmpty(DataSourceData.getCustomSQL(__bind))) {
__canPage = false;
if (log.isDebugEnabled())
log.debug("Paging disabled for full custom queries. "
+ "Fetching all rows.Set sql.customReturnsAllRows:"
+ " false in config to change this behavior");
}
Object parameter = null;
if (__canPage) {
// initial SqlDriver for limit & count sql
if (sqlDriver == null) {
try {
sqlDriver = SQLDriver.instance(environment,
getSqlSessionFactoryProvider().getDbType(environment));
} catch (Exception e) {
throw new SlxException(Tmodule.SQL,
Texception.SQL_SQLEXCEPTION,
"Can't instance SQLDriver", e);
}
}
parameter = new MybatisParameter(req, __return,
reqData.getRawCriteria(), sqlDriver, __canPage);
} else {
parameter = req.getContext().getRawCriteria();
}
final List<Object> results = new ArrayList<Object>();
// log start time
long _$ = System.currentTimeMillis();
session.select(mybatisStatement, parameter, new ResultHandler() {
@Override
public void handleResult(ResultContext context) {
results.add(context.getResultObject());
}
});
// fire time event.
getEventWork().createAndFireTimeEvent(
(System.currentTimeMillis() - _$),
"SQL window query,Query total rows: " + results.size());
// prcess result and prepare to return.
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);
return __return;
}
/**
* @param atuoCommit
* @return
* @throws SlxException
*/
private SqlSession getSqlSession(boolean atuoCommit) throws SlxException {
SqlSessionFactoryProvider provider = getSqlSessionFactoryProvider();
Assert.isNotNull(provider);
SqlSessionFactory factory = provider.createSqlSessionFactory(environment);
return factory.openSession(atuoCommit);
}
/**
* @param dsObject
* @return
*/
private MybatisDataSource getDataSource(String datasourceName)
throws SlxException {
DataSource datasource = DefaultDataSourceManager.getDataSource(datasourceName);
if (datasource instanceof MybatisDataSource) {
return (MybatisDataSource) datasource;
} else {
throw new SlxException("the datasource [" + datasource.toString()
+ "] cannot processed by Mybatis DataSource.");
}
}
private boolean isMybatisOperation(Eoperation operationType) {
return DataTools.isFetch(operationType)
|| DataTools.isAdd(operationType)
|| DataTools.isRemove(operationType)
|| DataTools.isUpdate(operationType)
|| DataTools.isReplace(operationType);
}
/**
* @return the sqlSessionFactoryProvider
*/
public SqlSessionFactoryProvider getSqlSessionFactoryProvider() {
return sqlSessionFactoryProvider;
}
private SqlSessionFactory getSqlSessionFactory() throws SlxException {
if (sqlSessionFactory == null)
sqlSessionFactory = getSqlSessionFactoryProvider().createSqlSessionFactory(
environment);
return sqlSessionFactory;
}
/**
* @param sqlSessionFactoryProvider the sqlSessionFactoryProvider to set
*/
public void setSqlSessionFactoryProvider(
SqlSessionFactoryProvider sqlSessionFactoryProvider) {
this.sqlSessionFactoryProvider = sqlSessionFactoryProvider;
}
/**
* Factory method for instance new mybatis datasource. {@inheritDoc}
*
* @see org.solmix.fmk.datasource.BasicDataSource#instance(org.solmix.api.datasource.DataSourceData)
*/
@Override
public DataSource instance(DataSourceData data) throws SlxException {
MybatisDataSource ds = new MybatisDataSource(sc);
if (this.getSqlSessionFactoryProvider() != null) {
ds.setSqlSessionFactoryProvider(getSqlSessionFactoryProvider());
}
ds.init(data);
return ds;
}
}