From 8dc23a10829c0d1fc03bd80a7b762d6d9729464b Mon Sep 17 00:00:00 2001 From: Teo Sarca Date: Sat, 29 Apr 2017 11:50:03 +0300 Subject: [PATCH] MAJOR views refactoring https://github.com/metasfresh/metasfresh-webui-api/issues/330 --- .../handlingunits/HUEditorViewFactory.java | 2 +- .../web/pporder/PPOrderLinesViewFactory.java | 4 +- .../de/metas/ui/web/view/IViewFactory.java | 2 +- .../java/de/metas/ui/web/view/SqlView.java | 343 ++---------- .../ui/web/view/SqlViewEvaluationCtx.java | 75 +++ .../de/metas/ui/web/view/SqlViewFactory.java | 57 +- .../metas/ui/web/view/SqlViewRepository.java | 231 ++++++++ .../java/de/metas/ui/web/view/ViewRow.java | 55 +- .../de/metas/ui/web/view/ViewsRepository.java | 4 +- .../web/view/descriptor/SqlViewBinding.java | 494 +++++++++--------- .../descriptor/SqlViewRowFieldBinding.java | 170 ++++++ .../SqlViewRowIdsOrderedSelectionFactory.java | 17 +- ...OBasedDocumentEntityDescriptorFactory.java | 10 +- ...qlDocumentEntityDataBindingDescriptor.java | 115 ++-- ...SqlDocumentFieldDataBindingDescriptor.java | 123 +++-- .../descriptor/sql/SqlEntityBinding.java | 32 ++ .../descriptor/sql/SqlEntityFieldBinding.java | 36 ++ .../model/sql/SqlDocumentFiltersBuilder.java | 301 +++++++++++ .../model/sql/SqlDocumentQueryBuilder.java | 240 +-------- 19 files changed, 1364 insertions(+), 947 deletions(-) create mode 100644 src/main/java/de/metas/ui/web/view/SqlViewEvaluationCtx.java create mode 100644 src/main/java/de/metas/ui/web/view/SqlViewRepository.java create mode 100644 src/main/java/de/metas/ui/web/view/descriptor/SqlViewRowFieldBinding.java create mode 100644 src/main/java/de/metas/ui/web/window/descriptor/sql/SqlEntityBinding.java create mode 100644 src/main/java/de/metas/ui/web/window/descriptor/sql/SqlEntityFieldBinding.java create mode 100644 src/main/java/de/metas/ui/web/window/model/sql/SqlDocumentFiltersBuilder.java diff --git a/src/main/java/de/metas/ui/web/handlingunits/HUEditorViewFactory.java b/src/main/java/de/metas/ui/web/handlingunits/HUEditorViewFactory.java index cf228e0d1..2d0b31051 100644 --- a/src/main/java/de/metas/ui/web/handlingunits/HUEditorViewFactory.java +++ b/src/main/java/de/metas/ui/web/handlingunits/HUEditorViewFactory.java @@ -60,7 +60,7 @@ public ViewLayout getViewLayout(final WindowId windowId, final JSONViewDataType } @Override - public Collection getViewFilters(final WindowId windowId) + public Collection getViewFilters(final WindowId windowId, final JSONViewDataType viewType) { return null; // not supported } diff --git a/src/main/java/de/metas/ui/web/pporder/PPOrderLinesViewFactory.java b/src/main/java/de/metas/ui/web/pporder/PPOrderLinesViewFactory.java index d3013b60e..48cda6e66 100644 --- a/src/main/java/de/metas/ui/web/pporder/PPOrderLinesViewFactory.java +++ b/src/main/java/de/metas/ui/web/pporder/PPOrderLinesViewFactory.java @@ -7,9 +7,9 @@ import de.metas.ui.web.pattribute.ASIRepository; import de.metas.ui.web.view.ASIViewRowAttributesProvider; +import de.metas.ui.web.view.IViewFactory; import de.metas.ui.web.view.ViewCreateRequest; import de.metas.ui.web.view.ViewFactory; -import de.metas.ui.web.view.IViewFactory; import de.metas.ui.web.view.ViewId; import de.metas.ui.web.view.descriptor.ViewLayout; import de.metas.ui.web.view.json.JSONViewDataType; @@ -70,7 +70,7 @@ public ViewLayout getViewLayout(final WindowId windowId, final JSONViewDataType } @Override - public Collection getViewFilters(final WindowId windowId) + public Collection getViewFilters(final WindowId windowId, final JSONViewDataType viewType) { return null; // not supported } diff --git a/src/main/java/de/metas/ui/web/view/IViewFactory.java b/src/main/java/de/metas/ui/web/view/IViewFactory.java index 4b900e9e2..4c843a3a9 100644 --- a/src/main/java/de/metas/ui/web/view/IViewFactory.java +++ b/src/main/java/de/metas/ui/web/view/IViewFactory.java @@ -33,7 +33,7 @@ public interface IViewFactory { ViewLayout getViewLayout(WindowId windowId, JSONViewDataType viewDataType); - Collection getViewFilters(WindowId windowId); + Collection getViewFilters(WindowId windowId, JSONViewDataType viewDataType); IView createView(ViewCreateRequest request); diff --git a/src/main/java/de/metas/ui/web/view/SqlView.java b/src/main/java/de/metas/ui/web/view/SqlView.java index 6be178120..34aff2d47 100644 --- a/src/main/java/de/metas/ui/web/view/SqlView.java +++ b/src/main/java/de/metas/ui/web/view/SqlView.java @@ -1,8 +1,5 @@ package de.metas.ui.web.view; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; import java.util.Collection; import java.util.List; import java.util.Objects; @@ -15,40 +12,31 @@ import org.adempiere.ad.dao.IQueryBL; import org.adempiere.ad.dao.impl.TypedSqlQueryFilter; -import org.adempiere.ad.expression.api.IExpressionEvaluator.OnVariableNotFound; -import org.adempiere.ad.trx.api.ITrx; import org.adempiere.exceptions.DBException; import org.adempiere.model.PlainContextAware; -import org.adempiere.util.Check; import org.adempiere.util.GuavaCollectors; import org.adempiere.util.Services; import org.adempiere.util.lang.impl.TableRecordReference; import org.compiere.util.CCache; -import org.compiere.util.DB; +import org.compiere.util.Env; import org.compiere.util.Evaluatee; import org.slf4j.Logger; import com.google.common.base.MoreObjects; -import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import de.metas.logging.LogManager; -import de.metas.ui.web.base.model.I_T_WEBUI_ViewSelection; import de.metas.ui.web.exceptions.EntityNotFoundException; import de.metas.ui.web.view.descriptor.SqlViewBinding; -import de.metas.ui.web.view.descriptor.SqlViewBinding.SqlViewRowFieldLoader; -import de.metas.ui.web.view.descriptor.SqlViewBinding.ViewFieldsBinding; import de.metas.ui.web.view.event.ViewChangesCollector; import de.metas.ui.web.window.datatypes.DocumentId; import de.metas.ui.web.window.datatypes.LookupValuesList; +import de.metas.ui.web.window.datatypes.WindowId; import de.metas.ui.web.window.datatypes.json.filters.JSONDocumentFilter; -import de.metas.ui.web.window.descriptor.DocumentEntityDescriptor; -import de.metas.ui.web.window.descriptor.DocumentFieldDescriptor.Characteristic; import de.metas.ui.web.window.descriptor.filters.DocumentFilterDescriptorsProvider; import de.metas.ui.web.window.model.DocumentQueryOrderBy; import de.metas.ui.web.window.model.filters.DocumentFilter; -import de.metas.ui.web.window.model.sql.SqlDocumentQueryBuilder; +import lombok.NonNull; /* * #%L @@ -74,23 +62,16 @@ class SqlView implements IView { - public static final Builder builder(final DocumentEntityDescriptor entityDescriptor) + public static final Builder builder(final SqlViewBinding sqlViewBinding) { - return new Builder(entityDescriptor); + return new Builder(sqlViewBinding); } private static final Logger logger = LogManager.getLogger(SqlView.class); private final AtomicBoolean closed = new AtomicBoolean(false); - private final ViewId parentViewId; - - private final String tableName; - private final String keyColumnName; - - private final transient String sqlSelectPage; - private final transient String sqlSelectById; - private final transient SqlViewRowFieldLoader sqlFieldLoaders; + private final SqlViewRepository viewRowsRepo; private final transient IViewRowIdsOrderedSelectionFactory orderedSelectionFactory; private final transient ViewRowIdsOrderedSelection defaultSelection; @@ -118,27 +99,29 @@ public static final Builder builder(final DocumentEntityDescriptor entityDescrip private SqlView(final Builder builder) { - super(); - + viewRowsRepo = new SqlViewRepository(builder.getSqlViewBinding()); parentViewId = builder.getParentViewId(); - tableName = builder.getTableName(); - keyColumnName = builder.getKeyColumnName(); - - sqlSelectPage = builder.buildSqlSelectPage(); - sqlSelectById = builder.buildSqlSelectById(); - sqlFieldLoaders = builder.getRowFieldLoaders(); - - orderedSelectionFactory = builder.getOrderedSelectionFactory(); - defaultSelection = builder.buildInitialSelection(); - selectionsByOrderBys.put(defaultSelection.getOrderBys(), defaultSelection); - // // Filters filterDescriptors = builder.getFilterDescriptors(); stickyFilters = ImmutableList.copyOf(builder.getStickyFilters()); filters = ImmutableList.copyOf(builder.getFilters()); + // + // Selection + { + final SqlViewEvaluationCtx evalCtx = SqlViewEvaluationCtx.of(Env.getCtx()); + orderedSelectionFactory = viewRowsRepo.createOrderedSelectionFactory(evalCtx); + + defaultSelection = viewRowsRepo.createOrderedSelection( + evalCtx // + , builder.getWindowId() // + , ImmutableList. builder().addAll(stickyFilters).addAll(filters).build() // + ); + selectionsByOrderBys.put(defaultSelection.getOrderBys(), defaultSelection); + } + // // Attributes attributesProvider = ViewRowAttributesProviderFactory.instance.createProviderOrNull(defaultSelection.getWindowId()); @@ -146,7 +129,7 @@ private SqlView(final Builder builder) // // Cache cache_rowsById = CCache.newLRUCache( // - tableName + "#rowById#viewId=" + defaultSelection.getSelectionId() // cache name + viewRowsRepo.getTableName() + "#rowById#viewId=" + defaultSelection.getSelectionId() // cache name , 100 // maxSize , 2 // expireAfterMinutes ); @@ -162,7 +145,7 @@ public String toString() _toString = MoreObjects.toStringHelper(this) .omitNullValues() .add("viewId", defaultSelection.getViewId()) - .add("tableName", tableName) + .add("tableName", viewRowsRepo.getTableName()) .add("parentViewId", parentViewId) .add("defaultSelection", defaultSelection) // .add("sql", sqlSelectPage) // too long.. @@ -171,13 +154,13 @@ public String toString() } return _toString; } - + @Override public ViewId getParentViewId() { return parentViewId; } - + @Override public ViewId getViewId() { @@ -187,13 +170,7 @@ public ViewId getViewId() @Override public String getTableName() { - return tableName; - } - - private String getKeyColumnName() - { - Preconditions.checkNotNull(keyColumnName, "View %s has key column", this); - return keyColumnName; + return viewRowsRepo.getTableName(); } @Override @@ -259,60 +236,15 @@ public ViewResult getPage(final int firstRow, final int pageLength, final List= 0, "firstRow >= 0 but it was {}", firstRow); - Check.assume(pageLength > 0, "pageLength > 0 but it was {}", pageLength); - + final SqlViewEvaluationCtx evalCtx = SqlViewEvaluationCtx.of(Env.getCtx()); final ViewRowIdsOrderedSelection orderedSelection = getOrderedSelection(orderBys); - logger.debug("Using: {}", orderedSelection); - - final String selectionId = orderedSelection.getSelectionId(); - final int firstSeqNo = firstRow + 1; // NOTE: firstRow is 0-based while SeqNo are 1-based - final int lastSeqNo = firstRow + pageLength; - final Object[] sqlParams = new Object[] { selectionId, firstSeqNo, lastSeqNo }; - PreparedStatement pstmt = null; - ResultSet rs = null; - try - { - pstmt = DB.prepareStatement(sqlSelectPage, ITrx.TRXNAME_ThreadInherited); - pstmt.setMaxRows(pageLength); - DB.setParameters(pstmt, sqlParams); - - rs = pstmt.executeQuery(); - - final ImmutableList.Builder pageBuilder = ImmutableList.builder(); - while (rs.next()) - { - final IViewRow row = loadViewRow(rs); - if (row == null) - { - continue; - } + final List page = viewRowsRepo.retrievePage(evalCtx, orderedSelection, firstRow, pageLength); - pageBuilder.add(row); - } + // Add to cache + page.forEach(row -> cache_rowsById.put(row.getId(), row)); - final List page = pageBuilder.build(); - - // Add to cache - for (final IViewRow row : page) - { - cache_rowsById.put(row.getId(), row); - } - - return ViewResult.ofViewAndPage(this, firstRow, pageLength, orderedSelection.getOrderBys(), page); - } - catch (final SQLException | DBException e) - { - throw DBException.wrapIfNeeded(e) - .setSqlIfAbsent(sqlSelectPage, sqlParams); - } - finally - { - DB.close(rs, pstmt); - } + return ViewResult.ofViewAndPage(this, firstRow, pageLength, orderedSelection.getOrderBys(), page); } @Override @@ -324,59 +256,10 @@ public IViewRow getById(final DocumentId rowId) private final IViewRow getOrRetrieveById(final DocumentId rowId) { - return cache_rowsById.getOrLoad(rowId, () -> retrieveById(rowId)); - } - - private final IViewRow retrieveById(final DocumentId rowId) - { - final Object[] sqlParams = new Object[] { getViewId().getViewId(), rowId.toInt() }; - PreparedStatement pstmt = null; - ResultSet rs = null; - try - { - pstmt = DB.prepareStatement(sqlSelectById, ITrx.TRXNAME_ThreadInherited); - pstmt.setMaxRows(1 + 1); - DB.setParameters(pstmt, sqlParams); - - rs = pstmt.executeQuery(); - - IViewRow firstDocument = null; - while (rs.next()) - { - final IViewRow document = loadViewRow(rs); - if (document == null) - { - continue; - } - - if (firstDocument != null) - { - logger.warn("More than one document found for rowId={} in {}. Returning only the first one: {}", rowId, this, firstDocument); - return firstDocument; - } - else - { - firstDocument = document; - } - } - - if (firstDocument == null) - { - throw new EntityNotFoundException("No document found for rowId=" + rowId); - } - - return firstDocument; - } - catch (final SQLException | DBException e) - { - throw DBException.wrapIfNeeded(e) - .setSqlIfAbsent(sqlSelectById, sqlParams); - } - finally - { - DB.close(rs, pstmt); - } - + return cache_rowsById.getOrLoad(rowId, () -> { + final SqlViewEvaluationCtx evalCtx = SqlViewEvaluationCtx.of(Env.getCtx()); + return viewRowsRepo.retrieveById(evalCtx, getViewId(), rowId); + }); } private ViewRowIdsOrderedSelection getOrderedSelection(final List orderBys) throws DBException @@ -395,40 +278,10 @@ private ViewRowIdsOrderedSelection getOrderedSelection(final List orderedSelectionFactory.createFromView(defaultSelection, orderBysImmutable)); } - private IViewRow loadViewRow(final ResultSet rs) throws SQLException - { - final ViewRow.Builder viewRowBuilder = ViewRow.builder(getViewId().getWindowId()); - final boolean loaded = sqlFieldLoaders.loadViewRowField(viewRowBuilder, rs); - if (!loaded) - { - return null; - } - - return viewRowBuilder - .setAttributesProvider(attributesProvider) - .build(); - } - @Override public String getSqlWhereClause(final Collection rowIds) { - final String sqlTableName = getTableName(); - final String sqlKeyColumnName = getKeyColumnName(); - - final StringBuilder sqlWhereClause = new StringBuilder(); - sqlWhereClause.append("exists (select 1 from " + I_T_WEBUI_ViewSelection.Table_Name + " sel " - + " where " - + " " + I_T_WEBUI_ViewSelection.COLUMNNAME_UUID + "=" + DB.TO_STRING(getViewId().getViewId()) - + " and sel." + I_T_WEBUI_ViewSelection.COLUMNNAME_Record_ID + "=" + sqlTableName + "." + sqlKeyColumnName - + ")"); - - if (!Check.isEmpty(rowIds)) - { - final Set rowIdsAsInts = DocumentId.toIntSet(rowIds); - sqlWhereClause.append(" AND ").append(sqlKeyColumnName).append(" IN ").append(DB.buildSqlList(rowIdsAsInts)); - } - - return sqlWhereClause.toString(); + return viewRowsRepo.getSqlWhereClause(getViewId(), rowIds); } @Override @@ -509,8 +362,8 @@ public void notifyRecordsChanged(final Set recordRefs) .filter(recordRef -> Objects.equals(viewTableName, recordRef.getTableName())) .map(recordRef -> DocumentId.of(recordRef.getRecord_ID())) .collect(GuavaCollectors.toImmutableSet()); - - if(rowIds.isEmpty()) + + if (rowIds.isEmpty()) { return; } @@ -530,70 +383,55 @@ public void notifyRecordsChanged(final Set recordRefs) public static final class Builder { + private WindowId windowId; private ViewId parentViewId; - - private final DocumentEntityDescriptor _entityDescriptor; - private Set _viewFieldNames; + private final SqlViewBinding sqlViewBinding; + private List _stickyFilters; private List _filters; - private SqlDocumentQueryBuilder _queryBuilder; - - - private Builder(final DocumentEntityDescriptor entityDescriptor) + private Builder(@NonNull final SqlViewBinding sqlViewBinding) { - super(); - Check.assumeNotNull(entityDescriptor, "Parameter entityDescriptor is not null"); - _entityDescriptor = entityDescriptor; + this.sqlViewBinding = sqlViewBinding; } public SqlView build() { return new SqlView(this); } - + public Builder setParentViewId(final ViewId parentViewId) { this.parentViewId = parentViewId; return this; } - - private ViewId getParentViewId() - { - return parentViewId; - } - private SqlViewRowFieldLoader getRowFieldLoaders() + public Builder setWindowId(final WindowId windowId) { - return getViewFieldsBinding().getValueLoaders(); + this.windowId = windowId; + return this; } - private SqlDocumentQueryBuilder getQueryBuilder() + public WindowId getWindowId() { - if (_queryBuilder == null) - { - _queryBuilder = SqlDocumentQueryBuilder.newInstance(getEntityDescriptor()) - .addDocumentFilters(getStickyFilters()) - .addDocumentFilters(getFilters()); - } - return _queryBuilder; + return windowId; } - private DocumentEntityDescriptor getEntityDescriptor() + private ViewId getParentViewId() { - return _entityDescriptor; + return parentViewId; } - private SqlViewBinding getBinding() + private SqlViewBinding getSqlViewBinding() { - return getQueryBuilder().getEntityBinding().getViewBinding(); + return sqlViewBinding; } private DocumentFilterDescriptorsProvider getFilterDescriptors() { - return getEntityDescriptor().getFiltersProvider(); + return sqlViewBinding.getFilterDescriptors(); } - + public Builder setStickyFilter(@Nullable final DocumentFilter stickyFilter) { _stickyFilters = stickyFilter == null ? ImmutableList.of() : ImmutableList.of(stickyFilter); @@ -621,76 +459,5 @@ private List getFilters() { return _filters; } - - public Builder setViewFieldsByCharacteristic(final Characteristic characteristic) - { - final Set viewFieldNames = getEntityDescriptor().getFieldNamesWithCharacteristic(characteristic); - if (viewFieldNames.isEmpty()) - { - throw new IllegalStateException("No fields were found for characteristic: " + characteristic); - } - - _viewFieldNames = ImmutableSet.copyOf(viewFieldNames); - - return this; - } - - private Set getViewFieldNames() - { - Check.assumeNotEmpty(_viewFieldNames, "viewFieldNames is not empty"); - return _viewFieldNames; - } - - private String getTableName() - { - return getEntityDescriptor().getTableName(); - } - - private String getKeyColumnName() - { - return getBinding().getKeyColumnName(); - } - - private ViewFieldsBinding getViewFieldsBinding() - { - return getBinding().getViewFieldsBinding(getViewFieldNames()); - } - - private String getSqlPagedSelectAllFrom() - { - // TODO: cache it locally - final SqlDocumentQueryBuilder queryBuilder = getQueryBuilder(); - final Evaluatee evalCtx = queryBuilder.getEvaluationContext(); - return getViewFieldsBinding() - .getSqlPagedSelect() - .evaluate(evalCtx, OnVariableNotFound.Fail); - } - - private String buildSqlSelectPage() - { - return getSqlPagedSelectAllFrom() - + "\n WHERE " + SqlViewBinding.COLUMNNAME_Paging_UUID + "=?" - + "\n AND " + SqlViewBinding.COLUMNNAME_Paging_SeqNo + " BETWEEN ? AND ?" - + "\n ORDER BY " + SqlViewBinding.COLUMNNAME_Paging_SeqNo; - } - - private String buildSqlSelectById() - { - return getSqlPagedSelectAllFrom() - + "\n WHERE " + SqlViewBinding.COLUMNNAME_Paging_UUID + "=?" - + "\n AND " + SqlViewBinding.COLUMNNAME_Paging_Record_ID + "=?"; - } - - public IViewRowIdsOrderedSelectionFactory getOrderedSelectionFactory() - { - final Evaluatee evalCtx = getQueryBuilder().getEvaluationContext(); - return getBinding().createOrderedSelectionFactory(evalCtx); - } - - private ViewRowIdsOrderedSelection buildInitialSelection() - { - final SqlDocumentQueryBuilder queryBuilder = getQueryBuilder(); - return getBinding().createOrderedSelection(queryBuilder); - } } } diff --git a/src/main/java/de/metas/ui/web/view/SqlViewEvaluationCtx.java b/src/main/java/de/metas/ui/web/view/SqlViewEvaluationCtx.java new file mode 100644 index 000000000..b05fb71e6 --- /dev/null +++ b/src/main/java/de/metas/ui/web/view/SqlViewEvaluationCtx.java @@ -0,0 +1,75 @@ +package de.metas.ui.web.view; + +import java.util.Properties; + +import org.adempiere.ad.security.UserRolePermissionsKey; +import org.adempiere.ad.security.impl.AccessSqlStringExpression; +import org.compiere.util.Env; +import org.compiere.util.Evaluatee; +import org.compiere.util.Evaluatees; + +import com.google.common.base.MoreObjects; + +import lombok.NonNull; + +/* + * #%L + * metasfresh-webui-api + * %% + * Copyright (C) 2017 metas GmbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +public class SqlViewEvaluationCtx +{ + public static final SqlViewEvaluationCtx of(final Properties ctx) + { + final String adLanguage = Env.getAD_Language(ctx); + final UserRolePermissionsKey permissionsKey = UserRolePermissionsKey.of(ctx); + return new SqlViewEvaluationCtx(ctx, adLanguage, permissionsKey); + } + + private final Properties ctx; // needed for global context vars + private final String adLanguage; + private final UserRolePermissionsKey permissionsKey; + + private SqlViewEvaluationCtx(@NonNull final Properties ctx, @NonNull final String adLanguage, @NonNull final UserRolePermissionsKey permissionsKey) + { + this.ctx = ctx; + this.adLanguage = adLanguage; + this.permissionsKey = permissionsKey; + } + + @Override + public String toString() + { + return MoreObjects.toStringHelper(this) + .add("adLanguage", adLanguage) + .add("permissionsKey", permissionsKey) + .toString(); + } + + public Evaluatee toEvaluatee() + { + return Evaluatees.mapBuilder() + .put(Env.CTXNAME_AD_Language, adLanguage) + .put(AccessSqlStringExpression.PARAM_UserRolePermissionsKey.getName(), permissionsKey.toPermissionsKeyString()) + .build() + // Fallback to global context + .andComposeWith(Evaluatees.ofCtx(ctx)); + } +} diff --git a/src/main/java/de/metas/ui/web/view/SqlViewFactory.java b/src/main/java/de/metas/ui/web/view/SqlViewFactory.java index cc57def4d..872fa00ab 100644 --- a/src/main/java/de/metas/ui/web/view/SqlViewFactory.java +++ b/src/main/java/de/metas/ui/web/view/SqlViewFactory.java @@ -1,22 +1,27 @@ package de.metas.ui.web.view; import java.util.Collection; +import java.util.Set; +import org.compiere.util.CCache; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import de.metas.ui.web.view.descriptor.SqlViewBinding; import de.metas.ui.web.view.descriptor.ViewLayout; import de.metas.ui.web.view.json.JSONViewDataType; import de.metas.ui.web.window.datatypes.DocumentPath; import de.metas.ui.web.window.datatypes.WindowId; -import de.metas.ui.web.window.descriptor.DocumentDescriptor; import de.metas.ui.web.window.descriptor.DocumentEntityDescriptor; +import de.metas.ui.web.window.descriptor.DocumentFieldDescriptor.Characteristic; import de.metas.ui.web.window.descriptor.DocumentLayoutDescriptor; import de.metas.ui.web.window.descriptor.factory.DocumentDescriptorFactory; import de.metas.ui.web.window.descriptor.filters.DocumentFilterDescriptor; +import de.metas.ui.web.window.descriptor.sql.SqlDocumentEntityDataBindingDescriptor; import de.metas.ui.web.window.model.DocumentReference; import de.metas.ui.web.window.model.DocumentReferencesService; import de.metas.ui.web.window.model.filters.DocumentFilter; +import lombok.Value; /* * #%L @@ -48,6 +53,8 @@ public class SqlViewFactory implements IViewFactory @Autowired private DocumentReferencesService documentReferencesService; + private final transient CCache viewBindings = CCache.newCache("SqlViewBindings", 20, 0); + @Override public ViewLayout getViewLayout(final WindowId windowId, final JSONViewDataType viewDataType) { @@ -70,12 +77,10 @@ public ViewLayout getViewLayout(final WindowId windowId, final JSONViewDataType } @Override - public Collection getViewFilters(final WindowId windowId) + public Collection getViewFilters(final WindowId windowId, final JSONViewDataType viewType) { - final DocumentDescriptor descriptor = documentDescriptorFactory.getDocumentDescriptor(windowId); - final DocumentEntityDescriptor entityDescriptor = descriptor.getEntityDescriptor(); - final Collection filters = entityDescriptor.getFiltersProvider().getAll(); - return filters; + final SqlViewBindingKey sqlViewBindingKey = new SqlViewBindingKey(windowId, viewType.getRequiredFieldCharacteristic()); + return getViewBinding(sqlViewBindingKey).getFilterDescriptors().getAll(); } @Override @@ -86,16 +91,12 @@ public IView createView(final ViewCreateRequest request) throw new IllegalArgumentException("Filtering by Ids are not supported: " + request); } - final DocumentEntityDescriptor entityDescriptor = documentDescriptorFactory.getDocumentEntityDescriptor(request.getWindowId()); - return SqlView.builder(entityDescriptor) - // + final SqlViewBindingKey sqlViewBindingKey = new SqlViewBindingKey(request.getWindowId(), request.getViewTypeRequiredFieldCharacteristic()); + return SqlView.builder(getViewBinding(sqlViewBindingKey)) + .setWindowId(request.getWindowId()) .setParentViewId(request.getParentViewId()) - // - .setViewFieldsByCharacteristic(request.getViewTypeRequiredFieldCharacteristic()) - // - .setStickyFilter(extractReferencedDocumentFilter(entityDescriptor.getWindowId(), request.getSingleReferencingDocumentPathOrNull())) + .setStickyFilter(extractReferencedDocumentFilter(request.getWindowId(), request.getSingleReferencingDocumentPathOrNull())) .setFiltersFromJSON(request.getFilters()) - // .build(); } @@ -112,4 +113,32 @@ private final DocumentFilter extractReferencedDocumentFilter(final WindowId targ } } + private SqlViewBinding getViewBinding(final SqlViewBindingKey key) + { + return viewBindings.getOrLoad(key, () -> createViewBinding(key)); + } + + private SqlViewBinding createViewBinding(final SqlViewBindingKey key) + { + final DocumentEntityDescriptor entityDescriptor = documentDescriptorFactory.getDocumentEntityDescriptor(key.getWindowId()); + final Set displayFieldNames = entityDescriptor.getFieldNamesWithCharacteristic(key.getRequiredFieldCharacteristic()); + + final SqlDocumentEntityDataBindingDescriptor entityBinding = SqlDocumentEntityDataBindingDescriptor.cast(entityDescriptor.getDataBinding()); + + return SqlViewBinding.builder() + .setTableName(entityBinding.getTableName()) + .setTableAlias(entityBinding.getTableAlias()) + .setDisplayFieldNames(displayFieldNames) + .addFields(entityBinding.getFields()) + .setFilterDescriptors(entityDescriptor.getFiltersProvider()) + .setOrderBys(entityBinding.getOrderBys()) + .build(); + } + + @Value + private static final class SqlViewBindingKey + { + private final WindowId windowId; + private final Characteristic requiredFieldCharacteristic; + } } diff --git a/src/main/java/de/metas/ui/web/view/SqlViewRepository.java b/src/main/java/de/metas/ui/web/view/SqlViewRepository.java new file mode 100644 index 000000000..ebcd1f922 --- /dev/null +++ b/src/main/java/de/metas/ui/web/view/SqlViewRepository.java @@ -0,0 +1,231 @@ +package de.metas.ui.web.view; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; +import java.util.List; + +import org.adempiere.ad.expression.api.IExpressionEvaluator.OnVariableNotFound; +import org.adempiere.ad.trx.api.ITrx; +import org.adempiere.exceptions.DBException; +import org.adempiere.util.Check; +import org.compiere.util.DB; +import org.compiere.util.Evaluatee; +import org.slf4j.Logger; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; + +import de.metas.logging.LogManager; +import de.metas.ui.web.exceptions.EntityNotFoundException; +import de.metas.ui.web.view.descriptor.SqlViewBinding; +import de.metas.ui.web.view.descriptor.SqlViewRowFieldBinding; +import de.metas.ui.web.view.descriptor.SqlViewRowFieldBinding.SqlViewRowFieldLoader; +import de.metas.ui.web.window.datatypes.DocumentId; +import de.metas.ui.web.window.datatypes.WindowId; +import de.metas.ui.web.window.model.filters.DocumentFilter; +import lombok.NonNull; + +/* + * #%L + * metasfresh-webui-api + * %% + * Copyright (C) 2017 metas GmbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +class SqlViewRepository +{ + private static final Logger logger = LogManager.getLogger(SqlViewRepository.class); + + private final SqlViewBinding sqlBindings; + + SqlViewRepository(@NonNull final SqlViewBinding sqlBindings) + { + this.sqlBindings = sqlBindings; + } + + @Override + public String toString() + { + return MoreObjects.toStringHelper(this).addValue(sqlBindings).toString(); + } + + public String getTableName() + { + return sqlBindings.getTableName(); + } + + public String getSqlWhereClause(final ViewId viewId, final Collection rowIds) + { + return sqlBindings.getSqlWhereClause(viewId.getViewId(), rowIds); + } + + public IViewRowIdsOrderedSelectionFactory createOrderedSelectionFactory(final SqlViewEvaluationCtx evalCtx) + { + return sqlBindings.createOrderedSelectionFactory(evalCtx); + } + + public ViewRowIdsOrderedSelection createOrderedSelection(final SqlViewEvaluationCtx viewEvalCtx, final WindowId windowId, final List filters) + { + return sqlBindings.createOrderedSelection(viewEvalCtx, windowId, filters); + } + + public IViewRow retrieveById(final SqlViewEvaluationCtx viewEvalCtx, final ViewId viewId, final DocumentId rowId) + { + final WindowId windowId = viewId.getWindowId(); + final String viewSelectionId = viewId.getViewId(); + final IViewRowAttributesProvider attributesProvider = null; // TODO + + final Evaluatee evalCtx = viewEvalCtx.toEvaluatee(); + final String sql = sqlBindings.getSqlSelectById().evaluate(evalCtx, OnVariableNotFound.Fail); + + final Object[] sqlParams = new Object[] { viewSelectionId, rowId.toInt() }; + PreparedStatement pstmt = null; + ResultSet rs = null; + try + { + pstmt = DB.prepareStatement(sql, ITrx.TRXNAME_ThreadInherited); + pstmt.setMaxRows(1 + 1); + DB.setParameters(pstmt, sqlParams); + + rs = pstmt.executeQuery(); + + IViewRow firstDocument = null; + while (rs.next()) + { + final IViewRow document = loadViewRow(rs, windowId, attributesProvider); + if (document == null) + { + continue; + } + + if (firstDocument != null) + { + logger.warn("More than one document found for rowId={} in {}. Returning only the first one: {}", rowId, this, firstDocument); + return firstDocument; + } + else + { + firstDocument = document; + } + } + + if (firstDocument == null) + { + throw new EntityNotFoundException("No document found for rowId=" + rowId); + } + + return firstDocument; + } + catch (final SQLException | DBException e) + { + throw DBException.wrapIfNeeded(e) + .setSqlIfAbsent(sql, sqlParams); + } + finally + { + DB.close(rs, pstmt); + } + } + + private IViewRow loadViewRow(final ResultSet rs, final WindowId windowId, final IViewRowAttributesProvider attributesProvider) throws SQLException + { + final ViewRow.Builder viewRowBuilder = ViewRow.builder(windowId) + .setAttributesProvider(attributesProvider); + + for (final SqlViewRowFieldBinding field : sqlBindings.getFields()) + { + final String fieldName = field.getFieldName(); + final boolean keyColumn = field.isKeyColumn(); + final SqlViewRowFieldLoader fieldLoader = field.getFieldLoader(); + final Object value = fieldLoader.retrieveValueAsJson(rs); + + if (keyColumn) + { + if (value == null) + { + logger.warn("No ID found for current row. Skipping the row."); + return null; + } + + viewRowBuilder.setRowIdFromObject(value); + } + + viewRowBuilder.putFieldValue(fieldName, value); + } + + return viewRowBuilder.build(); + } + + public List retrievePage(final SqlViewEvaluationCtx viewEvalCtx, final ViewRowIdsOrderedSelection orderedSelection, final int firstRow, final int pageLength) throws DBException + { + logger.debug("Getting page: firstRow={}, pageLength={} - {}", firstRow, pageLength, this); + + Check.assume(firstRow >= 0, "firstRow >= 0 but it was {}", firstRow); + Check.assume(pageLength > 0, "pageLength > 0 but it was {}", pageLength); + + logger.debug("Using: {}", orderedSelection); + final WindowId windowId = orderedSelection.getWindowId(); + final String viewSelectionId = orderedSelection.getSelectionId(); + + final IViewRowAttributesProvider attributesProvider = null; // TODO + + final int firstSeqNo = firstRow + 1; // NOTE: firstRow is 0-based while SeqNo are 1-based + final int lastSeqNo = firstRow + pageLength; + + final Evaluatee evalCtx = viewEvalCtx.toEvaluatee(); + final String sql = sqlBindings.getSqlSelectByPage().evaluate(evalCtx, OnVariableNotFound.Fail); + final Object[] sqlParams = new Object[] { viewSelectionId, firstSeqNo, lastSeqNo }; + PreparedStatement pstmt = null; + ResultSet rs = null; + try + { + pstmt = DB.prepareStatement(sql, ITrx.TRXNAME_ThreadInherited); + pstmt.setMaxRows(pageLength); + DB.setParameters(pstmt, sqlParams); + + rs = pstmt.executeQuery(); + + final ImmutableList.Builder pageBuilder = ImmutableList.builder(); + while (rs.next()) + { + final IViewRow row = loadViewRow(rs, windowId, attributesProvider); + if (row == null) + { + continue; + } + + pageBuilder.add(row); + } + + final List page = pageBuilder.build(); + return page; + } + catch (final SQLException | DBException e) + { + throw DBException.wrapIfNeeded(e) + .setSqlIfAbsent(sql, sqlParams); + } + finally + { + DB.close(rs, pstmt); + } + } + +} diff --git a/src/main/java/de/metas/ui/web/view/ViewRow.java b/src/main/java/de/metas/ui/web/view/ViewRow.java index ec2abf1e2..40fc56c1d 100644 --- a/src/main/java/de/metas/ui/web/view/ViewRow.java +++ b/src/main/java/de/metas/ui/web/view/ViewRow.java @@ -17,6 +17,7 @@ import de.metas.ui.web.window.datatypes.WindowId; import de.metas.ui.web.window.datatypes.json.JSONLookupValue; import lombok.NonNull; +import lombok.ToString; /* * #%L @@ -159,10 +160,10 @@ public List getIncludedRows() // // // + @ToString public static final class Builder { private final WindowId windowId; - private String idFieldName; private DocumentId _rowId; private IViewRowType type; private Boolean processed; @@ -189,55 +190,53 @@ private DocumentPath getDocumentPath() return DocumentPath.rootDocumentPath(windowId, documentId); } - public Builder setIdFieldName(final String idFieldName) + public Builder setRowId(final DocumentId rowId) { - this.idFieldName = idFieldName; + _rowId = rowId; return this; } - public Builder setRowId(final DocumentId rowId) + public Builder setRowIdFromObject(final Object jsonRowIdObj) { - _rowId = rowId; + setRowId(convertToRowId(jsonRowIdObj)); return this; } - /** @return view row ID */ - private DocumentId getRowId() + private static final DocumentId convertToRowId(@NonNull final Object jsonRowIdObj) { - if (_rowId != null) + if (jsonRowIdObj instanceof DocumentId) { - return _rowId; + return (DocumentId)jsonRowIdObj; } - - if (idFieldName == null) - { - throw new IllegalStateException("No idFieldName was specified"); - } - - final Object idJson = values.get(idFieldName); - - if (idJson == null) + else if (jsonRowIdObj instanceof Integer) { - throw new IllegalArgumentException("No ID found for " + idFieldName); + return DocumentId.of((Integer)jsonRowIdObj); } - if (idJson instanceof Integer) + else if (jsonRowIdObj instanceof String) { - return DocumentId.of((Integer)idJson); + return DocumentId.of(jsonRowIdObj.toString()); } - else if (idJson instanceof String) - { - return DocumentId.of(idJson.toString()); - } - else if (idJson instanceof JSONLookupValue) + else if (jsonRowIdObj instanceof JSONLookupValue) { // case: usually this is happening when a view's column which is Lookup is also marked as KEY. - final JSONLookupValue jsonLookupValue = (JSONLookupValue)idJson; + final JSONLookupValue jsonLookupValue = (JSONLookupValue)jsonRowIdObj; return DocumentId.of(jsonLookupValue.getKey()); } else { - throw new IllegalArgumentException("Cannot convert id '" + idJson + "' (" + idJson.getClass() + ") to integer"); + throw new IllegalArgumentException("Cannot convert id '" + jsonRowIdObj + "' (" + jsonRowIdObj.getClass() + ") to integer"); + } + + } + + /** @return view row ID */ + private DocumentId getRowId() + { + if (_rowId == null) + { + throw new IllegalStateException("No rowId was provided for " + this); } + return _rowId; } private IViewRowType getType() diff --git a/src/main/java/de/metas/ui/web/view/ViewsRepository.java b/src/main/java/de/metas/ui/web/view/ViewsRepository.java index 9dd771dc9..fe04d07e0 100644 --- a/src/main/java/de/metas/ui/web/view/ViewsRepository.java +++ b/src/main/java/de/metas/ui/web/view/ViewsRepository.java @@ -26,8 +26,8 @@ import de.metas.ui.web.exceptions.EntityNotFoundException; import de.metas.ui.web.menu.MenuTreeRepository; import de.metas.ui.web.view.descriptor.ViewLayout; -import de.metas.ui.web.view.json.JSONViewLayout; import de.metas.ui.web.view.json.JSONViewDataType; +import de.metas.ui.web.view.json.JSONViewLayout; import de.metas.ui.web.window.datatypes.WindowId; import de.metas.ui.web.window.datatypes.json.JSONOptions; import de.metas.ui.web.window.descriptor.filters.DocumentFilterDescriptor; @@ -129,7 +129,7 @@ public JSONViewLayout getViewLayout(final WindowId windowId, final JSONViewDataT { final IViewFactory factory = getFactory(windowId, viewDataType); final ViewLayout viewLayout = factory.getViewLayout(windowId, viewDataType); - final Collection viewFilters = factory.getViewFilters(windowId); + final Collection viewFilters = factory.getViewFilters(windowId, viewDataType); final JSONViewLayout jsonLayout = JSONViewLayout.of(viewLayout, viewFilters, jsonOpts); // diff --git a/src/main/java/de/metas/ui/web/view/descriptor/SqlViewBinding.java b/src/main/java/de/metas/ui/web/view/descriptor/SqlViewBinding.java index a2f13fbf9..b0aa12e56 100644 --- a/src/main/java/de/metas/ui/web/view/descriptor/SqlViewBinding.java +++ b/src/main/java/de/metas/ui/web/view/descriptor/SqlViewBinding.java @@ -1,48 +1,52 @@ package de.metas.ui.web.view.descriptor; -import java.sql.ResultSet; -import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import org.adempiere.ad.expression.api.IExpressionEvaluator.OnVariableNotFound; import org.adempiere.ad.expression.api.IStringExpression; import org.adempiere.ad.expression.api.impl.CompositeStringExpression; import org.adempiere.ad.expression.api.impl.ConstantStringExpression; import org.adempiere.ad.security.IUserRolePermissions; +import org.adempiere.ad.security.IUserRolePermissionsDAO; +import org.adempiere.ad.security.UserRolePermissionsKey; import org.adempiere.ad.security.impl.AccessSqlStringExpression; import org.adempiere.ad.security.permissions.WindowMaxQueryRecordsConstraint; import org.adempiere.ad.trx.api.ITrx; import org.adempiere.util.Check; +import org.adempiere.util.Services; import org.compiere.util.DB; import org.compiere.util.Evaluatee; import org.slf4j.Logger; +import com.google.common.base.Joiner; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import de.metas.logging.LogManager; import de.metas.ui.web.base.model.I_T_WEBUI_ViewSelection; import de.metas.ui.web.view.IViewRowIdsOrderedSelectionFactory; +import de.metas.ui.web.view.SqlViewEvaluationCtx; import de.metas.ui.web.view.ViewId; -import de.metas.ui.web.view.ViewRow; import de.metas.ui.web.view.ViewRowIdsOrderedSelection; -import de.metas.ui.web.window.datatypes.Values; +import de.metas.ui.web.view.descriptor.SqlViewRowFieldBinding.SqlViewRowFieldLoader; +import de.metas.ui.web.window.datatypes.DocumentId; import de.metas.ui.web.window.datatypes.WindowId; +import de.metas.ui.web.window.descriptor.filters.DocumentFilterDescriptorsProvider; +import de.metas.ui.web.window.descriptor.filters.NullDocumentFilterDescriptorsProvider; import de.metas.ui.web.window.descriptor.sql.SqlDocumentFieldDataBindingDescriptor; -import de.metas.ui.web.window.descriptor.sql.SqlDocumentFieldDataBindingDescriptor.DocumentFieldValueLoader; +import de.metas.ui.web.window.descriptor.sql.SqlEntityBinding; import de.metas.ui.web.window.model.DocumentQueryOrderBy; -import de.metas.ui.web.window.model.sql.SqlDocumentQueryBuilder; -import lombok.Value; +import de.metas.ui.web.window.model.filters.DocumentFilter; +import de.metas.ui.web.window.model.sql.SqlDocumentFiltersBuilder; +import lombok.NonNull; /* * #%L @@ -66,7 +70,7 @@ * #L% */ -public class SqlViewBinding +public class SqlViewBinding implements SqlEntityBinding { public static final Builder builder() { @@ -85,10 +89,16 @@ public static final Builder builder() private final String _tableName; private final String _tableAlias; - private final Map _fieldsByFieldName; - private final SqlDocumentFieldDataBindingDescriptor _keyField; - private final ConcurrentHashMap, ViewFieldsBinding> _viewFieldsBindings = new ConcurrentHashMap<>(); + private final ImmutableMap _fieldsByFieldName; + private final SqlViewRowFieldBinding _keyField; + + private final IStringExpression sqlSelectByPage; + private final IStringExpression sqlSelectById; + private final List rowFieldLoaders; + + private final List orderBys; + private final DocumentFilterDescriptorsProvider filterDescriptors; private SqlViewBinding(final Builder builder) { @@ -97,27 +107,69 @@ private SqlViewBinding(final Builder builder) _tableAlias = builder.getTableAlias(); _fieldsByFieldName = ImmutableMap.copyOf(builder.getFieldsByFieldName()); _keyField = builder.getKeyField(); + + final Collection displayFieldNames = builder.getDisplayFieldNames(); + + final Collection allFields = _fieldsByFieldName.values(); + final IStringExpression sqlSelect = buildSqlSelect(_tableName, _tableAlias, _keyField.getColumnName(), displayFieldNames, allFields); + + sqlSelectByPage = sqlSelect.toComposer() + .append("\n WHERE ") + .append("\n " + SqlViewBinding.COLUMNNAME_Paging_UUID + "=?") + .append("\n AND " + SqlViewBinding.COLUMNNAME_Paging_SeqNo + " BETWEEN ? AND ?") + .append("\n ORDER BY " + SqlViewBinding.COLUMNNAME_Paging_SeqNo) + .build(); + + sqlSelectById = sqlSelect.toComposer() + .append("\n WHERE ") + .append("\n " + SqlViewBinding.COLUMNNAME_Paging_UUID + "=?") + .append("\n AND " + SqlViewBinding.COLUMNNAME_Paging_Record_ID + "=?") + .build(); + + final List rowFieldLoaders = new ArrayList<>(allFields.size()); + for (final SqlViewRowFieldBinding field : allFields) + { + final boolean keyColumn = field.isKeyColumn(); + final SqlViewRowFieldLoader rowFieldLoader = field.getFieldLoader(); + + if (keyColumn) + { + // If it's key column, add it first, because in case the record is missing, we want to fail fast + rowFieldLoaders.add(0, rowFieldLoader); + } + else + { + rowFieldLoaders.add(rowFieldLoader); + } + } + this.rowFieldLoaders = ImmutableList.copyOf(rowFieldLoaders); + + orderBys = ImmutableList.copyOf(builder.getOrderBys()); + filterDescriptors = builder.getFilterDescriptors(); } @Override public String toString() { + // NOTE: keep it short return MoreObjects.toStringHelper(this) .add("tableName", _tableName) .toString(); } + @Override public String getTableName() { return _tableName; } + @Override public String getTableAlias() { return _tableAlias; } - private SqlDocumentFieldDataBindingDescriptor getKeyField() + private SqlViewRowFieldBinding getKeyField() { Preconditions.checkNotNull(_keyField, "View %s does not have a key column defined", this); return _keyField; @@ -128,14 +180,15 @@ public String getKeyColumnName() return getKeyField().getColumnName(); } - private Collection getFields() + public Collection getFields() { return _fieldsByFieldName.values(); } - private SqlDocumentFieldDataBindingDescriptor getFieldByFieldName(final String fieldName) + @Override + public SqlViewRowFieldBinding getFieldByFieldName(final String fieldName) { - final SqlDocumentFieldDataBindingDescriptor field = _fieldsByFieldName.get(fieldName); + final SqlViewRowFieldBinding field = _fieldsByFieldName.get(fieldName); if (field == null) { throw new IllegalArgumentException("No field found for '" + fieldName + "' in " + this); @@ -143,24 +196,21 @@ private SqlDocumentFieldDataBindingDescriptor getFieldByFieldName(final String f return field; } - public ViewFieldsBinding getViewFieldsBinding(final Collection fieldNames) + private static IStringExpression buildSqlSelect( // + final String sqlTableName // + , final String sqlTableAlias // + , final String sqlKeyColumnName // + , final Collection displayFieldNames // + , final Collection allFields + ) { - return _viewFieldsBindings.computeIfAbsent(ImmutableSet.copyOf(fieldNames), fieldNamesEffective -> { - IStringExpression sqlPagedSelect = buildSqlPagedSelect(fieldNamesEffective); - SqlViewRowFieldLoader valueLoaders = createRowFieldLoaders(fieldNamesEffective); - return new ViewFieldsBinding(sqlPagedSelect, valueLoaders); - }); - } - - private IStringExpression buildSqlPagedSelect(final Set fieldNames) - { - final String sqlTableName = getTableName(); - final String sqlTableAlias = getTableAlias(); - final String sqlKeyColumnName = getKeyField().getColumnName(); + // final String sqlTableName = getTableName(); + // final String sqlTableAlias = getTableAlias(); + // final String sqlKeyColumnName = getKeyField().getColumnName(); - final List sqlSelectValuesList = new ArrayList<>(); + final List sqlSelectValuesList = new ArrayList<>(); final List sqlSelectDisplayNamesList = new ArrayList<>(); - getFields().forEach(field -> { + allFields.forEach(field -> { // Collect the SQL select for internal value // NOTE: we need to collect all fields because, even if the field is not needed it might be present in some where clause sqlSelectValuesList.add(field.getSqlSelectValue()); @@ -168,7 +218,7 @@ private IStringExpression buildSqlPagedSelect(final Set fieldNames) // Collect the SQL select for displayed value, // * if there is one // * and if it was required by caller (i.e. present in fieldNames list) - if (field.isUsingDisplayColumn() && fieldNames.contains(field.getFieldName())) + if (field.isUsingDisplayColumn() && displayFieldNames.contains(field.getFieldName())) { sqlSelectDisplayNamesList.add(field.getSqlSelectDisplayValue()); } @@ -184,7 +234,7 @@ private IStringExpression buildSqlPagedSelect(final Set fieldNames) .append(", \n").appendAllJoining("\n, ", sqlSelectDisplayNamesList) // DisplayName fields .append("\n FROM (") .append("\n SELECT ") - .append("\n ").appendAllJoining(", ", sqlSelectValuesList) + .append("\n ").append(Joiner.on("\n , ").join(sqlSelectValuesList)) .append("\n , sel." + I_T_WEBUI_ViewSelection.COLUMNNAME_Line + " AS " + COLUMNNAME_Paging_SeqNo) .append("\n , sel." + I_T_WEBUI_ViewSelection.COLUMNNAME_UUID + " AS " + COLUMNNAME_Paging_UUID) .append("\n , sel." + I_T_WEBUI_ViewSelection.COLUMNNAME_Record_ID + " AS " + COLUMNNAME_Paging_Record_ID) @@ -198,7 +248,7 @@ private IStringExpression buildSqlPagedSelect(final Set fieldNames) { return IStringExpression.composer() .append("SELECT ") - .append("\n ").appendAllJoining("\n, ", sqlSelectValuesList) + .append("\n ").append(Joiner.on("\n , ").join(sqlSelectValuesList)) .append("\n , sel." + I_T_WEBUI_ViewSelection.COLUMNNAME_Line + " AS " + COLUMNNAME_Paging_SeqNo) .append("\n , sel." + I_T_WEBUI_ViewSelection.COLUMNNAME_UUID + " AS " + COLUMNNAME_Paging_UUID) .append("\n , sel." + I_T_WEBUI_ViewSelection.COLUMNNAME_Record_ID + " AS " + COLUMNNAME_Paging_Record_ID) @@ -210,100 +260,7 @@ private IStringExpression buildSqlPagedSelect(final Set fieldNames) } } - private SqlViewRowFieldLoader createRowFieldLoaders(final Set fieldNames) - { - final Collection fields = getFields(); - - final List rowFieldLoaders = new ArrayList<>(fields.size()); - for (final SqlDocumentFieldDataBindingDescriptor field : fields) - { - if (field == null) - { - logger.warn("No SQL databinding provided for {}. Skip creating the field loader", field); - continue; - } - - final String fieldName = field.getFieldName(); - final boolean keyColumn = field.isKeyColumn(); - final DocumentFieldValueLoader documentFieldLoader = field.getDocumentFieldValueLoader(); - final boolean isDisplayColumnAvailable = fieldNames.contains(fieldName); - final SqlViewRowFieldLoader rowFieldLoader = createRowFieldLoader(fieldName, keyColumn, documentFieldLoader, isDisplayColumnAvailable); - - if (keyColumn) - { - // If it's key column, add it first, because in case the record is missing, we want to fail fast - rowFieldLoaders.add(0, rowFieldLoader); - } - else - { - rowFieldLoaders.add(rowFieldLoader); - } - } - return CompositeSqlViewRowFieldLoader.of(rowFieldLoaders); - } - - /** - * NOTE to developer: keep this method static and provide only primitive or lambda parameters - * - * @param fieldName - * @param keyColumn - * @param fieldValueLoader - * @return - */ - private static SqlViewRowFieldLoader createRowFieldLoader( // - final String fieldName // - , final boolean keyColumn // - , final DocumentFieldValueLoader fieldValueLoader // - , final boolean isDisplayColumnAvailable // - ) - { - Check.assumeNotNull(fieldValueLoader, "Parameter fieldValueLoader is not null"); - - if (keyColumn) - { - return (viewRowBuilder, rs) -> { - // If document is not present anymore in our view (i.e. the Key is null) then we shall skip it. - final Object fieldValue = fieldValueLoader.retrieveFieldValue(rs, isDisplayColumnAvailable); - if (fieldValue == null) - { - // Debugging info - if (logger.isDebugEnabled()) - { - Integer recordId = null; - Integer seqNo = null; - try - { - recordId = rs.getInt(SqlViewBinding.COLUMNNAME_Paging_Record_ID); - seqNo = rs.getInt(SqlViewBinding.COLUMNNAME_Paging_SeqNo); - } - catch (final Exception e) - { - } - - logger.debug("Skip missing record: Record_ID={}, SeqNo={}", recordId, seqNo); - } - - return false; // not loaded - } - - viewRowBuilder.setIdFieldName(fieldName); - - final Object jsonValue = Values.valueToJsonObject(fieldValue); - viewRowBuilder.putFieldValue(fieldName, jsonValue); - - return true; // ok, loaded - }; - } - - return (viewRowBuilder, rs) -> { - final Object fieldValue = fieldValueLoader.retrieveFieldValue(rs, isDisplayColumnAvailable); - final Object jsonValue = Values.valueToJsonObject(fieldValue); - viewRowBuilder.putFieldValue(fieldName, jsonValue); - return true; // ok, loaded - }; - } - - private final IStringExpression buildSqlFullOrderBy(final List orderBys) + private final IStringExpression buildSqlOrderBy(final List orderBys) { if (orderBys.isEmpty()) { @@ -312,54 +269,71 @@ private final IStringExpression buildSqlFullOrderBy(final List buildSqlFullOrderBy(orderBy)) + .map(orderBy -> getFieldByFieldName(orderBy.getFieldName()).getSqlOrderBy(orderBy.isAscending())) .filter(sql -> sql != null && !sql.isNullExpression()) .collect(IStringExpression.collectJoining(", ")); return sqlOrderByFinal; } - private final IStringExpression buildSqlFullOrderBy(final DocumentQueryOrderBy orderBy) + public IStringExpression getSqlSelectByPage() { - final String fieldName = orderBy.getFieldName(); - final SqlDocumentFieldDataBindingDescriptor fieldBinding = getFieldByFieldName(fieldName); - return fieldBinding.buildSqlFullOrderBy(orderBy.isAscending()); + return sqlSelectByPage; } - private Map getAvailableFieldFullOrderBys(final Evaluatee evalCtx) + public IStringExpression getSqlSelectById() { - final ImmutableMap.Builder result = ImmutableMap.builder(); - for (final SqlDocumentFieldDataBindingDescriptor fieldBinding : getFields()) - { - final String fieldName = fieldBinding.getFieldName(); - final String fieldOrderBy = fieldBinding.getSqlFullOrderBy() - .evaluate(evalCtx, OnVariableNotFound.Fail); + return sqlSelectById; + } - if (Check.isEmpty(fieldOrderBy, true)) - { - continue; - } + public String getSqlWhereClause(final String selectionId, final Collection rowIds) + { + final String sqlTableName = getTableName(); + final String sqlKeyColumnName = getKeyColumnName(); + + final StringBuilder sqlWhereClause = new StringBuilder(); + sqlWhereClause.append("exists (select 1 from " + I_T_WEBUI_ViewSelection.Table_Name + " sel " + + " where " + + " " + I_T_WEBUI_ViewSelection.COLUMNNAME_UUID + "=" + DB.TO_STRING(selectionId) + + " and sel." + I_T_WEBUI_ViewSelection.COLUMNNAME_Record_ID + "=" + sqlTableName + "." + sqlKeyColumnName + + ")"); - result.put(fieldName, fieldOrderBy); + if (!Check.isEmpty(rowIds)) + { + final Set rowIdsAsInts = DocumentId.toIntSet(rowIds); + sqlWhereClause.append(" AND ").append(sqlKeyColumnName).append(" IN ").append(DB.buildSqlList(rowIdsAsInts)); } - return result.build(); + return sqlWhereClause.toString(); + + } + + public List getRowFieldLoaders() + { + return rowFieldLoaders; + } + + public List getOrderBys() + { + return orderBys; } - public ViewRowIdsOrderedSelection createOrderedSelection(final SqlDocumentQueryBuilder queryBuilder) + public DocumentFilterDescriptorsProvider getFilterDescriptors() { - final Evaluatee evalCtx = queryBuilder.getEvaluationContext(); + return filterDescriptors; + } + public ViewRowIdsOrderedSelection createOrderedSelection( // + final SqlViewEvaluationCtx viewEvalCtx // + , final WindowId windowId // + , final List filters // + ) + { + final Evaluatee evalCtx = viewEvalCtx.toEvaluatee(); final String sqlTableName = getTableName(); final String sqlTableAlias = getTableAlias(); final String keyColumnNameFQ = getKeyColumnName(); - final IStringExpression sqlWhereClause = queryBuilder.getSqlWhere(); - final List sqlWhereClauseParams = queryBuilder.getSqlWhereParams(); - - final List orderBys = queryBuilder.getOrderBysEffective(); - - final WindowId windowId = queryBuilder.getEntityDescriptor().getWindowId(); final ViewId viewId = ViewId.random(windowId); // @@ -375,7 +349,7 @@ public ViewRowIdsOrderedSelection createOrderedSelection(final SqlDocumentQueryB // // SELECT ... FROM ... WHERE 1=1 { - IStringExpression sqlOrderBy = buildSqlFullOrderBy(orderBys); + IStringExpression sqlOrderBy = buildSqlOrderBy(orderBys); if (sqlOrderBy.isNullExpression()) { sqlOrderBy = ConstantStringExpression.of(keyColumnNameFQ); @@ -396,23 +370,34 @@ public ViewRowIdsOrderedSelection createOrderedSelection(final SqlDocumentQueryB // // WHERE clause (from query) - if (!sqlWhereClause.isNullExpression()) { - sqlBuilder.append("\n AND (\n").append(sqlWhereClause).append("\n)"); - sqlParams.addAll(sqlWhereClauseParams); + final List sqlWhereClauseParams = new ArrayList<>(); + final String sqlWhereClause = SqlDocumentFiltersBuilder.newInstance(this) + .addFilters(filters) + .buildSqlWhereClause(sqlWhereClauseParams); + + if (!Check.isEmpty(sqlWhereClause, true)) + { + sqlBuilder.append("\n AND (\n").append(sqlWhereClause).append("\n)"); + sqlParams.addAll(sqlWhereClauseParams); + } } // // Enforce a LIMIT, to not affect server performances on huge tables - final int queryLimit = queryBuilder.getPermissions() - .getConstraint(WindowMaxQueryRecordsConstraint.class) - .or(WindowMaxQueryRecordsConstraint.DEFAULT) - .getMaxQueryRecordsPerRole(); - if (queryLimit > 0) + final int queryLimit; { - sqlBuilder.append("\n LIMIT ?"); - sqlParams.add(queryLimit); + final UserRolePermissionsKey permissionsKey = UserRolePermissionsKey.fromEvaluatee(evalCtx, AccessSqlStringExpression.PARAM_UserRolePermissionsKey.getName()); + final IUserRolePermissions permissions = Services.get(IUserRolePermissionsDAO.class).retrieveUserRolePermissions(permissionsKey); + queryLimit = permissions.getConstraint(WindowMaxQueryRecordsConstraint.class) + .or(WindowMaxQueryRecordsConstraint.DEFAULT) + .getMaxQueryRecordsPerRole(); + if (queryLimit > 0) + { + sqlBuilder.append("\n LIMIT ?"); + sqlParams.add(queryLimit); + } } // @@ -435,16 +420,28 @@ public ViewRowIdsOrderedSelection createOrderedSelection(final SqlDocumentQueryB .build(); } - public IViewRowIdsOrderedSelectionFactory createOrderedSelectionFactory(final Evaluatee evalCtx) + public IViewRowIdsOrderedSelectionFactory createOrderedSelectionFactory(final SqlViewEvaluationCtx viewEvalCtx) { - final String sql = getSqlCreateSelectionFromSelection() - .evaluate(evalCtx, OnVariableNotFound.Fail); - final Map fieldName2sqlDictionary = getAvailableFieldFullOrderBys(evalCtx); + final Evaluatee evalCtx = viewEvalCtx.toEvaluatee(); + final String sqlCreateFromViewId = getSqlCreateSelectionFromSelection(); - return new SqlViewRowIdsOrderedSelectionFactory(sql, fieldName2sqlDictionary); + final ImmutableMap.Builder sqlOrderBysByFieldName = ImmutableMap.builder(); + for (final SqlViewRowFieldBinding fieldBinding : getFields()) + { + final String fieldOrderBy = fieldBinding.getSqlOrderBy().evaluate(evalCtx, OnVariableNotFound.Fail); + if (Check.isEmpty(fieldOrderBy, true)) + { + continue; + } + + final String fieldName = fieldBinding.getFieldName(); + sqlOrderBysByFieldName.put(fieldName, fieldOrderBy); + } + + return new SqlViewRowIdsOrderedSelectionFactory(sqlCreateFromViewId, sqlOrderBysByFieldName.build()); } - public IStringExpression getSqlCreateSelectionFromSelection() + private String getSqlCreateSelectionFromSelection() { final String sqlTableName = getTableName(); final String sqlTableAlias = getTableAlias(); @@ -453,7 +450,7 @@ public IStringExpression getSqlCreateSelectionFromSelection() // // INSERT INTO T_WEBUI_ViewSelection (UUID, Line, Record_ID) - final CompositeStringExpression.Builder sqlBuilder = IStringExpression.composer() + final StringBuilder sqlBuilder = new StringBuilder() .append("INSERT INTO " + I_T_WEBUI_ViewSelection.Table_Name + " (" + " " + I_T_WEBUI_ViewSelection.COLUMNNAME_UUID + ", " + I_T_WEBUI_ViewSelection.COLUMNNAME_Line @@ -463,87 +460,38 @@ public IStringExpression getSqlCreateSelectionFromSelection() // // SELECT ... FROM T_WEBUI_ViewSelection sel INNER JOIN ourTable WHERE sel.UUID=[fromUUID] { - sqlBuilder.append( - IStringExpression.composer() - .append("\n SELECT ") - .append("\n ?") // newUUID - .append("\n, ").append("row_number() OVER (ORDER BY ").append(PLACEHOLDER_OrderBy).append(")") // Line - .append("\n, ").append(keyColumnNameFQ) // Record_ID - .append("\n FROM ").append(I_T_WEBUI_ViewSelection.Table_Name).append(" sel") - .append("\n LEFT OUTER JOIN ").append(sqlTableName).append(" ").append(sqlTableAlias).append(" ON (").append(keyColumnNameFQ).append("=").append("sel.") - .append(I_T_WEBUI_ViewSelection.COLUMNNAME_Record_ID).append(")") - .append("\n WHERE sel.").append(I_T_WEBUI_ViewSelection.COLUMNNAME_UUID).append("=?") // fromUUID - ); + sqlBuilder + .append("\n SELECT ") + .append("\n ?") // newUUID + .append("\n, ").append("row_number() OVER (ORDER BY ").append(PLACEHOLDER_OrderBy).append(")") // Line + .append("\n, ").append(keyColumnNameFQ) // Record_ID + .append("\n FROM ").append(I_T_WEBUI_ViewSelection.Table_Name).append(" sel") + .append("\n LEFT OUTER JOIN ").append(sqlTableName).append(" ").append(sqlTableAlias).append(" ON (").append(keyColumnNameFQ).append("=").append("sel.") + .append(I_T_WEBUI_ViewSelection.COLUMNNAME_Record_ID).append(")") + .append("\n WHERE sel.").append(I_T_WEBUI_ViewSelection.COLUMNNAME_UUID).append("=?") // fromUUID + ; } - return sqlBuilder.build(); - } - - /** - * Retrieves a particular field from given {@link ResultSet} and loads it to given {@link ViewRow.Builder}. - */ - @FunctionalInterface - public static interface SqlViewRowFieldLoader - { - /** - * @param viewRowBuilder - * @param rs - * @return true if loaded; false if not loaded and document shall be skipped - */ - boolean loadViewRowField(final ViewRow.Builder viewRowBuilder, ResultSet rs) throws SQLException; + return sqlBuilder.toString(); } - private static final class CompositeSqlViewRowFieldLoader implements SqlViewRowFieldLoader - { - public static final CompositeSqlViewRowFieldLoader of(final List fieldLoaders) - { - return new CompositeSqlViewRowFieldLoader(fieldLoaders); - } - - private final ImmutableList fieldLoaders; - - private CompositeSqlViewRowFieldLoader(final List fieldLoaders) - { - super(); - this.fieldLoaders = ImmutableList.copyOf(fieldLoaders); - } - - @Override - public String toString() - { - return MoreObjects.toStringHelper("composite").addValue(fieldLoaders).toString(); - } - - @Override - public boolean loadViewRowField(final ViewRow.Builder viewRowBuilder, final ResultSet rs) throws SQLException - { - for (final SqlViewRowFieldLoader fieldLoader : fieldLoaders) - { - final boolean loaded = fieldLoader.loadViewRowField(viewRowBuilder, rs); - if (!loaded) - { - return false; - } - } - - return true; - } - } - - @Value - public static final class ViewFieldsBinding - { - private final IStringExpression sqlPagedSelect; - private final SqlViewRowFieldLoader valueLoaders; - } + // + // + // + // + // public static final class Builder { private String _sqlTableName; private String _tableAlias; - private final Map _fieldsByFieldName = new LinkedHashMap<>(); - private SqlDocumentFieldDataBindingDescriptor _keyField; + private Collection displayFieldNames; + private final Map _fieldsByFieldName = new LinkedHashMap<>(); + private SqlViewRowFieldBinding _keyField; + + private List orderBys; + private DocumentFilterDescriptorsProvider filterDescriptors = NullDocumentFilterDescriptorsProvider.instance; private Builder() { @@ -577,25 +525,71 @@ private String getTableAlias() return _tableAlias; } - private SqlDocumentFieldDataBindingDescriptor getKeyField() + private SqlViewRowFieldBinding getKeyField() { return _keyField; } - private Map getFieldsByFieldName() + public Builder setDisplayFieldNames(final Collection displayFieldNames) + { + this.displayFieldNames = displayFieldNames; + return this; + } + + public Collection getDisplayFieldNames() + { + if (displayFieldNames == null || displayFieldNames.isEmpty()) + { + throw new IllegalStateException("No display field names configured for " + this); + } + return displayFieldNames; + } + + private Map getFieldsByFieldName() { return _fieldsByFieldName; } - public final Builder addField(final SqlDocumentFieldDataBindingDescriptor field) + private final Builder addField(final SqlDocumentFieldDataBindingDescriptor field) { - _fieldsByFieldName.put(field.getFieldName(), field); - if (field.isKeyColumn()) + final boolean isDisplayColumnAvailable = getDisplayFieldNames().contains(field.getFieldName()); + final SqlViewRowFieldBinding rowFieldBinding = SqlViewRowFieldBinding.of(field, isDisplayColumnAvailable); + + _fieldsByFieldName.put(rowFieldBinding.getFieldName(), rowFieldBinding); + if (rowFieldBinding.isKeyColumn()) { - _keyField = field; + _keyField = rowFieldBinding; } return this; } + + public Builder addFields(final Collection fields) + { + fields.forEach(this::addField); + return this; + } + + public Builder setOrderBys(final List orderBys) + { + this.orderBys = orderBys; + return this; + } + + private List getOrderBys() + { + return orderBys == null ? ImmutableList.of() : orderBys; + } + + public Builder setFilterDescriptors(@NonNull final DocumentFilterDescriptorsProvider filterDescriptors) + { + this.filterDescriptors = filterDescriptors; + return this; + } + + private DocumentFilterDescriptorsProvider getFilterDescriptors() + { + return filterDescriptors; + } } } diff --git a/src/main/java/de/metas/ui/web/view/descriptor/SqlViewRowFieldBinding.java b/src/main/java/de/metas/ui/web/view/descriptor/SqlViewRowFieldBinding.java new file mode 100644 index 000000000..556659c08 --- /dev/null +++ b/src/main/java/de/metas/ui/web/view/descriptor/SqlViewRowFieldBinding.java @@ -0,0 +1,170 @@ +package de.metas.ui.web.view.descriptor; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.adempiere.ad.expression.api.IExpression; +import org.adempiere.ad.expression.api.IStringExpression; +import org.adempiere.util.Check; + +import de.metas.ui.web.window.datatypes.Values; +import de.metas.ui.web.window.descriptor.DocumentFieldWidgetType; +import de.metas.ui.web.window.descriptor.sql.SqlDocumentFieldDataBindingDescriptor; +import de.metas.ui.web.window.descriptor.sql.SqlDocumentFieldDataBindingDescriptor.DocumentFieldValueLoader; +import de.metas.ui.web.window.descriptor.sql.SqlEntityFieldBinding; + +/* + * #%L + * metasfresh-webui-api + * %% + * Copyright (C) 2017 metas GmbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +public class SqlViewRowFieldBinding implements SqlEntityFieldBinding +{ + static final SqlViewRowFieldBinding of(final SqlDocumentFieldDataBindingDescriptor fieldDataBinding, final boolean isDisplayColumnAvailable) + { + return new SqlViewRowFieldBinding(fieldDataBinding, isDisplayColumnAvailable); + } + + /** + * Retrieves a particular field from given {@link ResultSet}. + */ + @FunctionalInterface + public static interface SqlViewRowFieldLoader + { + Object retrieveValueAsJson(ResultSet rs) throws SQLException; + } + + private final String fieldName; + private final String columnName; + private final String columnSql; + private final boolean keyColumn; + private final DocumentFieldWidgetType widgetType; + + private final Class sqlValueClass; + private final String sqlSelectValue; + private final IStringExpression sqlSelectDisplayValue; + private final boolean usingDisplayColumn; + + private final IExpression sqlOrderBy; + private final IStringExpression sqlOrderByAsc; + private final IStringExpression sqlOrderByDesc; + + private final SqlViewRowFieldLoader fieldLoader; + + private SqlViewRowFieldBinding(final SqlDocumentFieldDataBindingDescriptor fieldDataBinding, final boolean isDisplayColumnAvailable) + { + fieldName = fieldDataBinding.getFieldName(); + columnName = fieldDataBinding.getColumnName(); + columnSql = fieldDataBinding.getColumnSql(); + keyColumn = fieldDataBinding.isKeyColumn(); + widgetType = fieldDataBinding.getWidgetType(); + + sqlValueClass = fieldDataBinding.getSqlValueClass(); + sqlSelectValue = fieldDataBinding.getSqlSelectValue(); + usingDisplayColumn = fieldDataBinding.isUsingDisplayColumn(); // TODO: shall we use displayColumnAvailable instead? + sqlSelectDisplayValue = fieldDataBinding.getSqlSelectDisplayValue(); + + sqlOrderBy = fieldDataBinding.getSqlFullOrderBy(); + sqlOrderByAsc = fieldDataBinding.buildSqlFullOrderBy(true); + sqlOrderByDesc = fieldDataBinding.buildSqlFullOrderBy(false); + + final DocumentFieldValueLoader fieldValueLoader = fieldDataBinding.getDocumentFieldValueLoader(); + fieldLoader = createRowFieldLoader(fieldValueLoader, isDisplayColumnAvailable); + } + + /** + * NOTE to developer: keep this method static and provide only primitive or lambda parameters + * + * @param fieldValueLoader + * @param isDisplayColumnAvailable + */ + private static SqlViewRowFieldLoader createRowFieldLoader(final DocumentFieldValueLoader fieldValueLoader, final boolean isDisplayColumnAvailable) + { + Check.assumeNotNull(fieldValueLoader, "Parameter fieldValueLoader is not null"); + return rs -> { + final Object fieldValue = fieldValueLoader.retrieveFieldValue(rs, isDisplayColumnAvailable); + return Values.valueToJsonObject(fieldValue); + }; + } + + public String getFieldName() + { + return fieldName; + } + + @Override + public String getColumnName() + { + return columnName; + } + + public boolean isKeyColumn() + { + return keyColumn; + } + + @Override + public String getColumnSql() + { + return columnSql; + } + + @Override + public DocumentFieldWidgetType getWidgetType() + { + return widgetType; + } + + @Override + public Class getSqlValueClass() + { + return sqlValueClass; + } + + public String getSqlSelectValue() + { + return sqlSelectValue; + } + + public boolean isUsingDisplayColumn() + { + return usingDisplayColumn; + } + + public IStringExpression getSqlSelectDisplayValue() + { + return sqlSelectDisplayValue; + } + + public SqlViewRowFieldLoader getFieldLoader() + { + return fieldLoader; + } + + public IExpression getSqlOrderBy() + { + return sqlOrderBy; + } + + public IStringExpression getSqlOrderBy(final boolean ascending) + { + return ascending ? sqlOrderByAsc : sqlOrderByDesc; + } +} diff --git a/src/main/java/de/metas/ui/web/view/descriptor/SqlViewRowIdsOrderedSelectionFactory.java b/src/main/java/de/metas/ui/web/view/descriptor/SqlViewRowIdsOrderedSelectionFactory.java index 632757ef6..2a48f4c33 100644 --- a/src/main/java/de/metas/ui/web/view/descriptor/SqlViewRowIdsOrderedSelectionFactory.java +++ b/src/main/java/de/metas/ui/web/view/descriptor/SqlViewRowIdsOrderedSelectionFactory.java @@ -10,10 +10,11 @@ import org.compiere.util.DB; import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableMap; -import de.metas.ui.web.view.ViewRowIdsOrderedSelection; import de.metas.ui.web.view.IViewRowIdsOrderedSelectionFactory; import de.metas.ui.web.view.ViewId; +import de.metas.ui.web.view.ViewRowIdsOrderedSelection; import de.metas.ui.web.window.datatypes.WindowId; import de.metas.ui.web.window.model.DocumentQueryOrderBy; @@ -42,13 +43,13 @@ class SqlViewRowIdsOrderedSelectionFactory implements IViewRowIdsOrderedSelectionFactory { private final String sqlCreateFromViewId; - private final Map fieldName2sqlDictionary; + private final Map sqlOrderBysByFieldName; - SqlViewRowIdsOrderedSelectionFactory(final String sql, final Map fieldName2sqlDictionary) + SqlViewRowIdsOrderedSelectionFactory(final String sqlCreateFromViewId, final Map sqlOrderBysByFieldName) { super(); - this.sqlCreateFromViewId = sql; - this.fieldName2sqlDictionary = fieldName2sqlDictionary; + this.sqlCreateFromViewId = sqlCreateFromViewId; + this.sqlOrderBysByFieldName = ImmutableMap.copyOf(sqlOrderBysByFieldName); } @Override @@ -56,7 +57,7 @@ public String toString() { return MoreObjects.toStringHelper(this) .add("sql", sqlCreateFromViewId) - .add("fieldName2sqlDictionary", fieldName2sqlDictionary) + .add("sqlOrderBysByFieldName", sqlOrderBysByFieldName) .toString(); } @@ -98,10 +99,10 @@ private final String buildOrderBys(final List orderBys) private final String buildOrderBy(final DocumentQueryOrderBy orderBy) { final String fieldName = orderBy.getFieldName(); - final String fieldSql = fieldName2sqlDictionary.get(fieldName); + final String fieldSql = sqlOrderBysByFieldName.get(fieldName); if (fieldSql == null) { - throw new DBException("No SQL field mapping found for: " + fieldName + ". Available fields are: " + fieldName2sqlDictionary); + throw new DBException("No SQL field mapping found for: " + fieldName + ". Available fields are: " + sqlOrderBysByFieldName.keySet()); } return "(" + fieldSql + ") " + (orderBy.isAscending() ? " ASC" : " DESC"); diff --git a/src/main/java/de/metas/ui/web/window/descriptor/factory/standard/GridTabVOBasedDocumentEntityDescriptorFactory.java b/src/main/java/de/metas/ui/web/window/descriptor/factory/standard/GridTabVOBasedDocumentEntityDescriptorFactory.java index 174325c9e..38426ac6e 100644 --- a/src/main/java/de/metas/ui/web/window/descriptor/factory/standard/GridTabVOBasedDocumentEntityDescriptorFactory.java +++ b/src/main/java/de/metas/ui/web/window/descriptor/factory/standard/GridTabVOBasedDocumentEntityDescriptorFactory.java @@ -7,11 +7,8 @@ import org.adempiere.ad.expression.api.ConstantLogicExpression; import org.adempiere.ad.expression.api.IExpression; -import org.adempiere.ad.expression.api.IExpressionFactory; import org.adempiere.ad.expression.api.ILogicExpression; -import org.adempiere.ad.expression.api.IStringExpression; import org.adempiere.util.Check; -import org.adempiere.util.Services; import org.adempiere.util.lang.IPair; import org.adempiere.util.lang.ImmutablePair; import org.compiere.model.GridFieldVO; @@ -23,7 +20,6 @@ import de.metas.ui.web.window.datatypes.DocumentType; import de.metas.ui.web.window.descriptor.DetailId; import de.metas.ui.web.window.descriptor.DocumentEntityDescriptor; -import de.metas.ui.web.window.descriptor.DocumentEntityDescriptor.Builder; import de.metas.ui.web.window.descriptor.DocumentFieldDescriptor; import de.metas.ui.web.window.descriptor.DocumentFieldDescriptor.Characteristic; import de.metas.ui.web.window.descriptor.DocumentFieldWidgetType; @@ -62,7 +58,6 @@ { // Services private static final Logger logger = LogManager.getLogger(GridTabVOBasedDocumentEntityDescriptorFactory.class); - private final transient IExpressionFactory expressionFactory = Services.get(IExpressionFactory.class); private final DocumentsRepository documentsRepository = SqlDocumentsRepository.instance; private final Map _adFieldId2columnName; @@ -71,7 +66,7 @@ // // State - private final Builder _documentEntryBuilder; + private final DocumentEntityDescriptor.Builder _documentEntryBuilder; public GridTabVOBasedDocumentEntityDescriptorFactory(final GridTabVO gridTabVO, final GridTabVO parentTabVO, final boolean isSOTrx) { @@ -323,7 +318,7 @@ else if (!gridFieldVO.isUpdateable() && !gridFieldVO.isParentLink()) orderBySortNo = Integer.MAX_VALUE; } - final IStringExpression sqlColumnSql = expressionFactory.compile(gridFieldVO.getColumnSQL(false), IStringExpression.class); + final String sqlColumnSql = gridFieldVO.getColumnSQL(false); final SqlDocumentFieldDataBindingDescriptor fieldBinding = SqlDocumentFieldDataBindingDescriptor.builder() .setFieldName(sqlColumnName) @@ -335,6 +330,7 @@ else if (!gridFieldVO.isUpdateable() && !gridFieldVO.isParentLink()) .setMandatory(gridFieldVO.isMandatoryDB()) .setWidgetType(widgetType) .setValueClass(valueClass) + .setSqlValueClass(entityBindings.getPOInfo().getColumnClass(sqlColumnName)) .setLookupDescriptor(lookupDescriptor) .setKeyColumn(keyColumn) .setEncrypted(gridFieldVO.isEncryptedColumn()) diff --git a/src/main/java/de/metas/ui/web/window/descriptor/sql/SqlDocumentEntityDataBindingDescriptor.java b/src/main/java/de/metas/ui/web/window/descriptor/sql/SqlDocumentEntityDataBindingDescriptor.java index 69a2aba2a..c0ad9253a 100644 --- a/src/main/java/de/metas/ui/web/window/descriptor/sql/SqlDocumentEntityDataBindingDescriptor.java +++ b/src/main/java/de/metas/ui/web/window/descriptor/sql/SqlDocumentEntityDataBindingDescriptor.java @@ -21,12 +21,12 @@ import org.adempiere.util.lang.IPair; import org.compiere.model.POInfo; +import com.google.common.base.Joiner; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import de.metas.ui.web.view.descriptor.SqlViewBinding; import de.metas.ui.web.window.descriptor.DetailId; import de.metas.ui.web.window.descriptor.DocumentEntityDataBindingDescriptor; import de.metas.ui.web.window.descriptor.DocumentFieldDataBindingDescriptor; @@ -46,16 +46,16 @@ * * This program 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 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public - * License along with this program. If not, see + * License along with this program. If not, see * . * #L% */ -public final class SqlDocumentEntityDataBindingDescriptor implements DocumentEntityDataBindingDescriptor +public final class SqlDocumentEntityDataBindingDescriptor implements DocumentEntityDataBindingDescriptor, SqlEntityBinding { public static final Builder builder() { @@ -71,11 +71,11 @@ public static final SqlDocumentEntityDataBindingDescriptor cast(final DocumentEn public static final String FIELDNAME_Version = "Updated"; -// // -// // Paging constants -// public static final String COLUMNNAME_Paging_UUID = "_sel_UUID"; -// public static final String COLUMNNAME_Paging_SeqNo = "_sel_SeqNo"; -// public static final String COLUMNNAME_Paging_Record_ID = "_sel_Record_ID"; + // // + // // Paging constants + // public static final String COLUMNNAME_Paging_UUID = "_sel_UUID"; + // public static final String COLUMNNAME_Paging_SeqNo = "_sel_SeqNo"; + // public static final String COLUMNNAME_Paging_Record_ID = "_sel_Record_ID"; private final DocumentsRepository documentsRepository; private final String sqlTableName; @@ -90,8 +90,6 @@ public static final SqlDocumentEntityDataBindingDescriptor cast(final DocumentEn private final Map _fieldsByFieldName; - private final SqlViewBinding viewBinding; - private final Optional sqlSelectVersionById; private SqlDocumentEntityDataBindingDescriptor(final Builder builder) @@ -119,24 +117,23 @@ private SqlDocumentEntityDataBindingDescriptor(final Builder builder) .caching(); orderBys = ImmutableList.copyOf(builder.getOrderBysList()); - + sqlSelectVersionById = builder.getSqlSelectVersionById(); - - viewBinding = builder.buildViewBinding(); } @Override public String toString() { + // NOTE: keep it short return MoreObjects.toStringHelper(this) .omitNullValues() .add("sqlTableName", sqlTableName) .add("sqlTableAlias", sqlTableAlias) - .add("sqlKeyColumnName", sqlKeyColumnName) - .add("sqlLinkColumnName", sqlLinkColumnName) - .add("sqlParentLinkColumnName", sqlParentLinkColumnName) - .add("orderBys", orderBys) - .add("fields", _fieldsByFieldName.isEmpty() ? null : _fieldsByFieldName.values()) + // .add("sqlKeyColumnName", sqlKeyColumnName) + // .add("sqlLinkColumnName", sqlLinkColumnName) + // .add("sqlParentLinkColumnName", sqlParentLinkColumnName) + // .add("orderBys", orderBys) + // .add("fields", _fieldsByFieldName.isEmpty() ? null : _fieldsByFieldName.values()) .toString(); } @@ -146,27 +143,23 @@ public DocumentsRepository getDocumentsRepository() return documentsRepository; } + @Override public String getTableName() { return sqlTableName; } + @Override public String getTableAlias() { return sqlTableAlias; } - public POInfo getPOInfo() - { - // NOTE: don't cache it here because it might change dynamically and it would be so nice to support that case... - return POInfo.getPOInfo(sqlTableName); - } - public String getKeyColumnName() { return sqlKeyColumnName; } - + public String getLinkColumnName() { return sqlLinkColumnName; @@ -186,7 +179,7 @@ public IStringExpression getSqlWhereClause() { return sqlWhereClause; } - + public String getSqlWhereClauseById(final int recordId) { return sqlTableName + "." + sqlKeyColumnName + " = " + recordId; @@ -245,15 +238,16 @@ private final IStringExpression buildSqlFullOrderBy(final DocumentQueryOrderBy o public String replaceTableNameWithTableAlias(final String sql) { - if(sql == null || sql.isEmpty()) + if (sql == null || sql.isEmpty()) { return sql; } - + final String sqlFixed = sql.replace(getTableName() + ".", getTableAlias() + "."); return sqlFixed; } + @Override public SqlDocumentFieldDataBindingDescriptor getFieldByFieldName(final String fieldName) { final SqlDocumentFieldDataBindingDescriptor field = _fieldsByFieldName.get(fieldName); @@ -263,17 +257,17 @@ public SqlDocumentFieldDataBindingDescriptor getFieldByFieldName(final String fi } return field; } - - public SqlViewBinding getViewBinding() + + public Collection getFields() { - return viewBinding; + return _fieldsByFieldName.values(); } - + public Optional getSqlSelectVersionById() { return sqlSelectVersionById; } - + @Override public boolean isVersioningSupported() { @@ -297,8 +291,8 @@ public static final class Builder implements DocumentEntityDataBindingDescriptor private final LinkedHashMap _fieldsByFieldName = new LinkedHashMap<>(); private SqlDocumentFieldDataBindingDescriptor _keyField; - - private SqlViewBinding.Builder viewBinding = SqlViewBinding.builder(); + + // private SqlViewBinding.Builder viewBinding = SqlViewBinding.builder(); private Builder() { @@ -343,13 +337,13 @@ private void buildSqlSelects() throw new AdempiereException("No SQL fields found"); } - final List sqlSelectValuesList = new ArrayList<>(fields.size()); + final List sqlSelectValuesList = new ArrayList<>(fields.size()); final List sqlSelectDisplayNamesList = new ArrayList<>(fields.size()); for (final SqlDocumentFieldDataBindingDescriptor sqlField : fields) { // // Value column - final IStringExpression sqlSelectValue = sqlField.getSqlSelectValue(); + final String sqlSelectValue = sqlField.getSqlSelectValue(); sqlSelectValuesList.add(sqlSelectValue); // @@ -365,14 +359,14 @@ private void buildSqlSelects() _sqlSelectAll = buildSqlSelect(sqlSelectValuesList, sqlSelectDisplayNamesList); } - private final IStringExpression buildSqlSelect(final List sqlSelectValuesList, final List sqlSelectDisplayNamesList) + private final IStringExpression buildSqlSelect(final List sqlSelectValuesList, final List sqlSelectDisplayNamesList) { final String sqlTableName = getTableName(); final String sqlTableAlias = getTableAlias(); final IStringExpression sqlInnerExpr = IStringExpression.composer() .append("SELECT ") - .append("\n ").appendAllJoining("\n, ", sqlSelectValuesList) + .append("\n ").append(Joiner.on("\n, ").join(sqlSelectValuesList)) .append("\n FROM ").append(sqlTableName) .wrap(AccessSqlStringExpression.wrapper(sqlTableName, IUserRolePermissions.SQL_FULLYQUALIFIED, IUserRolePermissions.SQL_RO)) // security .build(); @@ -412,7 +406,7 @@ private IStringExpression buildSqlWhereClause() // NOTE: because current AD_Tab.WhereClause contain fully qualified TableNames, we shall replace them with our table alias // (e.g. "R_Request.SalesRep_ID=@#AD_User_ID@" shall become ""tableAlias.SalesRep_ID=@#AD_User_ID@" .replace(getTableName() + ".", getTableAlias() + ".") // - ; + ; final IStringExpression sqlWhereClauseExpr = Services.get(IExpressionFactory.class).compileOrDefault(sqlWhereClausePrepared, IStringExpression.NULL, IStringExpression.class); return sqlWhereClauseExpr; @@ -447,7 +441,6 @@ public Builder setTableName(final String sqlTableName) { assertNotBuilt(); _sqlTableName = sqlTableName; - viewBinding.setTableName(sqlTableName); return this; } @@ -456,11 +449,15 @@ public String getTableName() return _sqlTableName; } + public POInfo getPOInfo() + { + return POInfo.getPOInfo(getTableName()); + } + private Builder setTableAlias(final String sqlTableAlias) { assertNotBuilt(); _tableAlias = sqlTableAlias; - viewBinding.setTableAlias(sqlTableAlias); return this; } @@ -482,12 +479,12 @@ public String getTableAlias() { return _tableAlias; } - + public Builder setChildToParentLinkColumnNames(final IPair childToParentLinkColumnNames) { assertNotBuilt(); - - if(childToParentLinkColumnNames != null) + + if (childToParentLinkColumnNames != null) { _sqlLinkColumnName = childToParentLinkColumnNames.getLeft(); _sqlParentLinkColumnName = childToParentLinkColumnNames.getRight(); @@ -530,8 +527,8 @@ public Builder addField(final DocumentFieldDataBindingDescriptor field) { setKeyField(sqlField); } - - viewBinding.addField(sqlField); + + // viewBinding.addField(sqlField); return this; } @@ -540,7 +537,7 @@ private Map getFieldsByFieldName( { return _fieldsByFieldName; } - + private Builder setKeyField(final SqlDocumentFieldDataBindingDescriptor keyField) { Check.assumeNull(_keyField, "More than one key field is not allowed: {}, {}", _keyField, keyField); @@ -552,25 +549,25 @@ private String getKeyColumnName() { return _keyField == null ? null : _keyField.getColumnName(); } - - private SqlViewBinding buildViewBinding() - { - return viewBinding.build(); - } - + + // private SqlViewBinding buildViewBinding() + // { + // return viewBinding.build(); + // } + private Optional getSqlSelectVersionById() { - if(getFieldsByFieldName().get(FIELDNAME_Version) == null) + if (getFieldsByFieldName().get(FIELDNAME_Version) == null) { return Optional.empty(); } - + final String keyColumnName = getKeyColumnName(); - if(keyColumnName == null) + if (keyColumnName == null) { return Optional.empty(); } - + final String sql = "SELECT " + FIELDNAME_Version + " FROM " + getTableName() + " WHERE " + keyColumnName + "=?"; return Optional.of(sql); } diff --git a/src/main/java/de/metas/ui/web/window/descriptor/sql/SqlDocumentFieldDataBindingDescriptor.java b/src/main/java/de/metas/ui/web/window/descriptor/sql/SqlDocumentFieldDataBindingDescriptor.java index 16a3bb0ab..e4251076e 100644 --- a/src/main/java/de/metas/ui/web/window/descriptor/sql/SqlDocumentFieldDataBindingDescriptor.java +++ b/src/main/java/de/metas/ui/web/window/descriptor/sql/SqlDocumentFieldDataBindingDescriptor.java @@ -10,6 +10,7 @@ import org.adempiere.ad.expression.api.IStringExpression; import org.adempiere.ad.expression.api.NullStringExpression; +import org.adempiere.ad.expression.api.impl.ConstantStringExpression; import org.adempiere.util.Check; import org.adempiere.util.NumberUtils; import org.compiere.util.DisplayType; @@ -39,16 +40,16 @@ * * This program 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 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public - * License along with this program. If not, see + * License along with this program. If not, see * . * #L% */ -public class SqlDocumentFieldDataBindingDescriptor implements DocumentFieldDataBindingDescriptor +public class SqlDocumentFieldDataBindingDescriptor implements DocumentFieldDataBindingDescriptor, SqlEntityFieldBinding { public static final Builder builder() { @@ -69,7 +70,7 @@ public static final SqlDocumentFieldDataBindingDescriptor castOrNull(final Optio final DocumentFieldDataBindingDescriptor descriptor = optionalDescriptor.get(); return castOrNull(descriptor); } - + public static final SqlDocumentFieldDataBindingDescriptor castOrNull(final DocumentFieldDataBindingDescriptor descriptor) { if (descriptor instanceof SqlDocumentFieldDataBindingDescriptor) @@ -80,7 +81,6 @@ public static final SqlDocumentFieldDataBindingDescriptor castOrNull(final Docum return null; } - private static final transient Logger logger = LogManager.getLogger(SqlDocumentFieldDataBindingDescriptor.class); private final String fieldName; @@ -88,7 +88,9 @@ public static final SqlDocumentFieldDataBindingDescriptor castOrNull(final Docum private final String sqlTableName; private final String sqlTableAlias; private final String sqlColumnName; - private final IStringExpression sqlColumnSql; + private final String sqlColumnSql; + private final Class sqlValueClass; + private final boolean virtualColumn; private final boolean mandatory; private final boolean keyColumn; @@ -102,12 +104,11 @@ public static final SqlDocumentFieldDataBindingDescriptor castOrNull(final Docum private final IStringExpression displayColumnSqlExpression; private final Boolean numericKey; // - private final IStringExpression sqlSelectValue; + private final String sqlSelectValue; private final IStringExpression sqlSelectDisplayValue; private final int defaultOrderByPriority; private final boolean defaultOrderByAscending; - private SqlDocumentFieldDataBindingDescriptor(final Builder builder) { @@ -118,6 +119,7 @@ private SqlDocumentFieldDataBindingDescriptor(final Builder builder) sqlTableAlias = builder.getTableAlias(); sqlColumnName = builder.getColumnName(); sqlColumnSql = builder.getColumnSql(); + sqlValueClass = builder.getSqlValueClass(); virtualColumn = builder.isVirtualColumn(); mandatory = builder.mandatory; keyColumn = builder.keyColumn; @@ -179,17 +181,24 @@ public String getColumnName() /** * @return ColumnName or a SQL string expression in case {@link #isVirtualColumn()} */ - public IStringExpression getColumnSql() + @Override + public String getColumnSql() { return sqlColumnSql; } - + + @Override + public Class getSqlValueClass() + { + return sqlValueClass; + } + /** @return SQL to be used in SELECT ... 'this field's sql' ... FROM ... */ - public IStringExpression getSqlSelectValue() + public String getSqlSelectValue() { return sqlSelectValue; } - + public IStringExpression getSqlSelectDisplayValue() { return sqlSelectDisplayValue; @@ -202,13 +211,14 @@ public boolean isVirtualColumn() { return virtualColumn; } - + @Override public boolean isMandatory() { return mandatory; } - + + @Override public DocumentFieldWidgetType getWidgetType() { return widgetType; @@ -281,7 +291,7 @@ public final IStringExpression buildSqlOrderBy(final boolean ascending) */ public final IStringExpression buildSqlFullOrderBy(final boolean ascending) { - final IStringExpression orderByExpr = isUsingDisplayColumn() ? getDisplayColumnSqlExpression() : getColumnSql(); + final IStringExpression orderByExpr = getSqlFullOrderBy(); if (orderByExpr.isNullExpression()) { return orderByExpr; @@ -294,7 +304,7 @@ public final IStringExpression buildSqlFullOrderBy(final boolean ascending) public IStringExpression getSqlFullOrderBy() { - final IStringExpression orderByExpr = isUsingDisplayColumn() ? getDisplayColumnSqlExpression() : getColumnSql(); + final IStringExpression orderByExpr = isUsingDisplayColumn() ? getDisplayColumnSqlExpression() : ConstantStringExpression.of(getColumnSql()); return orderByExpr; } @@ -305,7 +315,7 @@ public IStringExpression getSqlFullOrderBy() public static interface DocumentFieldValueLoader { Object retrieveFieldValue(ResultSet rs, boolean isDisplayColumnAvailable, String adLanguage) throws SQLException; - + default Object retrieveFieldValue(ResultSet rs, boolean isDisplayColumnAvailable) throws SQLException { String adLanguage = null; @@ -320,8 +330,9 @@ public static final class Builder private String _sqlTableName; private String _sqlTableAlias; private String _sqlColumnName; - private IStringExpression _sqlColumnSql; - + private String _sqlColumnSql; + private Class sqlValueClass; + private Boolean _virtualColumn; private Boolean mandatory; @@ -356,10 +367,10 @@ public SqlDocumentFieldDataBindingDescriptor build() if (_lookupDescriptor != null) { _usingDisplayColumn = true; - + final String sqlTableAlias = getTableAlias(); final String sqlColumnName = getColumnName(); - + _displayColumnName = sqlColumnName + "$Display"; final String sqlColumnNameFQ = sqlTableAlias + "." + sqlColumnName; _displayColumnSqlExpression = sqlLookupDescriptor.getSqlForFetchingDisplayNameByIdExpression(sqlColumnNameFQ); @@ -375,44 +386,40 @@ public SqlDocumentFieldDataBindingDescriptor build() return new SqlDocumentFieldDataBindingDescriptor(this); } - - private final IStringExpression buildSqlSelectValue() + + private final String buildSqlSelectValue() { - final IStringExpression columnSqlExpr = getColumnSql(); + final String columnSql = getColumnSql(); final String columnName = getColumnName(); final boolean isVirtualColumn = isVirtualColumn(); if (isVirtualColumn) { - return IStringExpression.composer() - .append(columnSqlExpr).append(" AS ").append(columnName) - .build(); + return columnSql + " AS " + columnName; } else { - return IStringExpression.composer() - .append(getTableName()).append(".").append(columnSqlExpr).append(" AS ").append(columnName) - .build(); + return getTableName() + "." + columnSql + " AS " + columnName; } } - + private final IStringExpression buildSqlSelectDisplayValue() { - if(!isUsingDisplayColumn()) + if (!isUsingDisplayColumn()) { return IStringExpression.NULL; } - + final IStringExpression displayColumnSqlExpression = getDisplayColumnSqlExpression(); final String displayColumnName = getDisplayColumnName(); return IStringExpression.composer() .append("(").append(displayColumnSqlExpression).append(") AS ").append(displayColumnName) .build(); } - + private DocumentFieldValueLoader getDocumentFieldValueLoader() { - if(_documentFieldValueLoader == null) + if (_documentFieldValueLoader == null) { _documentFieldValueLoader = createDocumentFieldValueLoader( getColumnName() // @@ -446,7 +453,7 @@ private static final DocumentFieldValueLoader createDocumentFieldValueLoader( , final DocumentFieldWidgetType widgetType // , final boolean encrypted // , final Boolean numericKey // - ) + ) { final Logger logger = SqlDocumentFieldDataBindingDescriptor.logger; // yes, we can share the static logger @@ -456,7 +463,7 @@ private static final DocumentFieldValueLoader createDocumentFieldValueLoader( { return (rs, isDisplayColumnAvailable, adLanguage) -> { final int id = rs.getInt(sqlColumnName); - if(rs.wasNull()) + if (rs.wasNull()) { return null; } @@ -475,7 +482,7 @@ private static final DocumentFieldValueLoader createDocumentFieldValueLoader( { return (rs, isDisplayColumnAvailable, adLanguage) -> { final String key = rs.getString(sqlColumnName); - if(rs.wasNull()) + if (rs.wasNull()) { return null; } @@ -620,7 +627,7 @@ public Builder setTableName(final String tableName) this._sqlTableName = tableName; return this; } - + private String getTableName() { return _sqlTableName; @@ -631,7 +638,7 @@ public Builder setTableAlias(final String tableAlias) this._sqlTableAlias = tableAlias; return this; } - + private String getTableAlias() { return _sqlTableAlias; @@ -642,19 +649,19 @@ public Builder setColumnName(final String columnName) this._sqlColumnName = columnName; return this; } - + private String getColumnName() { return _sqlColumnName; } - public Builder setColumnSql(final IStringExpression columnSql) + public Builder setColumnSql(final String columnSql) { this._sqlColumnSql = columnSql; return this; } - - private IStringExpression getColumnSql() + + private String getColumnSql() { return _sqlColumnSql; } @@ -664,12 +671,12 @@ public Builder setVirtualColumn(final boolean virtualColumn) this._virtualColumn = virtualColumn; return this; } - + private boolean isVirtualColumn() { return _virtualColumn; } - + public Builder setMandatory(final boolean mandatory) { this.mandatory = mandatory; @@ -687,33 +694,45 @@ public Builder setWidgetType(final DocumentFieldWidgetType widgetType) this.widgetType = widgetType; return this; } - + + public Builder setSqlValueClass(final Class sqlValueClass) + { + this.sqlValueClass = sqlValueClass; + return this; + } + + private Class getSqlValueClass() + { + Check.assumeNotNull(sqlValueClass, "Parameter sqlValueClass is not null"); + return sqlValueClass; + } + public Builder setLookupDescriptor(final LookupDescriptor lookupDescriptor) { this._lookupDescriptor = lookupDescriptor; return this; } - + private boolean isUsingDisplayColumn() { return _usingDisplayColumn; } - + private String getDisplayColumnName() { return _displayColumnName; } - + public IStringExpression getDisplayColumnSqlExpression() { return _displayColumnSqlExpression; } - + public Boolean getNumericKey() { return _numericKey; } - + public Builder setKeyColumn(final boolean keyColumn) { this.keyColumn = keyColumn; diff --git a/src/main/java/de/metas/ui/web/window/descriptor/sql/SqlEntityBinding.java b/src/main/java/de/metas/ui/web/window/descriptor/sql/SqlEntityBinding.java new file mode 100644 index 000000000..20eed7145 --- /dev/null +++ b/src/main/java/de/metas/ui/web/window/descriptor/sql/SqlEntityBinding.java @@ -0,0 +1,32 @@ +package de.metas.ui.web.window.descriptor.sql; + +/* + * #%L + * metasfresh-webui-api + * %% + * Copyright (C) 2017 metas GmbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +public interface SqlEntityBinding +{ + String getTableName(); + + String getTableAlias(); + + SqlEntityFieldBinding getFieldByFieldName(String fieldName); +} diff --git a/src/main/java/de/metas/ui/web/window/descriptor/sql/SqlEntityFieldBinding.java b/src/main/java/de/metas/ui/web/window/descriptor/sql/SqlEntityFieldBinding.java new file mode 100644 index 000000000..0a5d28ffc --- /dev/null +++ b/src/main/java/de/metas/ui/web/window/descriptor/sql/SqlEntityFieldBinding.java @@ -0,0 +1,36 @@ +package de.metas.ui.web.window.descriptor.sql; + +import de.metas.ui.web.window.descriptor.DocumentFieldWidgetType; + +/* + * #%L + * metasfresh-webui-api + * %% + * Copyright (C) 2017 metas GmbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +public interface SqlEntityFieldBinding +{ + String getColumnName(); + + String getColumnSql(); + + DocumentFieldWidgetType getWidgetType(); + + Class getSqlValueClass(); +} diff --git a/src/main/java/de/metas/ui/web/window/model/sql/SqlDocumentFiltersBuilder.java b/src/main/java/de/metas/ui/web/window/model/sql/SqlDocumentFiltersBuilder.java new file mode 100644 index 000000000..39a1d41ca --- /dev/null +++ b/src/main/java/de/metas/ui/web/window/model/sql/SqlDocumentFiltersBuilder.java @@ -0,0 +1,301 @@ +package de.metas.ui.web.window.model.sql; + +import java.util.ArrayList; +import java.util.List; + +import org.adempiere.db.DBConstants; +import org.compiere.model.MQuery.Operator; +import org.compiere.util.DB; + +import de.metas.printing.esb.base.util.Check; +import de.metas.ui.web.window.descriptor.DocumentFieldWidgetType; +import de.metas.ui.web.window.descriptor.sql.SqlEntityBinding; +import de.metas.ui.web.window.descriptor.sql.SqlEntityFieldBinding; +import de.metas.ui.web.window.model.filters.DocumentFilter; +import de.metas.ui.web.window.model.filters.DocumentFilterParam; +import lombok.NonNull; + +/* + * #%L + * metasfresh-webui-api + * %% + * Copyright (C) 2017 metas GmbH + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +/** + * Helper class to build SQL where clauses from {@link DocumentFilter}s. + * + * @author metas-dev + * + */ +public class SqlDocumentFiltersBuilder +{ + public static final SqlDocumentFiltersBuilder newInstance(final SqlEntityBinding entityBinding) + { + return new SqlDocumentFiltersBuilder(entityBinding); + } + + private final SqlEntityBinding entityBinding; + private final List filters = new ArrayList<>(); + + private SqlDocumentFiltersBuilder(@NonNull final SqlEntityBinding entityBinding) + { + this.entityBinding = entityBinding; + } + + public String buildSqlWhereClause(final List sqlParams) + { + if (filters.isEmpty()) + { + return ""; + } + + final StringBuilder sqlWhereClauseBuilder = new StringBuilder(); + + for (final DocumentFilter filter : filters) + { + final String sqlFilter = buildSqlWhereClause(sqlParams, filter); + if (Check.isEmpty(sqlFilter, true)) + { + continue; + } + + if (sqlWhereClauseBuilder.length() > 0) + { + sqlWhereClauseBuilder.append("\n AND "); + } + sqlWhereClauseBuilder.append(DB.TO_COMMENT(filter.getFilterId())).append("(").append(sqlFilter).append(")"); + } + + return sqlWhereClauseBuilder.toString(); + } + + /** Build document filter where clause */ + private String buildSqlWhereClause(final List sqlParams, final DocumentFilter filter) + { + final StringBuilder sql = new StringBuilder(); + + for (final DocumentFilterParam filterParam : filter.getParameters()) + { + final String sqlFilterParam = buildSqlWhereClause(sqlParams, filterParam); + if (Check.isEmpty(sqlFilterParam, true)) + { + continue; + } + + if (sql.length() > 0) + { + sql.append(filterParam.isJoinAnd() ? " AND " : " OR "); + } + + sql.append("(").append(sqlFilterParam).append(")"); + } + + return sql.toString(); + } + + /** Build document filter parameter where clause */ + private String buildSqlWhereClause(final List sqlParams, final DocumentFilterParam filterParam) + { + // + // SQL filter + if (filterParam.isSqlFilter()) + { + final String sqlWhereClause = replaceTableNameWithTableAlias(filterParam.getSqlWhereClause()); + return sqlWhereClause; + } + + // + // Regular filter + final String fieldName = filterParam.getFieldName(); + final SqlEntityFieldBinding fieldBinding = entityBinding.getFieldByFieldName(fieldName); + + final String columnSql = fieldBinding.getColumnSql(); + final String columnName = fieldBinding.getColumnName(); + final DocumentFieldWidgetType widgetType = fieldBinding.getWidgetType(); + final Class targetClass = fieldBinding.getSqlValueClass(); + final Object sqlValue = SqlDocumentsRepository.convertValueToPO(filterParam.getValue(), columnName, widgetType, targetClass); + + final Operator operator = filterParam.getOperator(); + switch (operator) + { + case EQUAL: + { + final boolean negate = false; + return buildSqlWhereClause_Equals(columnSql, sqlValue, negate, sqlParams); + } + case NOT_EQUAL: + { + final boolean negate = true; + return buildSqlWhereClause_Equals(columnSql, sqlValue, negate, sqlParams); + } + case GREATER: + { + return buildSqlWhereClause_Compare(columnSql, ">", sqlValue, sqlParams); + } + case GREATER_EQUAL: + { + return buildSqlWhereClause_Compare(columnSql, ">=", sqlValue, sqlParams); + } + case LESS: + { + return buildSqlWhereClause_Compare(columnSql, "<", sqlValue, sqlParams); + } + case LESS_EQUAL: + { + return buildSqlWhereClause_Compare(columnSql, "<=", sqlValue, sqlParams); + } + case LIKE: + { + final boolean negate = false; + final boolean ignoreCase = false; + return buildSqlWhereClause_Like(columnSql, negate, ignoreCase, sqlValue, sqlParams); + } + case NOT_LIKE: + { + final boolean negate = true; + final boolean ignoreCase = false; + return buildSqlWhereClause_Like(columnSql, negate, ignoreCase, sqlValue, sqlParams); + } + case LIKE_I: + { + final boolean negate = false; + final boolean ignoreCase = true; + return buildSqlWhereClause_Like(columnSql, negate, ignoreCase, sqlValue, sqlParams); + } + case NOT_LIKE_I: + { + final boolean negate = true; + final boolean ignoreCase = true; + return buildSqlWhereClause_Like(columnSql, negate, ignoreCase, sqlValue, sqlParams); + } + case BETWEEN: + { + final Object sqlValueTo = SqlDocumentsRepository.convertValueToPO(filterParam.getValueTo(), columnName, widgetType, targetClass); + return buildSqlWhereClause_Between(columnSql, sqlValue, sqlValueTo, sqlParams); + } + default: + { + throw new IllegalArgumentException("Operator not supported: " + operator); + } + } + } + + private static final String buildSqlWhereClause_Equals(final String sqlColumnExpr, final Object sqlValue, final boolean negate, final List sqlParams) + { + if (sqlValue == null) + { + return buildSqlWhereClause_IsNull(sqlColumnExpr, negate); + } + + sqlParams.add(sqlValue); + return new StringBuilder() + .append(sqlColumnExpr).append(negate ? " <> ?" : " = ?") + .toString(); + } + + private static final String buildSqlWhereClause_IsNull(final String sqlColumnExpr, final boolean negate) + { + return new StringBuilder() + .append(sqlColumnExpr).append(negate ? " IS NOT NULL" : " IS NULL") + .toString(); + } + + private static final String buildSqlWhereClause_Compare(final String sqlColumnExpr, final String sqlOperator, final Object sqlValue, final List sqlParams) + { + sqlParams.add(sqlValue); + return new StringBuilder() + .append(sqlColumnExpr).append(sqlOperator).append("?") + .toString(); + } + + private static final String buildSqlWhereClause_Like(final String sqlColumnExpr, final boolean negate, final boolean ignoreCase, final Object sqlValue, final List sqlParams) + { + if (sqlValue == null) + { + return buildSqlWhereClause_IsNull(sqlColumnExpr, negate); + } + + String sqlValueStr = sqlValue.toString(); + if (sqlValueStr.isEmpty()) + { + // NO value supplied, it's pointless to enforce a LIKE on that... + // => considering all matches + return ""; + } + + if (!sqlValueStr.startsWith("%")) + { + sqlValueStr = "%" + sqlValueStr; + } + if (!sqlValueStr.endsWith("%")) + { + sqlValueStr = sqlValueStr + "%"; + } + + final String sqlOperator = (negate ? " NOT " : " ") + (ignoreCase ? "ILIKE " : "LIKE "); + + sqlParams.add(sqlValueStr); + return new StringBuilder() + .append(DBConstants.FUNCNAME_unaccent_string).append("(").append(sqlColumnExpr).append(", 1)") + .append(sqlOperator) + .append(DBConstants.FUNCNAME_unaccent_string).append("(?, 1)") + .toString(); + } + + private static final String buildSqlWhereClause_Between(final String sqlColumnExpr, final Object sqlValue, final Object sqlValueTo, final List sqlParams) + { + if (sqlValue == null) + { + if (sqlValueTo == null) + { + // Both values are null => considering all matches + return ""; + } + return buildSqlWhereClause_Compare(sqlColumnExpr, "<=", sqlValueTo, sqlParams); + } + if (sqlValueTo == null) + { + // NOTE: at this point sqlValue is not null! + return buildSqlWhereClause_Compare(sqlColumnExpr, ">=", sqlValue, sqlParams); + } + + sqlParams.add(sqlValue); + sqlParams.add(sqlValueTo); + return new StringBuilder() + .append(sqlColumnExpr).append(" BETWEEN ").append("? AND ?") + .toString(); + } + + public String replaceTableNameWithTableAlias(final String sql) + { + if (sql == null || sql.isEmpty()) + { + return sql; + } + + final String sqlFixed = sql.replace(entityBinding.getTableName() + ".", entityBinding.getTableAlias() + "."); + return sqlFixed; + } + + public SqlDocumentFiltersBuilder addFilters(final List filters) + { + this.filters.addAll(filters); + return this; + } +} diff --git a/src/main/java/de/metas/ui/web/window/model/sql/SqlDocumentQueryBuilder.java b/src/main/java/de/metas/ui/web/window/model/sql/SqlDocumentQueryBuilder.java index c4afef77c..4ad4491fe 100644 --- a/src/main/java/de/metas/ui/web/window/model/sql/SqlDocumentQueryBuilder.java +++ b/src/main/java/de/metas/ui/web/window/model/sql/SqlDocumentQueryBuilder.java @@ -10,12 +10,8 @@ import org.adempiere.ad.security.IUserRolePermissions; import org.adempiere.ad.security.UserRolePermissionsKey; import org.adempiere.ad.security.impl.AccessSqlStringExpression; -import org.adempiere.db.DBConstants; import org.adempiere.exceptions.AdempiereException; import org.adempiere.util.Check; -import org.compiere.model.MQuery.Operator; -import org.compiere.model.POInfo; -import org.compiere.util.DB; import org.compiere.util.Env; import org.compiere.util.Evaluatee; import org.compiere.util.Evaluatees; @@ -27,13 +23,11 @@ import de.metas.ui.web.window.descriptor.DocumentEntityDescriptor; import de.metas.ui.web.window.descriptor.DocumentFieldWidgetType; import de.metas.ui.web.window.descriptor.sql.SqlDocumentEntityDataBindingDescriptor; -import de.metas.ui.web.window.descriptor.sql.SqlDocumentFieldDataBindingDescriptor; import de.metas.ui.web.window.model.Document; import de.metas.ui.web.window.model.DocumentQuery; import de.metas.ui.web.window.model.DocumentQueryOrderBy; import de.metas.ui.web.window.model.IDocumentFieldView; import de.metas.ui.web.window.model.filters.DocumentFilter; -import de.metas.ui.web.window.model.filters.DocumentFilterParam; /* * #%L @@ -81,7 +75,6 @@ public static SqlDocumentQueryBuilder of(final DocumentQuery query) } private final Properties ctx; - private final DocumentEntityDescriptor entityDescriptor; private final SqlDocumentEntityDataBindingDescriptor entityBinding; private transient Evaluatee _evaluationContext = null; // lazy @@ -107,7 +100,6 @@ private SqlDocumentQueryBuilder(final DocumentEntityDescriptor entityDescriptor) ctx = Env.getCtx(); Check.assumeNotNull(entityDescriptor, "Parameter entityDescriptor is not null"); - this.entityDescriptor = entityDescriptor; entityBinding = SqlDocumentEntityDataBindingDescriptor.cast(entityDescriptor.getDataBinding()); } @@ -118,7 +110,6 @@ public String toString() return MoreObjects.toStringHelper(this) .omitNullValues() .add("TableName", entityBinding.getTableName()) - .add("detailId", entityDescriptor.getDetailId()) .toString(); } @@ -320,7 +311,7 @@ private void buildSqlWhereClause() final Object parentLinkValue = parentLinkField.getValue(); final DocumentFieldWidgetType parentLinkWidgetType = parentLinkField.getWidgetType(); - final Class targetClass = entityBinding.getFieldByFieldName(linkColumnName).getValueClass(); + final Class targetClass = entityBinding.getFieldByFieldName(linkColumnName).getSqlValueClass(); final Object sqlParentLinkValue = SqlDocumentsRepository.convertValueToPO(parentLinkValue, parentLinkColumnName, parentLinkWidgetType, targetClass); sqlWhereClauseBuilder.appendIfNotEmpty("\n AND "); @@ -332,8 +323,10 @@ private void buildSqlWhereClause() // // Document filters { - final IStringExpression sqlFilters = buildSqlWhereClause(sqlParams, getDocumentFilters()); - if (!sqlFilters.isNullExpression()) + final String sqlFilters = SqlDocumentFiltersBuilder.newInstance(entityBinding) + .addFilters(getDocumentFilters()) + .buildSqlWhereClause(sqlParams); + if(!Check.isEmpty(sqlFilters, true)) { sqlWhereClauseBuilder.appendIfNotEmpty("\n AND "); sqlWhereClauseBuilder.append(" /* filters */ (\n").append(sqlFilters).append(")\n"); @@ -346,224 +339,6 @@ private void buildSqlWhereClause() _sqlWhereParams = sqlParams; } - /** Build document filters where clause */ - private IStringExpression buildSqlWhereClause(final List sqlParams, final List filters) - { - if (filters.isEmpty()) - { - return IStringExpression.NULL; - } - - final CompositeStringExpression.Builder sqlWhereClauseBuilder = IStringExpression.composer(); - - for (final DocumentFilter filter : filters) - { - final IStringExpression sqlFilter = buildSqlWhereClause(sqlParams, filter); - if (sqlFilter.isNullExpression()) - { - continue; - } - - sqlWhereClauseBuilder.appendIfNotEmpty("\n AND "); - sqlWhereClauseBuilder.append(DB.TO_COMMENT(filter.getFilterId())).append("(").append(sqlFilter).append(")"); - } - - return sqlWhereClauseBuilder.build(); - } - - /** Build document filter where clause */ - private IStringExpression buildSqlWhereClause(final List sqlParams, final DocumentFilter filter) - { - final CompositeStringExpression.Builder sql = IStringExpression.composer(); - - for (final DocumentFilterParam filterParam : filter.getParameters()) - { - final IStringExpression sqlFilterParam = buildSqlWhereClause(sqlParams, filterParam); - if (sqlFilterParam.isNullExpression()) - { - continue; - } - - sql.appendIfNotEmpty(filterParam.isJoinAnd() ? " AND " : " OR "); - sql.append("(").append(sqlFilterParam).append(")"); - } - - return sql.build(); - } - - /** Build document filter parameter where clause */ - private IStringExpression buildSqlWhereClause(final List sqlParams, final DocumentFilterParam filterParam) - { - // - // SQL filter - if (filterParam.isSqlFilter()) - { - final SqlDocumentEntityDataBindingDescriptor binding = getEntityBinding(); - final String sqlWhereClause = binding.replaceTableNameWithTableAlias(filterParam.getSqlWhereClause()); - return IStringExpression.compile(sqlWhereClause); - } - - // - // Regular filter - final String fieldName = filterParam.getFieldName(); - final SqlDocumentFieldDataBindingDescriptor fieldBinding = entityBinding.getFieldByFieldName(fieldName); - - final POInfo poInfo = entityBinding.getPOInfo(); - final IStringExpression sqlColumnExpr = fieldBinding.getColumnSql(); - final String columnName = fieldBinding.getColumnName(); - final DocumentFieldWidgetType widgetType = fieldBinding.getWidgetType(); - final Class targetClass = poInfo.getColumnClass(columnName); - final Object sqlValue = SqlDocumentsRepository.convertValueToPO(filterParam.getValue(), columnName, widgetType, targetClass); - - final Operator operator = filterParam.getOperator(); - switch (operator) - { - case EQUAL: - { - final boolean negate = false; - return buildSqlWhereClause_Equals(sqlColumnExpr, sqlValue, negate, sqlParams); - } - case NOT_EQUAL: - { - final boolean negate = true; - return buildSqlWhereClause_Equals(sqlColumnExpr, sqlValue, negate, sqlParams); - } - case GREATER: - { - return buildSqlWhereClause_Compare(sqlColumnExpr, ">", sqlValue, sqlParams); - } - case GREATER_EQUAL: - { - return buildSqlWhereClause_Compare(sqlColumnExpr, ">=", sqlValue, sqlParams); - } - case LESS: - { - return buildSqlWhereClause_Compare(sqlColumnExpr, "<", sqlValue, sqlParams); - } - case LESS_EQUAL: - { - return buildSqlWhereClause_Compare(sqlColumnExpr, "<=", sqlValue, sqlParams); - } - case LIKE: - { - final boolean negate = false; - final boolean ignoreCase = false; - return buildSqlWhereClause_Like(sqlColumnExpr, negate, ignoreCase, sqlValue, sqlParams); - } - case NOT_LIKE: - { - final boolean negate = true; - final boolean ignoreCase = false; - return buildSqlWhereClause_Like(sqlColumnExpr, negate, ignoreCase, sqlValue, sqlParams); - } - case LIKE_I: - { - final boolean negate = false; - final boolean ignoreCase = true; - return buildSqlWhereClause_Like(sqlColumnExpr, negate, ignoreCase, sqlValue, sqlParams); - } - case NOT_LIKE_I: - { - final boolean negate = true; - final boolean ignoreCase = true; - return buildSqlWhereClause_Like(sqlColumnExpr, negate, ignoreCase, sqlValue, sqlParams); - } - case BETWEEN: - { - final Object sqlValueTo = SqlDocumentsRepository.convertValueToPO(filterParam.getValueTo(), columnName, widgetType, targetClass); - return buildSqlWhereClause_Between(sqlColumnExpr, sqlValue, sqlValueTo, sqlParams); - } - default: - { - throw new IllegalArgumentException("Operator not supported: " + operator); - } - } - } - - private static final IStringExpression buildSqlWhereClause_Equals(final IStringExpression sqlColumnExpr, final Object sqlValue, final boolean negate, final List sqlParams) - { - if (sqlValue == null) - { - return buildSqlWhereClause_IsNull(sqlColumnExpr, negate); - } - - sqlParams.add(sqlValue); - return IStringExpression.composer() - .append(sqlColumnExpr).append(negate ? " <> ?" : " = ?") - .build(); - } - - private static final IStringExpression buildSqlWhereClause_IsNull(final IStringExpression sqlColumnExpr, final boolean negate) - { - return IStringExpression.composer() - .append(sqlColumnExpr).append(negate ? " IS NOT NULL" : " IS NULL") - .build(); - } - - private static final IStringExpression buildSqlWhereClause_Compare(final IStringExpression sqlColumnExpr, final String sqlOperator, final Object sqlValue, final List sqlParams) - { - sqlParams.add(sqlValue); - return IStringExpression.composer() - .append(sqlColumnExpr).append(sqlOperator).append("?") - .build(); - } - - private static final IStringExpression buildSqlWhereClause_Like(final IStringExpression sqlColumnExpr, final boolean negate, final boolean ignoreCase, final Object sqlValue, - final List sqlParams) - { - if (sqlValue == null) - { - return buildSqlWhereClause_IsNull(sqlColumnExpr, negate); - } - - String sqlValueStr = sqlValue.toString(); - if (sqlValueStr.isEmpty()) - { - return IStringExpression.NULL; - } - - if (!sqlValueStr.startsWith("%")) - { - sqlValueStr = "%" + sqlValueStr; - } - if (!sqlValueStr.endsWith("%")) - { - sqlValueStr = sqlValueStr + "%"; - } - - final String sqlOperator = (negate ? " NOT " : " ") + (ignoreCase ? "ILIKE " : "LIKE "); - - sqlParams.add(sqlValueStr); - return CompositeStringExpression.builder() - .append(DBConstants.FUNCNAME_unaccent_string).append("(").append(sqlColumnExpr).append(", 1)") - .append(sqlOperator) - .append(DBConstants.FUNCNAME_unaccent_string).append("(?, 1)") - .build(); - } - - private static final IStringExpression buildSqlWhereClause_Between(final IStringExpression sqlColumnExpr, final Object sqlValue, final Object sqlValueTo, final List sqlParams) - { - if (sqlValue == null) - { - if (sqlValueTo == null) - { - return IStringExpression.NULL; - } - return buildSqlWhereClause_Compare(sqlColumnExpr, "<=", sqlValueTo, sqlParams); - } - if (sqlValueTo == null) - { - // NOTE: at this point sqlValue is not null! - return buildSqlWhereClause_Compare(sqlColumnExpr, ">=", sqlValue, sqlParams); - } - - sqlParams.add(sqlValue); - sqlParams.add(sqlValueTo); - return IStringExpression.composer() - .append(sqlColumnExpr).append(" BETWEEN ").append("? AND ?") - .build(); - } - public List getOrderBysEffective() { if (noSorting) @@ -586,11 +361,6 @@ public IStringExpression getSqlOrderByEffective() return entityBinding.buildSqlOrderBy(orderBys); } - public DocumentEntityDescriptor getEntityDescriptor() - { - return entityDescriptor; - } - public SqlDocumentEntityDataBindingDescriptor getEntityBinding() { return entityBinding;