Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,31 @@

import io.jmix.core.querycondition.LogicalCondition;
import io.jmix.flowui.component.filter.FilterComponent;
import io.jmix.flowui.component.genericfilter.configuration.DesignTimeConfiguration;
import io.jmix.flowui.component.genericfilter.configuration.RunTimeConfiguration;
import io.jmix.flowui.component.logicalfilter.LogicalFilterComponent;

import org.springframework.lang.Nullable;

/**
* A configuration is a set of filter components.
*
* <p><b>Important:</b> several mutating methods declared in this interface
* ({@code set*} / {@code reset*}) throw {@link UnsupportedOperationException}
* when called on a {@link DesignTimeConfiguration}. Those methods are marked
* {@link Deprecated} and will be <b>removed from this interface in Jmix 3.0</b>
* — they will exist only in {@link MutableConfiguration}, which is implemented
* exclusively by {@link RunTimeConfiguration} and guarantees that every
* mutating method works without throwing.
*
* <p>Migration path:
* <ul>
* <li>Declare variables / parameters as {@link MutableConfiguration} instead of
* {@code Configuration} when you need to call mutating methods. This gives a
* compile-time guarantee and removes the deprecation warning.</li>
* <li>Use {@code instanceof MutableConfiguration} checks where the type of the
* configuration is not statically known.</li>
* </ul>
*/
public interface Configuration extends Comparable<Configuration> {

Expand All @@ -45,12 +63,19 @@ public interface Configuration extends Comparable<Configuration> {
String getName();

/**
* Sets the name of configuration. This method is only available for
* the {@link RunTimeConfiguration}.
* Sets the name of configuration.
*
* @param name a configuration name
* @throws UnsupportedOperationException when called on {@link DesignTimeConfiguration}
* @deprecated since 2.8, for removal in 3.0.
* This method will be removed from {@link Configuration} and kept only in
* {@link MutableConfiguration}. Declare the variable as
* {@link MutableConfiguration} to call this method without a warning and
* without risk of {@link UnsupportedOperationException}.
* @see MutableConfiguration
* @see RunTimeConfiguration
*/
@Deprecated(since = "2.8", forRemoval = true)
void setName(@Nullable String name);

/**
Expand All @@ -60,13 +85,20 @@ public interface Configuration extends Comparable<Configuration> {
LogicalFilterComponent<?> getRootLogicalFilterComponent();

/**
* Sets the root element of configuration. This method is only available for
* the {@link RunTimeConfiguration}.
* Sets the root element of configuration.
*
* @param rootLogicalFilterComponent a root element of configuration
* @throws UnsupportedOperationException when called on {@link DesignTimeConfiguration}
* @deprecated since 2.8, for removal in 3.0.
* This method will be removed from {@link Configuration} and kept only in
* {@link MutableConfiguration}. Declare the variable as
* {@link MutableConfiguration} to call this method without a warning and
* without risk of {@link UnsupportedOperationException}.
* @see MutableConfiguration
* @see LogicalFilterComponent
* @see RunTimeConfiguration
*/
@Deprecated(since = "2.8", forRemoval = true)
void setRootLogicalFilterComponent(LogicalFilterComponent<?> rootLogicalFilterComponent);

/**
Expand All @@ -83,8 +115,16 @@ public interface Configuration extends Comparable<Configuration> {
* Sets whether configuration is modified. If a filter component is modified,
* then a remove button appears next to it.
*
* @param modified whether configuration is modified.
*/
* @param modified whether configuration is modified
* @throws UnsupportedOperationException when called on {@link DesignTimeConfiguration}
* @deprecated since 2.8, for removal in 3.0.
* This method will be removed from {@link Configuration} and kept only in
* {@link MutableConfiguration}. Declare the variable as
* {@link MutableConfiguration} to call this method without a warning and
* without risk of {@link UnsupportedOperationException}.
* @see MutableConfiguration
*/
@Deprecated(since = "2.8", forRemoval = true)
void setModified(boolean modified);

/**
Expand All @@ -102,7 +142,15 @@ public interface Configuration extends Comparable<Configuration> {
*
* @param filterComponent a filter component
* @param modified whether the filter component of configuration is modified
*/
* @throws UnsupportedOperationException when called on {@link DesignTimeConfiguration}
* @deprecated since 2.8, for removal in 3.0.
* This method will be removed from {@link Configuration} and kept only in
* {@link MutableConfiguration}. Declare the variable as
* {@link MutableConfiguration} to call this method without a warning and
* without risk of {@link UnsupportedOperationException}.
* @see MutableConfiguration
*/
@Deprecated(since = "2.8", forRemoval = true)
void setFilterComponentModified(FilterComponent filterComponent, boolean modified);

/**
Expand All @@ -119,7 +167,15 @@ public interface Configuration extends Comparable<Configuration> {
* component becomes null.
*
* @param parameterName a parameter name of filter component
*/
* @throws UnsupportedOperationException when called on {@link DesignTimeConfiguration}
* @deprecated since 2.8, for removal in 3.0.
* This method will be removed from {@link Configuration} and kept only in
* {@link MutableConfiguration}. Declare the variable as
* {@link MutableConfiguration} to call this method without a warning and
* without risk of {@link UnsupportedOperationException}.
* @see MutableConfiguration
*/
@Deprecated(since = "2.8", forRemoval = true)
void resetFilterComponentDefaultValue(String parameterName);

/**
Expand All @@ -133,11 +189,21 @@ public interface Configuration extends Comparable<Configuration> {

/**
* Sets null as the default value for all configuration filter components.
*/
*
* @throws UnsupportedOperationException when called on {@link DesignTimeConfiguration}
* @deprecated since 2.8, for removal in 3.0.
* This method will be removed from {@link Configuration} and kept only in
* {@link MutableConfiguration}. Declare the variable as
* {@link MutableConfiguration} to call this method without a warning and
* without risk of {@link UnsupportedOperationException}.
* @see MutableConfiguration
*/
@Deprecated(since = "2.8", forRemoval = true)
void resetAllDefaultValues();

/**
* Returns whether the configuration is available for all users
*
* @return true if the configuration is available for all users, otherwise false.
*/
default boolean isAvailableForAllUsers() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
/*
* Copyright 2024 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.jmix.flowui.component.genericfilter;

import io.jmix.flowui.UiComponents;
import io.jmix.flowui.component.filter.FilterComponent;
import io.jmix.flowui.component.filter.SingleFilterComponentBase;
import io.jmix.flowui.component.genericfilter.configuration.DesignTimeConfiguration;
import io.jmix.flowui.component.logicalfilter.LogicalFilterComponent;
import org.springframework.lang.Nullable;

import java.util.ArrayList;
import java.util.List;

import static io.jmix.core.common.util.Preconditions.checkNotNullArgument;

/**
* Fluent builder for {@link DesignTimeConfiguration}.
* <p>
* Encapsulates all steps that would otherwise need to be performed manually:
* <ul>
* <li>Creating and configuring the root {@link LogicalFilterComponent}</li>
* <li>Adding filter components to the root</li>
* <li>Calling {@code setFilterComponentDefaultValue} for each component that has a value</li>
* <li>Registering the configuration via {@link GenericFilter#addConfiguration(Configuration)}</li>
* <li>Optionally activating it via {@link GenericFilter#setCurrentConfiguration(Configuration)}</li>
* </ul>
* <p>
* Obtain an instance via {@link GenericFilter#configurationBuilder()}:
* <pre>{@code
* filter.configurationBuilder()
* .id("byStatus")
* .name("Search by Status")
* .operation(LogicalFilterComponent.Operation.AND)
* .add(statusFilter)
* .add(nameFilter, "Acme") // override default value for this component
* .asDefault()
* .buildAndRegister();
* }</pre>
*/
public class DesignTimeConfigurationBuilder {

protected final GenericFilter filter;
protected final UiComponents uiComponents;

protected String id;
protected String name;
protected LogicalFilterComponent.Operation operation = LogicalFilterComponent.Operation.AND;
protected boolean makeDefault = false;

protected final List<ComponentEntry> entries = new ArrayList<>();

DesignTimeConfigurationBuilder(GenericFilter filter, UiComponents uiComponents) {
this.filter = filter;
this.uiComponents = uiComponents;
}

/**
* Sets the configuration id. Required.
*
* @param id unique configuration identifier within this filter
*/
public DesignTimeConfigurationBuilder id(String id) {
checkNotNullArgument(id, "id must not be null");
this.id = id;
return this;
}

/**
* Sets the configuration display name.
*
* @param name display name shown in the configuration selector
*/
public DesignTimeConfigurationBuilder name(@Nullable String name) {
this.name = name;
return this;
}

/**
* Sets the logical operation of the root filter component. Defaults to {@code AND}.
*
* @param operation logical operation
*/
public DesignTimeConfigurationBuilder operation(LogicalFilterComponent.Operation operation) {
checkNotNullArgument(operation, "operation must not be null");
this.operation = operation;
return this;
}

/**
* Adds a filter component to the configuration using the component's current value
* (if any) as the default.
*
* @param filterComponent filter component to add
*/
public DesignTimeConfigurationBuilder add(FilterComponent filterComponent) {
checkNotNullArgument(filterComponent, "filterComponent must not be null");
entries.add(new ComponentEntry(filterComponent, null, false));
return this;
}

/**
* Adds a filter component to the configuration, overriding its default value.
* <p>
* This is equivalent to calling {@code component.setValue(defaultValue)} followed by
* {@code config.setFilterComponentDefaultValue(parameterName, defaultValue)}.
*
* @param filterComponent filter component to add
* @param defaultValue value to set as both the component's current value and the
* configuration's default
*/
public DesignTimeConfigurationBuilder add(FilterComponent filterComponent, @Nullable Object defaultValue) {
checkNotNullArgument(filterComponent, "filterComponent must not be null");
entries.add(new ComponentEntry(filterComponent, defaultValue, true));
return this;
}

/**
* Marks this configuration as the default (currently active) configuration.
* After {@link #buildAndRegister()} the configuration will be set as the current
* configuration of the filter.
*/
public DesignTimeConfigurationBuilder asDefault() {
this.makeDefault = true;
return this;
}

/**
* Builds the {@link DesignTimeConfiguration}, registers it with the filter, and
* optionally activates it.
* <p>
* Automatically:
* <ul>
* <li>Adds each filter component to the configuration's root</li>
* <li>Calls {@code setFilterComponentDefaultValue} for every component that carries a
* value (either via the overload with an explicit default or because the component
* already has a non-null value at the time {@code add()} was called)</li>
* <li>Calls {@link GenericFilter#addConfiguration(Configuration)}</li>
* <li>If {@link #asDefault()} was called, calls
* {@link GenericFilter#setCurrentConfiguration(Configuration)}</li>
* </ul>
*
* @return the newly created and registered {@link DesignTimeConfiguration}
* @throws IllegalStateException if {@code id} was not set
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public DesignTimeConfiguration buildAndRegister() {
if (id == null) {
throw new IllegalStateException(
"DesignTimeConfigurationBuilder: 'id' is required — call .id(\"...\") before .buildAndRegister()");
}

DesignTimeConfiguration config = filter.addConfiguration(id, name, operation);
LogicalFilterComponent<?> root = config.getRootLogicalFilterComponent();

for (ComponentEntry entry : entries) {
FilterComponent fc = entry.filterComponent;

if (entry.overrideDefault) {
// Apply the explicit default value to the component.
// Best-effort: the value component may not be ready yet when no DataLoader
// is assigned (e.g. in tests or lazy initialisation scenarios).
// The default value is still persisted in the configuration below.
if (fc instanceof SingleFilterComponentBase<?> sfc) {
try {
((SingleFilterComponentBase) sfc).setValue(entry.defaultValue);
} catch (RuntimeException ignored) {
// component not fully initialised; default stored in config below
}
}
}

root.add(fc);

// Persist the default value in the configuration so it survives reset.
// Skip components without a parameter name (e.g. void JpqlFilter with Void parameterClass).
if (fc instanceof SingleFilterComponentBase<?> sfc) {
String paramName = sfc.getParameterName();
Object valueToStore = entry.overrideDefault ? entry.defaultValue : sfc.getValue();
if (paramName != null && valueToStore != null) {
config.setFilterComponentDefaultValue(paramName, valueToStore);
}
}
}

if (makeDefault) {
filter.setCurrentConfiguration(config);
}

return config;
}

// -------------------------------------------------------------------------

protected static class ComponentEntry {
final FilterComponent filterComponent;
final Object defaultValue;
final boolean overrideDefault;

ComponentEntry(FilterComponent filterComponent, @Nullable Object defaultValue, boolean overrideDefault) {
this.filterComponent = filterComponent;
this.defaultValue = defaultValue;
this.overrideDefault = overrideDefault;
}
}
}
Loading