
io.vertigo.dynamox.task.AbstractTaskEngineSQL Maven / Gradle / Ivy
/**
* vertigo - simple java starter
*
* Copyright (C) 2013-2016, KleeGroup, [email protected] (http://www.kleegroup.com)
* KleeGroup, Centre d'affaire la Boursidiere - BP 159 - 92357 Le Plessis Robinson Cedex - France
*
* 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 io.vertigo.dynamox.task;
import java.sql.BatchUpdateException;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.vertigo.commons.script.ScriptManager;
import io.vertigo.commons.script.SeparatorType;
import io.vertigo.commons.script.parser.ScriptSeparator;
import io.vertigo.dynamo.database.SqlDataBaseManager;
import io.vertigo.dynamo.database.connection.SqlConnection;
import io.vertigo.dynamo.database.connection.SqlConnectionProvider;
import io.vertigo.dynamo.database.statement.SqlCallableStatement;
import io.vertigo.dynamo.database.statement.SqlPreparedStatement;
import io.vertigo.dynamo.domain.metamodel.DataType;
import io.vertigo.dynamo.domain.metamodel.Domain;
import io.vertigo.dynamo.domain.metamodel.DtDefinition;
import io.vertigo.dynamo.domain.metamodel.DtField;
import io.vertigo.dynamo.domain.model.DtList;
import io.vertigo.dynamo.domain.model.DtObject;
import io.vertigo.dynamo.domain.util.DtObjectUtil;
import io.vertigo.dynamo.store.StoreManager;
import io.vertigo.dynamo.task.metamodel.TaskAttribute;
import io.vertigo.dynamo.task.model.TaskEngine;
import io.vertigo.dynamo.transaction.VTransaction;
import io.vertigo.dynamo.transaction.VTransactionManager;
import io.vertigo.dynamo.transaction.VTransactionResourceId;
import io.vertigo.dynamox.task.TaskEngineSQLParam.InOutType;
import io.vertigo.lang.Assertion;
import io.vertigo.lang.WrappedException;
import io.vertigo.util.ListBuilder;
/**
* Fournit des méthodes de haut niveau pour les services de type SQL.
* Un Service SQL est composé de paramètre de type primitif, DTO ou DTC, en IN, OUT ou INOUT et d'une requête SQL
* sous forme de texte.
* La requête est parsée puis préparée pour replacer les paramètres dynamo par des variables bindées.
* Grammaire des requêtes :
* ##
: paramètre IN
* %%
: paramètre OUT
* @@
: paramètre INOUT
* où est :
* "primitif" : LON_IDENTIFIANT_ID ou DAT_DATE_SAISIE
* "champ de dto" : . : DTO_PERSONNE.NOM ou encore DTO_PERSONNE.PER_ID
* "champ de dtc" : .. : DTC_PERSONNE.2.NOM ou encore DTC_PERSONNE.0.PER_ID
*
* Les DTO et DTC n'ont pas besoin d'être en OUT ou en INOUT pour être mutées.
*
* Intérêt de gérer des paramètres DTC : il existe maintenant un moyen d'accéder aux champs
* d'une DTC, qui peut être utilisé pour créer des ServiceProviderSQL pseudo-dynamiques (ajout de mots clefs
* dans la requête SQL du KSP pour gérer des itérations sur DTC par ex).
*
* Exemple de requête :
*
* SELECT TOTO_ID, NOM
* FROM TOTO
* WHERE TOTO_ID = #LON_TOTO_ID#
* AND NOM like #DTO_FILTRE.NOM#||'%'
* AND TYPE_ID IN (#DTC_TYPE.0.TYPE_ID#,#DTC_TYPE.1.TYPE_ID#,#DTC_TYPE.2.TYPE_ID#)
*
* De plus permet de créer du SQL dynamiquement interprété.
* Les paramètres IN de la tache peuvent être invoqués pour construire
* la requête SQL.
* Exemple :
* request = " Select *
* From PRODUIT
* <%if (dtoProduitCritere.getPrdLibelle()!=null) {%>
* Where PRD_LIBELLE like #DTO_PRODUIT_CRITERE.PRD_LIBELLE#||'%%'
* <%}%> order by <%=1%>";
*
* @author pchretien, npiedeloup
* @param Type de Statement utilisé
*/
public abstract class AbstractTaskEngineSQL extends TaskEngine {
/**
* Identifiant de ressource SQL par défaut.
*/
public static final VTransactionResourceId SQL_MAIN_RESOURCE_ID = new VTransactionResourceId<>(VTransactionResourceId.Priority.TOP, "Sql-main");
/**
* Nom de l'attribut recevant le nombre de lignes affectées par un Statement.
* Dans le cas des Batchs ce nombre correspond à la somme de toutes les lignes affectées par le batch.
*/
//Qui utilise ça ?? // peut on revenir à une forme explicite
public static final String SQL_ROWCOUNT = "INT_SQL_ROWCOUNT";
/**
* Liste des séparateurs utilisés dans le traitement des requêtes KSP.
*/
private static final List SQL_SEPARATORS = createSqlSeparators();
/**
* Liste des paramètres
*/
private List params;
private final ScriptManager scriptManager;
private final VTransactionManager transactionManager;
private final StoreManager storeManager;
private final SqlDataBaseManager sqlDataBaseManager;
/**
* Constructeur.
* @param scriptManager Manager de traitment de scripts
*/
protected AbstractTaskEngineSQL(
final ScriptManager scriptManager,
final VTransactionManager transactionManager,
final StoreManager storeManager,
final SqlDataBaseManager sqlDataBaseManager) {
Assertion.checkNotNull(scriptManager);
Assertion.checkNotNull(transactionManager);
Assertion.checkNotNull(storeManager);
Assertion.checkNotNull(sqlDataBaseManager);
//-----
this.scriptManager = scriptManager;
this.transactionManager = transactionManager;
this.storeManager = storeManager;
this.sqlDataBaseManager = sqlDataBaseManager;
}
private static List createSqlSeparators() {
return new ListBuilder()
.add(new ScriptSeparator(InOutType.SQL_IN.getSeparator()))
.add(new ScriptSeparator(InOutType.SQL_OUT.getSeparator()))
.unmodifiable().build();
}
/**
* Exécution de la requête.
* @param connection Connexion BDD
* @param statement Requête
* @return Nombre de lignes affectées (Insert/ Update / Delete)
* @throws SQLException Erreur sql
*/
protected abstract int doExecute(final SqlConnection connection, final S statement) throws SQLException;
/** {@inheritDoc} */
@Override
public void execute() {
final SqlConnection connection = obtainConnection();
final String sql = prepareParams(getSqlQuery().trim());
try (final S statement = createStatement(sql, connection)) {
//Inialise les paramètres.
registerParameters(statement);
try {
//Initialise le statement JDBC.
statement.init();
//Execute le Statement JDBC.
final int sqlRowcount = doExecute(connection, statement);
//On positionne le nombre de lignes affectées.
setRowCount(sqlRowcount);
} catch (final BatchUpdateException sqle) { //some exception embedded the usefull one
// Gère les erreurs d'exécution Batch JDBC.
handleSQLException(connection, sqle.getNextException(), statement);
} catch (final SQLException sqle) {
//Gère les erreurs d'exécution JDBC.
handleSQLException(connection, sqle, statement);
}
}
}
private void setRowCount(final int sqlRowcount) {
if (getTaskDefinition().getOutAttributeOption().isPresent()) {
final TaskAttribute outTaskAttribute = getTaskDefinition().getOutAttributeOption().get();
if (SQL_ROWCOUNT.equals(outTaskAttribute.getName())) {
setResult(sqlRowcount);
}
}
}
//-----
/**
* Retourne la Query qui sera parsée
* Par défaut il s'agit de la request définie sur le service
* @return Chaine de configuration
*/
protected String getSqlQuery() {
//On ajoute dans la requête SQL le nom de la tache utilisée
return preProcessQuery(new StringBuilder()
.append("/* TaskEngine : ")
.append(getTaskDefinition().getName())
.append(" */\n")
.append(getTaskDefinition().getRequest())
.toString());
}
/**
* Permet de créer du SQL dynamiquement interprété.
* Les paramètres IN de la tache peuvent être invoqués pour construire
* la requête SQL.
* Exemple :
* request = " Select *
* From PRODUIT
* <%if (dtoProduitCritere.getPrdLibelle()!=null) {%>
* Where PRD_LIBELLE like #DTO_PRODUIT_CRITERE.PRD_LIBELLE#||'%%'
* <%}%> order by <%=1%>";
* @param sqlQuery Requete à évaluer
* @return Requete évaluée
**/
protected final String preProcessQuery(final String sqlQuery) {
final Collection inAttributes = getTaskDefinition().getInAttributes();
final Map inTaskAttributes = new HashMap<>(inAttributes.size());
for (final TaskAttribute taskAttribute : inAttributes) {
inTaskAttributes.put(taskAttribute, getValue(taskAttribute.getName()));
}
//-----
final ScriptPreProcessor scriptPreProcessor = new ScriptPreProcessor(scriptManager, inTaskAttributes, SeparatorType.CLASSIC);
final TrimPreProcessor trimPreProcessor = new TrimPreProcessor(SeparatorType.BEGIN_SEPARATOR_CLASSIC, SeparatorType.END_SEPARATOR_CLASSIC);
final WhereInPreProcessor whereInPreProcessor = new WhereInPreProcessor(inTaskAttributes);
//--
String sql = sqlQuery;
sql = scriptPreProcessor.evaluate(sql);
sql = trimPreProcessor.evaluate(sql);
sql = whereInPreProcessor.evaluate(sql);
return sql;
}
/**
* Permet de parser la requête afin d'enregistrer les paramètres utilisés
*
* @return La requête bindée
* @param query Requête SQL
*/
private String prepareParams(final String query) {
Assertion.checkNotNull(query); //La requête ne peut pas être nulle
Assertion.checkState(params == null, "La query a déjà été préparée !");
//-----
final SqlParserHandler scriptHandler = new SqlParserHandler(getTaskDefinition());
scriptManager.parse(query, scriptHandler, SQL_SEPARATORS);
params = scriptHandler.getParams();
return scriptHandler.getSql();
}
//==========================================================================
//========================CallableStatement=================================
//==========================================================================
/**
* Met à jour les paramètres de sorties
*
* @param cs CallableStatement
* @throws SQLException Si erreur */
protected final void setOutParameters(final SqlCallableStatement cs) throws SQLException {
Assertion.checkNotNull(cs); //KCallableStatement doit être renseigné
//-----
for (final TaskEngineSQLParam param : params) {
if (!param.isIn()) {
setOutParameter(cs, param);
}
}
}
/**
* Met à jour une valeur de sortie à partir du résultat de la requête.
*
* @param cs CallableStatement
* @param param Paramètre traité
* @throws SQLException Si erreur avec la base
*/
private void setOutParameter(final SqlCallableStatement cs, final TaskEngineSQLParam param) throws SQLException {
final Object value = cs.getValue(param.getIndex());
setValueParameter(param, value);
}
/**
* Crée le Statement pour le select ou bloc sql.
* Initialise la liste des paramètres en entrée et en sortie
*
* @param sql Requête SQL
* @param connection Connexion vers la base de données
* @return Statement StatementSQL
*/
protected abstract S createStatement(String sql, SqlConnection connection);
/**
* Initialise les paramètres en entrée du statement
* @param statement Statement
*/
private void registerParameters(final SqlPreparedStatement statement) {
for (final TaskEngineSQLParam param : params) {
statement.registerParameter(param.getIndex(), getDataTypeParameter(param), param.isIn());
}
}
/**
* Modifie le statement en fonction des paramètres
* Affecte les valeurs en entrée
*
* @param statement de type KPreparedStatement, KCallableStatement...
* @throws SQLException En cas d'erreur dans la configuration
*/
protected final void setInParameters(final SqlPreparedStatement statement) throws SQLException {
Assertion.checkNotNull(statement);
//-----
for (final TaskEngineSQLParam param : params) {
if (param.isIn()) {
final Integer rowNumber = param.isList() ? param.getRowNumber() : null;
setInParameter(statement, param, rowNumber);
}
}
}
/**
* @return Liste des paramètres
*/
protected final List getParams() {
return Collections.unmodifiableList(params);
}
/**
* Affecte un paramètre au Statement.
* @param ps PrepareStatement
* @param param Paramètre SQL
* @param rowNumber Ligne des données d'entrée.
* @throws SQLException Erreur sql
*/
protected final void setInParameter(final SqlPreparedStatement ps, final TaskEngineSQLParam param, final Integer rowNumber) throws SQLException {
ps.setValue(param.getIndex(), getValueParameter(param, rowNumber));
}
private DataType getDataTypeParameter(final TaskEngineSQLParam param) {
final Domain domain;
if (param.isPrimitive()) {
// Paramètre primitif
// TODO reporter l'assertion dans le ServiceProviderSelect
//if Assertion.invariant((this.getAttribute(paramName).getInOut() & ServiceRegistry.ATTR_IN) > 0, paramName " must have attribute ATTR_IN.");
domain = getTaskDefinition().getInAttribute(param.getAttributeName()).getDomain();
} else if (param.isObject()) {
// DtObject
final DtObject dto = getValue(param.getAttributeName());
Assertion.checkNotNull(dto);
final DtDefinition dtDefinition = DtObjectUtil.findDtDefinition(dto);
domain = dtDefinition.getField(param.getFieldName()).getDomain();
} else if (param.isList()) {
// DtList
final DtList> dtc = getValue(param.getAttributeName());
Assertion.checkNotNull(dtc);
domain = dtc.getDefinition().getField(param.getFieldName()).getDomain();
} else {
throw new IllegalStateException(" le param doit être un primitif, un objet ou une liste.");
}
return domain.getDataType();
}
private void setValueParameter(final TaskEngineSQLParam param, final Object value) {
if (param.isPrimitive()) {
Assertion.checkArgument(getTaskDefinition().getOutAttributeOption().isPresent(), "{0} must have one attribute ATTR_OUT", param.getAttributeName());
setResult(value);
} else if (param.isObject()) {
//DtObject
final DtObject dto = getValue(param.getAttributeName());
final DtDefinition dtDefinition = DtObjectUtil.findDtDefinition(dto);
final DtField dtField = dtDefinition.getField(param.getFieldName());
dtField.getDataAccessor().setValue(dto, value);
} else if (param.isList()) {
// DtList
final DtList extends DtObject> dtc = getValue(param.getAttributeName());
final DtObject dto = dtc.get(param.getRowNumber());
final DtField dtField = dtc.getDefinition().getField(param.getFieldName());
dtField.getDataAccessor().setValue(dto, value);
} else {
throw new IllegalStateException(" le param doit être un primitif, un objet ou une liste.");
}
}
private Object getValueParameter(final TaskEngineSQLParam param, final Integer rowNumber) {
final Object value;
if (param.isPrimitive()) {
value = getValue(param.getAttributeName());
} else if (param.isObject()) {
// DtObject
final DtObject dto = getValue(param.getAttributeName());
final DtDefinition dtDefinition = DtObjectUtil.findDtDefinition(dto);
final DtField dtField = dtDefinition.getField(param.getFieldName());
value = dtField.getDataAccessor().getValue(dto);
} else if (param.isList()) {
// DtList
final DtList extends DtObject> dtc = getValue(param.getAttributeName());
final DtObject dto = dtc.get(rowNumber.intValue());
final DtField dtField = dtc.getDefinition().getField(param.getFieldName());
value = dtField.getDataAccessor().getValue(dto);
} else {
throw new IllegalStateException(" le param doit être un primitif, un objet ou une liste.");
}
return value;
}
/**
* Retourne la connexion SQL de cette transaction en la demandant au pool de connexion si nécessaire.
* @return Connexion SQL
*/
private SqlConnection obtainConnection() {
final VTransaction transaction = transactionManager.getCurrentTransaction();
SqlConnection connection = transaction.getResource(getVTransactionResourceId());
if (connection == null) {
// On récupère une connexion du pool
// Utilise le provider de connexion déclaré sur le Container.
try {
connection = getConnectionProvider().obtainConnection();
} catch (final SQLException e) {
throw new WrappedException("Can't connect to database", e);
}
transaction.addResource(getVTransactionResourceId(), connection);
}
return connection;
}
/**
* @return Id de la Ressource Connexion SQL dans la transaction
*/
protected VTransactionResourceId getVTransactionResourceId() {
final String dataSpace = getTaskDefinition().getDataSpace();
if (StoreManager.MAIN_DATA_SPACE_NAME.equals(dataSpace)) {
return SQL_MAIN_RESOURCE_ID;
}
return new VTransactionResourceId<>(VTransactionResourceId.Priority.TOP, "Sql-" + dataSpace);
}
/**
* @return Manager de base de données
*/
protected final SqlDataBaseManager getDataBaseManager() {
return sqlDataBaseManager;
}
/**
* Il est possible de surcharger la configuration SQL d'un service.
* @return Configuration SQL.
*/
protected SqlConnectionProvider getConnectionProvider() {
final String dataSpace = getTaskDefinition().getDataSpace();
final String connectionName = storeManager.getDataStoreConfig().getConnectionName(dataSpace);
return getDataBaseManager().getConnectionProvider(connectionName);
}
/**
* Gestion centralisée des exceptions SQL.
* @param connection Connexion
* @param sqle Exception SQL
* @param statement Statement
*/
private static void handleSQLException(final SqlConnection connection, final SQLException sqle, final SqlPreparedStatement statement) {
connection.getDataBase().getSqlExceptionHandler().handleSQLException(sqle, statement);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy