package com.eas.client.model.application; import com.eas.client.DatabasesClient; import com.eas.client.SqlCompiledQuery; import com.eas.client.SqlQuery; import com.eas.client.StoredQueryFactory; import com.eas.client.changes.Change; import com.eas.client.model.Model; import com.eas.client.model.visitors.ModelVisitor; import com.eas.client.queries.QueriesProxy; import com.eas.script.ScriptFunction; import com.eas.util.IdGenerator; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; import jdk.nashorn.api.scripting.JSObject; /** * * @author mg */ public class ApplicationDbModel extends ApplicationModel<ApplicationDbEntity, SqlQuery> { protected Map<String, List<Change>> changeLogs = new HashMap<>(); protected DatabasesClient basesProxy; public ApplicationDbModel(QueriesProxy<SqlQuery> aQueries) { super(aQueries); } public ApplicationDbModel(DatabasesClient aBasesProxy, QueriesProxy<SqlQuery> aQueries) { this(aQueries); basesProxy = aBasesProxy; } @Override public <M extends Model<ApplicationDbEntity, ?>> void accept(ModelVisitor<ApplicationDbEntity, M> visitor) { visitor.visit((M) this); } public DatabasesClient getBasesProxy() { return basesProxy; } @Override public ApplicationDbEntity newGenericEntity() { return new ApplicationDbEntity(this); } @Override public void addEntity(ApplicationDbEntity aEntity) { aEntity.setModel(this); super.addEntity(aEntity); } private static final String MODIFIED_JSDOC = "" + "/**\n" + " * Flag is set to true if model has been modified.\n" + " */"; @ScriptFunction(jsDoc = MODIFIED_JSDOC) @Override public boolean isModified() throws Exception { return changeLogs.values().stream().anyMatch((List<Change> aLog) -> { return !aLog.isEmpty(); }); } @Override public int commit(Consumer<Integer> onSuccess, Consumer<Exception> onFailure) throws Exception { Map<String, List<Change>> logs = changeLogs; changeLogs = new HashMap<>(); // Change logs are cleared unconditionaly because of // compliance of synchronous and asynchronous cases with errors while commit in mind. return basesProxy.commit(logs, onSuccess, onFailure); } @Override public void commited() { super.commited(); } @ScriptFunction(jsDoc = REVERT_JSDOC) @Override public void revert() { changeLogs.values().stream().forEach((changeLog) -> { changeLog.clear(); }); super.revert(); } @ScriptFunction(jsDoc = REQUERY_JSDOC, params = {"onSuccess", "onFailure"}) @Override public void requery(JSObject aOnSuccess, JSObject aOnFailure) throws Exception { changeLogs.values().stream().forEach((changeLog) -> { changeLog.clear(); }); super.requery(aOnSuccess, aOnFailure); } public List<Change> getChangeLog(String aDatasourceName) { String datasourceName = aDatasourceName; // basesProxy.getDefaultDatasourceName() is needed here to avoid multi transaction // actions against the same datasource, leading to unexpected // row-level locking and deadlocks in two phase transaction commit process if (datasourceName == null || datasourceName.isEmpty()) { datasourceName = basesProxy.getDefaultDatasourceName(); } List<Change> changeLog = changeLogs.get(datasourceName); if (changeLog == null) { changeLog = new ArrayList<>(); changeLogs.put(datasourceName, changeLog); } return changeLog; } public void forEachChange(Consumer<Change> aActor) { changeLogs.entrySet().stream().forEach((Map.Entry<String, List<Change>> aEntry) -> { aEntry.getValue().stream().forEach(aActor); }); } public ApplicationDbEntity createEntity(String aSqlText) throws Exception { return createEntity(aSqlText, null); } private static final String CREATE_ENTITY_JSDOC = "" + "/**\n" + " * Creates new entity of model, based on passed sql query. This method works only in two tier components of a system.\n" + " * @param sqlText SQL text for the new entity.\n" + " * @param datasourceName the concrete database ID (optional).\n" + " * @return an entity instance.\n" + " */"; @ScriptFunction(jsDoc = CREATE_ENTITY_JSDOC, params = {"sqlText", "datasourceName"}) public ApplicationDbEntity createEntity(String aSqlText, String aDatasourceName) throws Exception { if (basesProxy == null) { throw new NullPointerException("null basesProxy detected while creating a query"); } ApplicationDbEntity created = newGenericEntity(); created.setName(USER_DATASOURCE_NAME); SqlQuery query = new SqlQuery(basesProxy, aDatasourceName, aSqlText); query.setEntityName(IdGenerator.genStringId()); StoredQueryFactory factory = new StoredQueryFactory(basesProxy, null, null); factory.putTableFieldsMetadata(query);// only select will be filled with output columns created.setQuery(query); // .schema collection will be empty if query is not a select return created; } public void executeSql(String aSql) throws Exception { executeSql(aSql, null); } private static final String EXECUTE_SQL_JSDOC = "" + "/**\n" + " * Executes a SQL query against specific datasource. This method works only in two tier components of a system.\n" + " * @param sqlText SQL text for the new entity.\n" + " * @param datasourceName. The specific databsource name (optional).\n" + " * @param onSuccess Success callback. Have a number argument, indicating updated rows count (optional).\n" + " * @param onFailure Failure callback. Have a string argument, indicating an error occured (optional).\n" + " * @return an entity instance.\n" + " */"; @ScriptFunction(jsDoc = EXECUTE_SQL_JSDOC, params = {"sqlText", "datasourceName"}) public void executeSql(String aSqlClause, String aDatasourceName, JSObject onSuccess, JSObject onFailure) throws Exception { if (basesProxy == null) { throw new NullPointerException("Null basesProxy detected while creating a query"); } SqlCompiledQuery compiled = new SqlCompiledQuery(basesProxy, aDatasourceName, aSqlClause); if (onSuccess != null) { basesProxy.executeUpdate(compiled, (Integer updated) -> { onSuccess.call(null, new Object[]{updated}); }, (Exception ex) -> { if (onFailure != null) { onFailure.call(null, new Object[]{ex.getMessage()}); } }); } else { basesProxy.executeUpdate(compiled, null, null); } } public void executeSql(String aSqlClause, String aDatasourceName, JSObject onSuccess) throws Exception { executeSql(aSqlClause, aDatasourceName, onSuccess, null); } public void executeSql(String aSqlClause, String aDatasourceName) throws Exception { executeSql(aSqlClause, aDatasourceName, null, null); } }