Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CAUSEWAY-3825: General Purpose Tabular Data Structure (v3) #2765

Merged
merged 5 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion api/applib/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
exports org.apache.causeway.applib.services.xmlsnapshot;
exports org.apache.causeway.applib.snapshot;
exports org.apache.causeway.applib.spec;
exports org.apache.causeway.applib.tabular;
exports org.apache.causeway.applib.types;
exports org.apache.causeway.applib.util.schema;
exports org.apache.causeway.applib.util;
Expand All @@ -145,7 +146,7 @@
requires transitive spring.core;
requires spring.tx;
requires org.apache.logging.log4j.core;

// JAXB viewmodels
opens org.apache.causeway.applib.annotation;
opens org.apache.causeway.applib.layout.component;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,78 +16,34 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.causeway.core.metamodel.tabular.simple;
package org.apache.causeway.applib.tabular;

import java.io.File;
import java.nio.file.Files;

import org.springframework.lang.Nullable;

import org.apache.causeway.applib.value.Blob;
import org.apache.causeway.applib.value.NamedWithMimeType.CommonMimeType;
import org.apache.causeway.commons.io.DataSource;
import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
import org.apache.causeway.commons.tabular.TabularModel;

import lombok.SneakyThrows;

/**
* SPI to provide file export to table views.
*
* @since 2.0 {@index}
* @since 3.2 {@index}
*/
public interface CollectionContentsExporter {

public enum AccessMode {
/**
* must be authorized, with transactions, with publishing, with domain events
*/
USER,
/**
* always authorized, no transactions, no publishing, no domain events;
*/
PASS_THROUGH;
/**
* @see #USER
*/
public boolean isUser() { return this==USER; }
/**
* @see #PASS_THROUGH
*/
public boolean isPassThrough() { return this==PASS_THROUGH; }
}
public interface TabularExporter {

/**
* Implementing exporters need to write given tabular data from
* {@link DataTable} into the {@link File tempFile},
* {@link org.apache.causeway.commons.tabular.TabularModel.TabularSheet} into the {@link File exportFile},
* which is provided by the framework for the duration of a single request cycle.
*
* @param dataTable data model for the table
* @param tempFile destination, this exporter writes its data to
* @param exportFile destination, this exporter writes its data to
*/
default void createExport(final DataTable dataTable, final File tempFile) {
createExport(dataTable, tempFile, AccessMode.USER);
}

void createExport(DataTable dataTable, File tempFile, @Nullable AccessMode accessMode);

/**
* Writes given tabular data into a {@link Blob} of given name.
*
*/
default Blob exportToBlob(final DataTable dataTable, final String name) {
return exportToBlob(dataTable, name, AccessMode.USER);
}

@SneakyThrows
default Blob exportToBlob(final DataTable dataTable, final String name, final @Nullable AccessMode accessMode) {
var tempFile = File.createTempFile(this.getClass().getCanonicalName(), name);
try {
createExport(dataTable, tempFile, accessMode);
return Blob.of(name, getMimeType(), DataSource.ofFile(tempFile).bytes());
} finally {
Files.deleteIfExists(tempFile.toPath()); // cleanup
}
}
void export(TabularModel.TabularSheet tabularSheet, File exportFile);

CommonMimeType getMimeType();

Expand Down Expand Up @@ -115,9 +71,23 @@ default Blob exportToBlob(final DataTable dataTable, final String name, final @N
int orderOfAppearanceInUiDropdown();

/**
* Whether this exporter applies to given {@code objectType}.
* If <code>false</code>, this exporter is not provided to the end user.
* Whether this exporter applies to given {@code elementType}.
* If <code>false</code>, this exporter is not provided to end users.
*/
default boolean appliesTo(final Class<?> elementType) { return true; }

/**
* Writes given tabular data to a {@link Blob}, using given sheet's name as blob name.
*/
default boolean appliesTo(final ObjectSpecification objectType) { return true; }
@SneakyThrows
default Blob exportToBlob(final TabularModel.TabularSheet tabularSheet) {
var tempFile = File.createTempFile(this.getClass().getCanonicalName(), tabularSheet.sheetName());
try {
export(tabularSheet, tempFile);
return Blob.of(tabularSheet.sheetName(), getMimeType(), DataSource.ofFile(tempFile).bytes());
} finally {
Files.deleteIfExists(tempFile.toPath()); // cleanup
}
}

}
1 change: 1 addition & 0 deletions commons/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
exports org.apache.causeway.commons.handler;
exports org.apache.causeway.commons.having;
exports org.apache.causeway.commons.io;
exports org.apache.causeway.commons.tabular;
// internals exported as well
exports org.apache.causeway.commons.internal;
exports org.apache.causeway.commons.internal.assertions;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* 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.commons.tabular;

import java.util.Objects;
import java.util.function.Supplier;
import java.util.stream.Stream;

import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;

import org.apache.causeway.commons.collections.Can;
import org.apache.causeway.commons.functional.Either;
import org.apache.causeway.commons.internal.assertions._Assert;
import org.apache.causeway.commons.internal.exceptions._Exceptions;

/**
* General purpose tabular data structure,
* that maps well onto excel files,
* but can also be used for other tabular file formats.
*
* @since 3.2
*/
public record TabularModel(
Can<TabularSheet> sheets) {

public TabularModel(final TabularSheet sheet) {
this(Can.of(sheet));
}

public record TabularSheet(
String sheetName,
Can<TabularColumn> columns,
Can<TabularRow> rows) {
}

public record TabularColumn(
int columnIndex,
String columnName,
String columnDescription) {
}

/**
* A cell can have no value {@code cardinality=0},
* one value {@code cardinality=1},
* or multiple values {@code cardinality>1}.
* <p>
* For the plural case, no pojo is provided.
* Instead a {@link Stream} of literals (labels) is provided.
*/
public record TabularCell(
int cardinality,
/**
* When cardinality is 0 then must be Either.right,
* otherwise no strict policy is enforced.<br>
* E.g. a TabularCell can decide to provide a label instead of a pojo,
* even though cardinality is 1.
*/
@NonNull Either<Object, Supplier<Stream<String>>> eitherValueOrLabelSupplier) {

// -- FACTORIES

private static TabularCell EMPTY = new TabularCell(0, Either.right(null));
public static TabularCell empty() { return EMPTY; }

public static TabularCell single(@Nullable final Object value) {
return value==null
? EMPTY
: new TabularCell(1, Either.left(value));
}
public static TabularCell labeled(final int cardinality, @NonNull final Supplier<Stream<String>> labelSupplier) {
Objects.requireNonNull(labelSupplier);
return new TabularCell(cardinality, Either.right(labelSupplier));
}

// -- CANONICAL CONSTRUCTOR

public TabularCell(
final int cardinality,
@NonNull final Either<Object, Supplier<Stream<String>>> eitherValueOrLabelSupplier) {
Objects.requireNonNull(eitherValueOrLabelSupplier);
if(cardinality<0) throw _Exceptions.illegalArgument("cardinality cannot be negative: %d", cardinality);
if(cardinality==0) {
_Assert.assertTrue(eitherValueOrLabelSupplier.isRight(), ()->
"cannot provide a value when cardinality is zero");
}
this.cardinality = cardinality;
this.eitherValueOrLabelSupplier = eitherValueOrLabelSupplier;
}

// -- LABELS

public Stream<String> labels() {
return eitherValueOrLabelSupplier.fold(
left->Stream.<String>empty(),
right->right.get());
}
}

public record TabularRow(
Can<TabularCell> cells) {

public TabularCell getCell(final TabularColumn column) {
return getCell(column.columnIndex());
}

public TabularCell getCell(final int columnIndex) {
return cells.getElseFail(columnIndex);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@
import org.apache.causeway.applib.annotation.Where;
import org.apache.causeway.applib.query.Query;
import org.apache.causeway.applib.services.bookmark.Bookmark;
import org.apache.causeway.applib.tabular.TabularExporter;
import org.apache.causeway.applib.value.Blob;
import org.apache.causeway.commons.collections.Can;
import org.apache.causeway.commons.internal.assertions._Assert;
import org.apache.causeway.commons.internal.base._Casts;
import org.apache.causeway.commons.internal.base._NullSafe;
import org.apache.causeway.commons.internal.base._Strings;
import org.apache.causeway.commons.internal.functions._Predicates;
import org.apache.causeway.commons.tabular.TabularModel.TabularSheet;
import org.apache.causeway.core.metamodel.consent.InteractionInitiatedBy;
import org.apache.causeway.core.metamodel.context.MetaModelContext;
import org.apache.causeway.core.metamodel.object.ManagedObject;
Expand Down Expand Up @@ -279,8 +281,30 @@ public DataTable visit(final CellVisitor visitor, final Predicate<DataColumn> co

// -- EXPORT

public enum AccessMode {
/**
* must be authorized, with transactions, with publishing, with domain events
*/
USER,
/**
* always authorized, no transactions, no publishing, no domain events;
*/
PASS_THROUGH;
/**
* @see #USER
*/
public boolean isUser() { return this==USER; }
/**
* @see #PASS_THROUGH
*/
public boolean isPassThrough() { return this==PASS_THROUGH; }
}

public TabularSheet toTabularSheet(final AccessMode accessMode) {
return TabularUtil.toTabularSheet(this, accessMode);
}

/**
*
* Typical use-case:<br>
* <pre>{@code
* @Inject CollectionContentsAsExcelExporter excelExporter;
Expand All @@ -292,8 +316,8 @@ public DataTable visit(final CellVisitor visitor, final Predicate<DataColumn> co
* }
* }</pre>
*/
public Blob exportToBlob(final CollectionContentsExporter exporter) {
return exporter.exportToBlob(this, tableFriendlyName);
public Blob exportToBlob(final TabularExporter exporter, final AccessMode accessMode) {
return exporter.exportToBlob(toTabularSheet(accessMode));
}

// -- COLUMN FILTER FACTORIES
Expand Down
Loading