diff --git a/application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy b/application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy index 4fa10efcc1a..f0275b57fbf 100644 --- a/application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy +++ b/application-engine/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy @@ -90,6 +90,7 @@ import org.springframework.core.io.FileSystemResource import org.springframework.data.domain.Page import org.springframework.data.domain.PageRequest import org.springframework.data.domain.Pageable +import com.netgrif.application.engine.objects.utils.Nullable import java.time.ZoneId import java.util.stream.Collectors @@ -907,6 +908,10 @@ class ActionDelegate { return find?.call(ico) } + Nullable nullable(T value) { + return Nullable.of(value) + } + Object get(String key) { map[key] } void set(String key, Object value) { map[key] = value } @@ -2763,12 +2768,12 @@ class ActionDelegate { String resolveStoragePath(Case aCase, String fileFieldId, String fileName) { Optional> storageFieldOptional = aCase.getPetriNet().getField(fileFieldId) if (storageFieldOptional.isEmpty()) { - throw new IllegalArgumentException("Field with id [%s] does not exist on Petri Net [%s]".formatted(fileFieldId, aCase.getPetriNetId())) - } + throw new IllegalArgumentException("Field with id [%s] does not exist on Petri Net [%s]".formatted(fileFieldId, aCase.getPetriNetId())) + } Field field = storageFieldOptional.get() if (!(field instanceof StorageField)) { - throw new IllegalArgumentException("Field with id [%s] is not a StorageField on Petri Net [%s]".formatted(fileFieldId, aCase.getPetriNetId())) - } + throw new IllegalArgumentException("Field with id [%s] is not a StorageField on Petri Net [%s]".formatted(fileFieldId, aCase.getPetriNetId())) + } StorageField storageField = (StorageField) field IStorageService storageService = storageResolverService.resolve(storageField.storageType) return storageService.getPath(aCase.stringId, fileFieldId, fileName) diff --git a/application-engine/src/main/java/com/netgrif/application/engine/configuration/groovy/GroovyShellConfiguration.java b/application-engine/src/main/java/com/netgrif/application/engine/configuration/groovy/GroovyShellConfiguration.java index 6ea43c0efd5..919ae67a91e 100644 --- a/application-engine/src/main/java/com/netgrif/application/engine/configuration/groovy/GroovyShellConfiguration.java +++ b/application-engine/src/main/java/com/netgrif/application/engine/configuration/groovy/GroovyShellConfiguration.java @@ -8,6 +8,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import java.util.List; + @Configuration public class GroovyShellConfiguration { @@ -18,6 +20,7 @@ public class GroovyShellConfiguration { @ConditionalOnMissingBean public ImportCustomizer importCustomizer() { ImportCustomizer importCustomizer = new ImportCustomizer(); + importCustomizer.addStarImports(getDefaultEngineImports()); importCustomizer.addImports(actionsProperties.getImports().toArray(new String[0])); importCustomizer.addStarImports(actionsProperties.getStarImports().toArray(new String[0])); importCustomizer.addStaticStars(actionsProperties.getStaticStarImports().toArray(new String[0])); @@ -32,4 +35,14 @@ public CompilerConfiguration compilerConfiguration() { return configuration; } + + protected String[] getDefaultEngineImports() { + return new String[]{ + "com.netgrif.application.engine.objects", + "com.netgrif.application.engine.adapter.spring", + "java.time" + }; + } + + } diff --git a/application-engine/src/main/resources/application.yaml b/application-engine/src/main/resources/application.yaml index 4e2bb303c18..876ae43853c 100644 --- a/application-engine/src/main/resources/application.yaml +++ b/application-engine/src/main/resources/application.yaml @@ -159,4 +159,4 @@ netgrif: RoleActionsRunner: debug CaseFieldsExpressionRunner: debug actions: - imports: com.netgrif.application.engine.objects.workflow.domain.menu.MenuItemBody,com.netgrif.application.engine.objects.petrinet.domain.I18nString,com.netgrif.application.engine.objects.workflow.domain.menu.dashboard.DashboardManagementBody,com.netgrif.application.engine.objects.workflow.domain.menu.dashboard.DashboardItemBody + imports: com.netgrif.application.engine.objects.workflow.domain.menu.MenuItemBody,com.netgrif.application.engine.objects.petrinet.domain.I18nString,com.netgrif.application.engine.objects.workflow.domain.menu.dashboard.DashboardManagementBody,com.netgrif.application.engine.objects.workflow.domain.menu.dashboard.DashboardItemBody,com.netgrif.application.engine.objects.utils.Nullable diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/utils/Nullable.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/utils/Nullable.java new file mode 100644 index 00000000000..df50295a4ab --- /dev/null +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/utils/Nullable.java @@ -0,0 +1,305 @@ +package com.netgrif.application.engine.objects.utils; + +import java.io.Serial; +import java.io.Serializable; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Stream; + + +/** + * A utility class representing a nullable container for a value of type {@code T}. It provides methods to handle + * optionality in a fluent and functional programming style. This class is inspired by {@link Optional} + * but differs in implementation and naming conventions. + * + * @param The type of the value contained in this {@code Nullable}. + */ +public final class Nullable implements Serializable { + + @Serial + private static final long serialVersionUID = 8683452581122892189L; + + private final T value; + + private Nullable(T value) { + this.value = value; + } + + /** + * Returns the value held by this instance. + * + * @return the value contained in this instance, or {@code null} if the instance is empty + */ + public T get() { + return value; + } + + /** + * Creates a new {@code Nullable} instance containing the given value. + * + * @param value the value to wrap in a {@code Nullable} instance, can be {@code null} + * @return a {@code Nullable} instance wrapping the provided value + */ + public static Nullable of(T value) { + return new Nullable<>(value); + } + + /** + * Returns an empty {@code Nullable} instance holding no value. + * + * @param the type of the value that can be held by this {@code Nullable} instance + * @return an empty {@code Nullable} instance + */ + public static Nullable empty() { + return new Nullable<>(null); + } + + /** + * Checks if a value is present in this instance. + * + * @return {@code true} if the value is not {@code null}, otherwise {@code false} + */ + public boolean isPresent() { + return value != null; + } + + /** + * Checks if this instance holds no value. + * + * @return {@code true} if the value is {@code null}, otherwise {@code false} + */ + public boolean isEmpty() { + return value == null; + } + + /** + * If a value is present in this instance, performs the given action with the value. + * + * @param action the action to be performed if a value is present; must be non-null + */ + public void ifPresent(Consumer action) { + if (value != null) { + action.accept(value); + } + } + + /** + * Performs the given action with the value if it is present, otherwise executes the provided empty action. + * + * @param action the {@code Consumer} to be executed if the value is present; must not be null + * @param emptyAction the {@code Runnable} to be executed if the value is not present; must not be null + */ + public void ifPresentOrElse(Consumer action, Runnable emptyAction) { + if (value != null) { + action.accept(value); + } else { + emptyAction.run(); + } + } + + /** + * Converts the value held by this instance into an {@code Optional}. + * + * @return an {@code Optional} containing the value if it is present, or an empty {@code Optional} if the value is {@code null} + */ + public Optional toOptional() { + return Optional.ofNullable(value); + } + + /** + * Filters the value contained in this {@code Nullable} instance based on the provided predicate. + * If the value is present and satisfies the predicate, this instance is returned. + * If the value does not satisfy the predicate or if the instance is empty, an empty {@code Nullable} is returned. + * + * @param predicate the predicate used to evaluate the contained value; must be non-null + * @return this {@code Nullable} instance if the value satisfies the predicate, + * or an empty {@code Nullable} instance otherwise + */ + public Nullable filter(Predicate predicate) { + Objects.requireNonNull(predicate); + if (isEmpty()) { + return this; + } else { + return predicate.test(value) ? this : empty(); + } + } + + /** + * Transforms the value contained in this {@code Nullable} instance using the provided mapping function. + * If this {@code Nullable} instance is empty, an empty {@code Nullable} instance is returned. + * + * @param the type of the value produced by the mapping function + * @param mapper the function to apply to the value; must not be null + * @return a {@code Nullable} instance containing the value produced by applying the mapping function, + * or an empty {@code Nullable} instance if this instance is empty + */ + public Nullable map(Function mapper) { + Objects.requireNonNull(mapper); + if (isEmpty()) { + return empty(); + } else { + return Nullable.of(mapper.apply(value)); + } + } + + /** + * Applies the provided mapping function to the value contained in this {@code Nullable} instance, + * and returns the {@code Nullable} instance produced by the mapping function. If this instance is empty, + * an empty {@code Nullable} is returned. + * + * @param the type of the value contained in the resulting {@code Nullable} instance + * @param mapper the mapping function to apply to the value if it is present; must not be null + * @return a {@code Nullable} instance produced by applying the mapping function to the value, + * or an empty {@code Nullable} if this instance is empty + */ + public Nullable flatMap(Function> mapper) { + Objects.requireNonNull(mapper); + if (isEmpty()) { + return empty(); + } else { + @SuppressWarnings("unchecked") + Nullable r = (Nullable) mapper.apply(value); + return Objects.requireNonNull(r); + } + } + + /** + * Returns this {@code Nullable} instance if a value is present, otherwise returns the result + * of invoking the provided {@code Supplier}. + * + * @param supplier the {@code Supplier} providing an alternative {@code Nullable} instance + * if this instance is empty; must not be {@code null} + * @return this {@code Nullable} instance if it contains a value, or the {@code Nullable} instance + * provided by the supplier if this instance is empty + * @throws NullPointerException if the supplier is {@code null} or the {@code Nullable} instance + * provided by the supplier is {@code null} + */ + public Nullable or(Supplier> supplier) { + Objects.requireNonNull(supplier); + if (isPresent()) { + return this; + } else { + @SuppressWarnings("unchecked") + Nullable r = (Nullable) supplier.get(); + return Objects.requireNonNull(r); + } + } + + /** + * Creates a sequential {@code Stream} containing the value held by this {@code Nullable} instance, + * if a value is present. If this instance is empty, returns an empty {@code Stream}. + * + * @return a {@code Stream} containing the value if it is present, or an empty {@code Stream} otherwise + */ + public Stream stream() { + if (isEmpty()) { + return Stream.empty(); + } else { + return Stream.of(value); + } + } + + /** + * Returns the value held by this instance if it is non-null; otherwise, returns the specified default value. + * + * @param other the value to be returned if the current value is null + * @return the value held by this instance if it is non-null, or the specified default value if the value is null + */ + public T orElse(T other) { + return value != null ? value : other; + } + + /** + * Returns the value held by this instance if it is present; otherwise, + * returns the result produced by the provided {@code Supplier}. + * + * @param supplier a {@code Supplier} to provide an alternative value + * if the current instance holds no value; must not be {@code null} + * @return the value contained in this instance if present, or the value + * obtained from the supplied {@code Supplier} if the instance is empty + * @throws NullPointerException if the provided supplier is {@code null} + */ + public T orElseGet(Supplier supplier) { + return value != null ? value : supplier.get(); + } + + /** + * Returns the value held by this instance if it is present; otherwise, + * throws a {@link NoSuchElementException}. + * + * @return the value contained in this instance + * @throws NoSuchElementException if no value is present in this instance + */ + public T orElseThrow() { + if (value == null) { + throw new NoSuchElementException("No value present"); + } + return value; + } + + /** + * Returns the value held by this instance if it is present, otherwise throws an exception + * supplied by the provided {@code Supplier}. + * + * @param the type of the exception to be thrown + * @param exceptionSupplier the supplier that provides the exception to be thrown if no value is present; must not be null + * @return the value held by this instance if it is present + * @throws X if no value is present + * @throws NullPointerException if the {@code exceptionSupplier} is null + */ + public T orElseThrow(Supplier exceptionSupplier) throws X { + if (value != null) { + return value; + } else { + throw exceptionSupplier.get(); + } + } + + /** + * Compares this {@code Nullable} instance with the specified object for equality. + * Two {@code Nullable} instances are considered equal if they both contain the + * same value or are both empty. + * + * @param obj the object to be compared for equality with this {@code Nullable} instance + * @return {@code true} if the specified object is equal to this {@code Nullable} instance, + * {@code false} otherwise + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + return obj instanceof Nullable other + && Objects.equals(value, other.value); + } + + /** + * Computes the hash code for this instance based on its value field. + * + * @return the hash code as an integer for this instance, + * calculated using the value field or default if the field is null. + */ + @Override + public int hashCode() { + return Objects.hashCode(value); + } + + /** + * Returns a string representation of the object. + * If the encapsulated value is non-null, the string will include the value. + * If the encapsulated value is null, a representation indicating emptiness will be returned. + * + * @return a string representation of the object, indicating the encapsulated value or an empty state + */ + @Override + public String toString() { + return value != null ? value.toString() : ""; + } + +}