diff --git a/api/applib/src/main/java/org/apache/causeway/applib/query/Query.java b/api/applib/src/main/java/org/apache/causeway/applib/query/Query.java index 78380362671..68f470a7991 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/query/Query.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/query/Query.java @@ -22,6 +22,7 @@ import java.util.function.Predicate; import org.apache.causeway.applib.services.repository.RepositoryService; +import org.apache.causeway.commons.collections.Can; import lombok.NonNull; @@ -69,15 +70,15 @@ public interface Query extends Serializable { Query withRange(@NonNull QueryRange range); - default Query withRange(long ...range) { + default Query withRange(final long ...range) { return withRange(QueryRange.of(range)); } - default Query withStart(long start) { + default Query withStart(final long start) { return withRange(start); } - default Query withLimit(long limit) { + default Query withLimit(final long limit) { return withRange(0L, limit); } @@ -94,4 +95,16 @@ static NamedQuery named( return new _NamedQueryDefault<>(resultType, queryName, QueryRange.unconstrained(), null); } + static SelectByIdQuery selectByIdQuery( + final @NonNull Class resultType, + final @NonNull String ... idsStringified) { + return selectByIdQuery(resultType, Can.ofArray(idsStringified)); + } + + static SelectByIdQuery selectByIdQuery( + final @NonNull Class resultType, + final @NonNull Can idsStringified) { + return new _SelectByIdQueryDefault<>(resultType, idsStringified, QueryRange.unconstrained()); + } + } diff --git a/api/applib/src/main/java/org/apache/causeway/applib/query/SelectByIdQuery.java b/api/applib/src/main/java/org/apache/causeway/applib/query/SelectByIdQuery.java new file mode 100644 index 00000000000..d53fc557f50 --- /dev/null +++ b/api/applib/src/main/java/org/apache/causeway/applib/query/SelectByIdQuery.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.causeway.applib.query; + +import org.apache.causeway.commons.collections.Can; + +/** + * @since 2.1, 3.1 {@index} + */ +public interface SelectByIdQuery extends Query { + + Can getIdsStringified(); + +} diff --git a/api/applib/src/main/java/org/apache/causeway/applib/query/_AllInstancesQueryDefault.java b/api/applib/src/main/java/org/apache/causeway/applib/query/_AllInstancesQueryDefault.java index 9f81a794278..ed3ceee3828 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/query/_AllInstancesQueryDefault.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/query/_AllInstancesQueryDefault.java @@ -20,14 +20,14 @@ import lombok.NonNull; -final class _AllInstancesQueryDefault -extends _QueryAbstract +final class _AllInstancesQueryDefault +extends _QueryAbstract implements AllInstancesQuery { private static final long serialVersionUID = 1L; protected _AllInstancesQueryDefault( - final @NonNull Class type, + final @NonNull Class type, final @NonNull QueryRange range) { super(type, range); } @@ -38,11 +38,10 @@ public String getDescription() { } // -- WITHERS - + @Override public _AllInstancesQueryDefault withRange(final @NonNull QueryRange range) { return new _AllInstancesQueryDefault<>(getResultType(), range); } - } diff --git a/api/applib/src/main/java/org/apache/causeway/applib/query/_SelectByIdQueryDefault.java b/api/applib/src/main/java/org/apache/causeway/applib/query/_SelectByIdQueryDefault.java new file mode 100644 index 00000000000..27dea7eb218 --- /dev/null +++ b/api/applib/src/main/java/org/apache/causeway/applib/query/_SelectByIdQueryDefault.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.causeway.applib.query; + +import org.apache.causeway.commons.collections.Can; + +import lombok.Getter; +import lombok.NonNull; + +final class _SelectByIdQueryDefault +extends _QueryAbstract +implements SelectByIdQuery { + + private static final long serialVersionUID = 1L; + + @Getter + private final @NonNull Can idsStringified; + + _SelectByIdQueryDefault( + final @NonNull Class type, + final @NonNull Can idsStringified, + final @NonNull QueryRange range) { + super(type, range); + this.idsStringified = idsStringified; + } + + @Override + public String getDescription() { + return getResultType().getName() + " (select by id)"; + } + + // -- WITHERS + + @Override + public _SelectByIdQueryDefault withRange(final @NonNull QueryRange range) { + return new _SelectByIdQueryDefault<>(getResultType(), idsStringified, range); + } + +} diff --git a/commons/src/main/java/org/apache/causeway/commons/internal/base/_Timing.java b/commons/src/main/java/org/apache/causeway/commons/internal/base/_Timing.java index d40f6abafa2..0a8bd7798ad 100644 --- a/commons/src/main/java/org/apache/causeway/commons/internal/base/_Timing.java +++ b/commons/src/main/java/org/apache/causeway/commons/internal/base/_Timing.java @@ -134,6 +134,21 @@ public static StopWatch run(final Runnable runnable) { return watch.stop(); } + public static void runVerbose(final String label, final Runnable runnable) { + System.out.println(String.format(Locale.US, "Entering call '%s'", label)); + final StopWatch watch = run(runnable); + System.out.println(String.format(Locale.US, "Running '%s' took %d ms", label, watch.getMillis())); + } + + public static T callVerbose(final String label, final Supplier callable) { + System.out.println(String.format(Locale.US, "Entering call '%s'", label)); + final StopWatch watch = now(); + T result = callable.get(); + watch.stop(); + System.out.println(String.format(Locale.US, "Calling '%s' took %d ms", label, watch.getMillis())); + return result; + } + public static void runVerbose(final Logger log, final String label, final Runnable runnable) { final StopWatch watch = run(runnable); log.info(String.format(Locale.US, "Running '%s' took %d ms", label, watch.getMillis())); @@ -147,7 +162,4 @@ public static T callVerbose(final Logger log, final String label, final Supp return result; } - - - } diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/tabular/interactive/DataTableInteractive.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/tabular/interactive/DataTableInteractive.java index 5404162ed13..0b5c7fc1cfd 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/tabular/interactive/DataTableInteractive.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/tabular/interactive/DataTableInteractive.java @@ -20,6 +20,7 @@ import java.io.Serializable; import java.util.Comparator; +import java.util.Iterator; import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; @@ -35,6 +36,7 @@ import org.apache.causeway.commons.binding.Bindable; import org.apache.causeway.commons.collections.Can; import org.apache.causeway.commons.functional.IndexedFunction; +import org.apache.causeway.commons.internal.base._Timing; import org.apache.causeway.commons.internal.binding._BindableAbstract; import org.apache.causeway.commons.internal.binding._Bindables; import org.apache.causeway.commons.internal.binding._Observables; @@ -146,22 +148,22 @@ private DataTableInteractive( //.filter(this::ignoreHidden) // I believe is redundant, has major performance impact ); - this.dataRows = _Observables.lazy(()-> + this.dataRows = _Observables.lazy(()->_Timing.callVerbose("dataRows.get()", ()-> dataElements.getValue().stream() .map(IndexedFunction.zeroBased((rowIndex, element)->new DataRow(rowIndex, this, element, tokens(element)))) - .collect(Can.toCan())); + .collect(Can.toCan()))); - this.dataRowsFilteredAndSorted = _Observables.lazy(()-> + this.dataRowsFilteredAndSorted = _Observables.lazy(()->_Timing.callVerbose("dataRowsFilteredAndSorted.get()", ()-> dataRows.getValue().stream() .filter(adaptSearchPredicate()) .sorted(sortingComparator() .orElseGet(()->(a, b)->0)) // else don't sort (no-op comparator for streams) - .collect(Can.toCan())); + .collect(Can.toCan()))); - this.dataRowsSelected = _Observables.lazy(()-> + this.dataRowsSelected = _Observables.lazy(()->_Timing.callVerbose("dataRowsSelected.get()", ()-> dataRows.getValue().stream() .filter(dataRow->dataRow.getSelectToggle().getValue().booleanValue()) - .collect(Can.toCan())); + .collect(Can.toCan()))); this.selectionChanges = _Bindables.forValue(Boolean.FALSE); this.selectAllToggle = _Bindables.forValue(Boolean.FALSE); @@ -374,6 +376,16 @@ public DataTable export() { .collect(Can.toCan())); } + @NonNull + public Iterator getDataRowsFilteredAndSorted(final long skip, final long limit) { + var stopWatch = _Timing.now(); + var iterator = dataRowsFilteredAndSorted.getValue() + .iterator(Math.toIntExact(skip), Math.toIntExact(limit)); + System.err.printf("get iterator took %s%n", stopWatch); + stopWatch.stop(); + return iterator; + } + // used internally for serialization private DataTable exportAll() { return new DataTable( @@ -406,12 +418,19 @@ public static class Memento implements Serializable { static Memento create( final @NonNull DataTableInteractive tableInteractive) { - return new Memento( + var stopWatch = _Timing.now(); + + var memento = new Memento( tableInteractive.managedMember.getIdentifier(), tableInteractive.where, tableInteractive.exportAll(), tableInteractive.searchArgument.getValue(), tableInteractive.getSelectedRowIndexes()); + + stopWatch.stop(); + System.err.printf("table memento created, took %s%n", stopWatch); + + return memento; } private final @NonNull Identifier featureId; @@ -432,6 +451,8 @@ public DataTableInteractive getDataTableModel(final ManagedObject owner) { throw _Exceptions.illegalArgument("cannot recreate from memento for deleted object"); } + var stopWatch = _Timing.now(); + val memberId = featureId.getMemberLogicalName(); final ManagedMember managedMember = featureId.getType().isPropertyOrCollection() @@ -456,6 +477,10 @@ public DataTableInteractive getDataTableModel(final ManagedObject owner) { .filter(dataRow->selectedRowIndexes.contains(dataRow.getRowIndex())) .forEach(dataRow->dataRow.getSelectToggle().setValue(true)); }); + + stopWatch.stop(); + System.err.printf("table restored from memento, took %s%n", stopWatch); + return dataTableInteractive; } diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/tabular/simple/DataTable.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/tabular/simple/DataTable.java index 0f8ace874c2..168053129ba 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/tabular/simple/DataTable.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/tabular/simple/DataTable.java @@ -41,6 +41,7 @@ import org.apache.causeway.core.metamodel.context.MetaModelContext; import org.apache.causeway.core.metamodel.object.ManagedObject; import org.apache.causeway.core.metamodel.objectmanager.ObjectBulkLoader; +import org.apache.causeway.core.metamodel.objectmanager.ObjectBulkLoader.Request; import org.apache.causeway.core.metamodel.objectmanager.ObjectManager; import org.apache.causeway.core.metamodel.spec.ObjectSpecification; import org.apache.causeway.core.metamodel.spec.feature.MixedIn; @@ -348,9 +349,20 @@ private Object readResolve() { var elementType = MetaModelContext.instanceElseFail().specForTypeElseFail(elementTypeClass); var dataTable = new DataTable(elementType, columnIds .map(columnId->elementType.getAssociationElseFail(columnId, MixedIn.INCLUDED))); - var rowElements = rowElementBookmarks.map(objectManager::loadObjectElseFail); - dataTable.setDataElements(rowElements); - dataTable.tableFriendlyName = tableFriendlyName; + + if(elementType.isEntity()) { + //TODO[CAUSEWAY-3779] instead of using the common element type we need to use to actual types + //(use a multivalued map by type) + final Can idsStringified = rowElementBookmarks.map(Bookmark::getIdentifier); + var rowElements = objectManager.getObjectBulkLoader() + .loadObject(Request.of(elementType, + Query.selectByIdQuery(elementType.getCorrespondingClass(), idsStringified))); + dataTable.setDataElements(rowElements); + } else { + var rowElements = rowElementBookmarks.map(objectManager::loadObjectElseFail); + dataTable.setDataElements(rowElements); + } + dataTable.tableFriendlyName = this.tableFriendlyName; return dataTable; } } diff --git a/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java b/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java index c7bfdef422d..1d8fc3417b8 100644 --- a/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java +++ b/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java @@ -40,6 +40,7 @@ import org.apache.causeway.applib.query.AllInstancesQuery; import org.apache.causeway.applib.query.NamedQuery; import org.apache.causeway.applib.query.Query; +import org.apache.causeway.applib.query.SelectByIdQuery; import org.apache.causeway.applib.services.bookmark.Bookmark; import org.apache.causeway.applib.services.exceprecog.Category; import org.apache.causeway.applib.services.exceprecog.ExceptionRecognizerService; @@ -250,6 +251,24 @@ public Can fetchByQuery(final Query query) { return resultList; + } else if(query instanceof SelectByIdQuery) { + + //TODO[CAUSEWAY-3779] implement select by + + val queryById = (SelectByIdQuery) query; + val queryEntityType = queryById.getResultType(); + val idsStringified = queryById.getIdsStringified(); + + // guard against misuse + _Assert.assertTypeIsInstanceOf(queryEntityType, entityClass); + + val pm = getPersistenceManager(); + + val q = pm.newQuery(queryEntityType, ":p.contains(id)"); + + val resultList = fetchWithinTransaction(()->(List)q.execute(idsStringified.toList())); + return resultList; + } else if(query instanceof NamedQuery) { val applibNamedQuery = (NamedQuery) query; diff --git a/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/entity/JpaEntityFacet.java b/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/entity/JpaEntityFacet.java index bfd94780b3a..03514c795e3 100644 --- a/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/entity/JpaEntityFacet.java +++ b/persistence/jpa/integration/src/main/java/org/apache/causeway/persistence/jpa/integration/entity/JpaEntityFacet.java @@ -31,6 +31,7 @@ import org.apache.causeway.applib.query.AllInstancesQuery; import org.apache.causeway.applib.query.NamedQuery; import org.apache.causeway.applib.query.Query; +import org.apache.causeway.applib.query.SelectByIdQuery; import org.apache.causeway.applib.services.bookmark.Bookmark; import org.apache.causeway.applib.services.repository.EntityState; import org.apache.causeway.commons.collections.Can; @@ -154,7 +155,12 @@ public Can fetchByQuery(final Query query) { typedQuery.getResultStream() .map(entity -> ManagedObject.adaptSingular(entitySpec, entity))); - } else if (query instanceof NamedQuery) { + } else if(query instanceof SelectByIdQuery) { + + //TODO[CAUSEWAY-3779] implement select by + + } else if(query instanceof NamedQuery) { + val applibNamedQuery = (NamedQuery) query; val queryResultType = applibNamedQuery.getResultType(); diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsSortableDataProvider.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsSortableDataProvider.java index 89a97ab3a79..b0538a05d8e 100644 --- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsSortableDataProvider.java +++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsSortableDataProvider.java @@ -86,6 +86,15 @@ public Iterator iterator(final long skip, final long limit) { .iterator(Math.toIntExact(skip), Math.toIntExact(limit)); } + //TODO[CAUSEWAY-3779] idea +// @Override +// public Iterator iterator(final long skip, final long limit) { +// var dataTable = getDataTableModel(); +// // honor (single) column sort (if any) +// dataTable.getColumnSort().setValue(columnSort().orElse(null)); +// return dataTable.getDataRowsFilteredAndSorted(Math.toIntExact(skip), Math.toIntExact(limit)); +// } + // -- HELPER private Optional columnSort() {