Any request parameter whose name does not start with {@code _} and is not a reserved
+ * pagination keyword ({@code page}, {@code size}) is treated as a potential field filter.
+ * The filter value is matched against the entity field registered in the {@link ViewDescriptor}
+ * and the appropriate {@link QueryConditions} condition is selected based on the field's Java type:
+ *
+ * Parameters for fields not present in the descriptor are silently ignored.
+ *
+ * ) fieldType, rawValue.toUpperCase());
+ query.and(paramName, QueryConditions.eq(enumValue));
+ }
+ // Date, entity references, collections → skipped (require richer handling)
+ } catch (Exception e) {
+ LoggingService.get(RestNavigationQuerySupport.class)
+ .warn("Ignoring filter param '" + paramName + "=" + rawValue + "': " + e.getMessage());
+ }
+ });
+ }
+
+ // -------------------------------------------------------------------------
+ // Dynamic ORDER BY from request parameters
+ // -------------------------------------------------------------------------
+
+ /**
+ * Applies dynamic ordering to the given {@link QueryBuilder} based on the HTTP request
+ * parameters {@code _sort} and {@code _order}.
+ *
+ * Supported parameters:
+ *
+ * - {@code _sort} — comma-separated list of field names (e.g., {@code name,age}).
+ * - {@code _order} — comma-separated list of directions ({@code asc}/{@code desc}).
+ * When fewer directions than fields are given, the last direction is reused.
+ * Defaults to {@code asc}.
+ *
+ *
+ * Each field is validated against the {@link ViewDescriptor} when one is provided;
+ * unknown fields are silently skipped to prevent JPQL injection.
+ *
+ * Usage examples:
+ * {@code
+ * GET /api/users?_sort=name → ORDER BY e.name ASC
+ * GET /api/users?_sort=name&_order=desc → ORDER BY e.name DESC
+ * GET /api/users?_sort=name,age&_order=asc,desc → ORDER BY e.name ASC, e.age DESC
+ * }
+ *
+ * @param request the current HTTP request
+ * @param query the query builder to augment with ORDER BY clauses
+ * @param descriptor the view descriptor used to validate field names; if {@code null},
+ * field names are accepted as-is
+ */
+ public static void applyRequestSorting(HttpServletRequest request, QueryBuilder query, ViewDescriptor descriptor) {
+ String sortParam = request.getParameter("_sort");
+ if (sortParam == null || sortParam.isBlank()) {
+ return;
+ }
+
+ String orderParam = request.getParameter("_order");
+ String[] fields = sortParam.split(",");
+ String[] directions = (orderParam != null && !orderParam.isBlank())
+ ? orderParam.split(",")
+ : new String[0];
+
+ for (int i = 0; i < fields.length; i++) {
+ String fieldName = fields[i].strip();
+ if (fieldName.isEmpty()) {
+ continue;
+ }
+
+ if (descriptor != null && descriptor.getField(fieldName) == null) {
+ LoggingService.get(RestNavigationQuerySupport.class)
+ .warn("Ignoring unknown sort field '" + fieldName + "'");
+ continue;
+ }
+
+ String rawDirection = (directions.length > 0)
+ ? directions[Math.min(i, directions.length - 1)].strip()
+ : "asc";
+ String direction = "desc".equalsIgnoreCase(rawDirection) ? "DESC" : "ASC";
+
+ query.orderBy(fieldName + " " + direction);
+ }
+ }
+
+ // -------------------------------------------------------------------------
+ // Pagination param reader
+ // -------------------------------------------------------------------------
+
+ /**
+ * Reads an integer request parameter by name. Returns {@code 0} if the parameter
+ * is absent or cannot be parsed as an integer.
+ *
+ * @param request the current HTTP request
+ * @param name the name of the request parameter
+ * @return the parsed integer value, or {@code 0} if not present / invalid
+ */
+ public static int getParameterNumber(HttpServletRequest request, String name) {
+ String value = request.getParameter(name);
+ if (value != null) {
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException ignored) {
+ // fall through
+ }
+ }
+ return 0;
+ }
+
+ // -------------------------------------------------------------------------
+ // Internal numeric parser
+ // -------------------------------------------------------------------------
+
+ /**
+ * Parses a raw string value into the target numeric type.
+ *
+ * Supports {@link Integer}, {@code int}, {@link Long}, {@code long},
+ * {@link Double}, {@code double}, {@link Float}, {@code float},
+ * {@link Short}, {@code short}, {@link Byte}, {@code byte},
+ * and {@link BigDecimal}.
+ *
+ * @param raw the raw string to parse
+ * @param targetType the target numeric {@link Class}
+ * @return the parsed number, or {@code null} if the type is not supported
+ */
+ static Object parseNumber(String raw, Class> targetType) {
+ if (targetType == Integer.class || targetType == int.class) return Integer.parseInt(raw);
+ if (targetType == Long.class || targetType == long.class) return Long.parseLong(raw);
+ if (targetType == Double.class || targetType == double.class) return Double.parseDouble(raw);
+ if (targetType == Float.class || targetType == float.class) return Float.parseFloat(raw);
+ if (targetType == Short.class || targetType == short.class) return Short.parseShort(raw);
+ if (targetType == Byte.class || targetType == byte.class) return Byte.parseByte(raw);
+ if (targetType == BigDecimal.class) return new BigDecimal(raw);
+ return null;
+ }
+}
+
+
diff --git a/platform/core/web/src/main/java/tools/dynamia/web/navigation/RestNavigationReadOperation.java b/platform/core/web/src/main/java/tools/dynamia/web/navigation/RestNavigationReadOperation.java
new file mode 100644
index 00000000..a108ca5e
--- /dev/null
+++ b/platform/core/web/src/main/java/tools/dynamia/web/navigation/RestNavigationReadOperation.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2023 Dynamia Soluciones IT S.A.S - NIT 900302344-1
+ * Colombia / South America
+ *
+ * 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 tools.dynamia.web.navigation;
+
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.http.ResponseEntity;
+import tools.dynamia.commons.collect.PagedList;
+import tools.dynamia.crud.CrudPage;
+import tools.dynamia.domain.query.DataPaginator;
+import tools.dynamia.domain.query.QueryParameters;
+import tools.dynamia.domain.util.QueryBuilder;
+import tools.dynamia.navigation.PageNotFoundException;
+import tools.dynamia.viewers.ViewDescriptor;
+import tools.dynamia.web.navigation.RestNavigationContext.ListResult;
+
+import java.util.List;
+
+/**
+ * Handles the read operations for the REST navigation API.
+ *
+ * Covers two HTTP verbs:
+ *
+ * - GET (collection) — paginated list with static conditions, dynamic filters, and sorting.
+ * - GET (single) — individual entity retrieval by numeric ID.
+ *
+ *
+ * Both operations honour the {@code _metadata} query parameter, which short-circuits the normal
+ * response and returns the entity's {@link ViewDescriptor} as JSON instead.
+ *
+ * @author Mario A. Serrano Leones
+ * @see RestNavigationQuerySupport
+ * @see RestNavigationContext
+ */
+public class RestNavigationReadOperation {
+
+ /** Default page size when the client does not supply a {@code size} parameter. */
+ private static final int DEFAULT_PAGINATION_SIZE = 50;
+
+ private final RestNavigationContext ctx;
+
+ /**
+ * Constructs a new {@code RestNavigationReadOperation}.
+ *
+ * @param ctx the shared navigation context providing infrastructure dependencies
+ */
+ public RestNavigationReadOperation(RestNavigationContext ctx) {
+ this.ctx = ctx;
+ }
+
+ // -------------------------------------------------------------------------
+ // Read all (paginated collection)
+ // -------------------------------------------------------------------------
+
+ /**
+ * Returns a paginated collection of entities for the {@link CrudPage} resolved from {@code path}.
+ *
+ * Processing pipeline:
+ *
+ * - Resolve and access-check the {@link CrudPage}.
+ * - Short-circuit with metadata when {@code _metadata} is present.
+ * - Apply static conditions from the {@link ViewDescriptor}.
+ * - Apply dynamic field filters from request parameters.
+ * - Apply dynamic ordering from {@code _sort} / {@code _order}.
+ * - Paginate using {@code page} / {@code size}.
+ *
+ *
+ * @param path the navigation path resolving to a {@link CrudPage}
+ * @param request the current HTTP request
+ * @return a paginated JSON response, or a metadata JSON response when {@code _metadata} is requested
+ */
+ public ResponseEntity readAll(String path, HttpServletRequest request) {
+ CrudPage page = ctx.findCrudPage(path);
+ Class entityClass = page.getEntityClass();
+
+ ViewDescriptor descriptor = RestNavigationContext.getJsonTableDescriptor(entityClass);
+ ResponseEntity metadata = RestNavigationContext.getMetadata(request, descriptor);
+ if (metadata != null) {
+ return metadata;
+ }
+
+ QueryBuilder query = QueryBuilder.select().from(entityClass, "e");
+ QueryParameters pageParams = (QueryParameters) page.getAttribute("queryParameters");
+ if (pageParams != null) {
+ query.where(pageParams);
+ }
+
+ RestNavigationQuerySupport.parseConditions(query, descriptor);
+ RestNavigationQuerySupport.applyRequestFilters(request, query, descriptor);
+ RestNavigationQuerySupport.applyRequestSorting(request, query, descriptor);
+
+ int pageSize = RestNavigationQuerySupport.getParameterNumber(request, "size");
+ int currentPage = RestNavigationQuerySupport.getParameterNumber(request, "page");
+ if (pageSize == 0) {
+ pageSize = DEFAULT_PAGINATION_SIZE;
+ }
+ query.getQueryParameters().paginate(pageSize);
+
+ List content = ctx.getCrudService().executeQuery(query);
+ DataPaginator paginator = query.getQueryParameters().getPaginator();
+ if (paginator != null) {
+ paginator.setPage(currentPage);
+ }
+
+ return RestNavigationContext.buildJsonResponse(descriptor, buildListResult(content, paginator, currentPage), "OK");
+ }
+
+ // -------------------------------------------------------------------------
+ // Read one (single entity)
+ // -------------------------------------------------------------------------
+
+ /**
+ * Returns a single entity identified by {@code id} from the {@link CrudPage} resolved by {@code path}.
+ *
+ * @param path the navigation path resolving to a {@link CrudPage}
+ * @param id the entity identifier
+ * @param request the current HTTP request
+ * @return a JSON response with the entity data
+ * @throws PageNotFoundException if no entity with the given {@code id} exists
+ */
+ public ResponseEntity readOne(String path, Long id, HttpServletRequest request) {
+ CrudPage page = ctx.findCrudPage(path);
+ Class entityClass = page.getEntityClass();
+
+ ViewDescriptor descriptor = RestNavigationContext.getJsonFormDescriptor(entityClass);
+ ResponseEntity metadata = RestNavigationContext.getMetadata(request, descriptor);
+ if (metadata != null) {
+ return metadata;
+ }
+
+ @SuppressWarnings("unchecked") Object result = ctx.getCrudService().find(entityClass, id);
+ if (result == null) {
+ throw new PageNotFoundException(entityClass.getSimpleName() + " with id " + id + " not found");
+ }
+
+ return RestNavigationContext.buildJsonResponse(descriptor, result, "OK");
+ }
+
+ // -------------------------------------------------------------------------
+ // ListResult builder (also used by other callers)
+ // -------------------------------------------------------------------------
+
+ /**
+ * Builds a {@link ListResult} from a raw content list, handling paged data sources
+ * when the content is a {@link PagedList}.
+ *
+ * @param content the raw list returned by the query
+ * @param paginator the {@link DataPaginator} associated with the query; may be {@code null}
+ * @param currentPage the requested page number (1-based); ignored when {@code <= 0}
+ * @return a populated {@link ListResult} ready for serialization
+ */
+ public static ListResult buildListResult(List content, DataPaginator paginator, int currentPage) {
+ ListResult result = new ListResult();
+ if (content instanceof PagedList pagedList && paginator != null) {
+ if (currentPage > 0) {
+ pagedList.getDataSource().setActivePage(currentPage);
+ }
+ result.setData(pagedList.getDataSource().getPageData());
+ result.setPageable(paginator);
+ } else {
+ result.setData(content);
+ }
+ result.setResponse("OK");
+ return result;
+ }
+}
+
diff --git a/platform/core/web/src/main/java/tools/dynamia/web/navigation/RestNavigationUpdateOperation.java b/platform/core/web/src/main/java/tools/dynamia/web/navigation/RestNavigationUpdateOperation.java
new file mode 100644
index 00000000..11d0ac31
--- /dev/null
+++ b/platform/core/web/src/main/java/tools/dynamia/web/navigation/RestNavigationUpdateOperation.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 Dynamia Soluciones IT S.A.S - NIT 900302344-1
+ * Colombia / South America
+ *
+ * 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 tools.dynamia.web.navigation;
+
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.http.ResponseEntity;
+import tools.dynamia.commons.ObjectOperations;
+import tools.dynamia.commons.StringPojoParser;
+import tools.dynamia.commons.logger.AbstractLoggable;
+import tools.dynamia.crud.CrudPage;
+import tools.dynamia.navigation.PageNotFoundException;
+import tools.dynamia.viewers.Field;
+import tools.dynamia.viewers.JsonViewDescriptorDeserializer;
+import tools.dynamia.viewers.ViewDescriptor;
+import tools.jackson.core.JacksonException;
+import tools.jackson.databind.JsonNode;
+
+/**
+ * Handles the update operation for the REST navigation API.
+ *
+ * Applies a partial JSON patch to an existing entity: only the fields present in the
+ * request body are updated; all other fields retain their current persisted values.
+ * The entity is first loaded from the database, patched in memory, and then saved back
+ * via {@link tools.dynamia.domain.services.CrudService#update}.
+ *
+ * @author Mario A. Serrano Leones
+ * @see RestNavigationContext
+ */
+public class RestNavigationUpdateOperation extends AbstractLoggable {
+
+ private final RestNavigationContext ctx;
+
+ /**
+ * Constructs a new {@code RestNavigationUpdateOperation}.
+ *
+ * @param ctx the shared navigation context providing infrastructure dependencies
+ */
+ public RestNavigationUpdateOperation(RestNavigationContext ctx) {
+ this.ctx = ctx;
+ }
+
+ /**
+ * Updates the entity identified by {@code id} by applying the fields present in {@code jsonData}.
+ *
+ * Processing steps:
+ *
+ * - Load the entity from the database; throw {@link PageNotFoundException} if absent.
+ * - Parse the JSON body into a {@link JsonNode} tree.
+ * - For each field present in both the JSON and the entity's {@link ViewDescriptor},
+ * invoke the corresponding setter via reflection.
+ * - Persist the patched entity and return it as JSON.
+ *
+ *
+ * @param path the navigation path resolving to a {@link CrudPage}
+ * @param id the entity identifier
+ * @param jsonData the JSON object containing the fields to update
+ * @param request the current HTTP request (reserved for future use)
+ * @return a {@code 200 OK} JSON response containing the updated entity
+ * @throws PageNotFoundException if no entity with the given {@code id} exists
+ */
+ public ResponseEntity update(String path, Long id, String jsonData, HttpServletRequest request) {
+ CrudPage page = ctx.findCrudPage(path);
+ Class entityClass = page.getEntityClass();
+
+ @SuppressWarnings("unchecked") final Object entity = ctx.getCrudService().find(entityClass, id);
+ if (entity == null) {
+ throw new PageNotFoundException(entityClass.getSimpleName() + " with id " + id + " not found");
+ }
+
+ ViewDescriptor descriptor = RestNavigationContext.getJsonFormDescriptor(entityClass, true);
+ try {
+ JsonNode node = StringPojoParser.createJsonMapper().readTree(jsonData);
+ node.properties().forEach(entry -> {
+ Field field = descriptor.getField(entry.getKey());
+ if (field != null) {
+ Object fieldValue = JsonViewDescriptorDeserializer.getNodeValue(field.getPropertyInfo(), entry.getValue());
+ ObjectOperations.invokeSetMethod(entity, field.getPropertyInfo(), fieldValue);
+ }
+ });
+ } catch (JacksonException e) {
+ log("Error updating entity", e);
+ }
+
+ return RestNavigationContext.buildJsonResponse(descriptor, ctx.getCrudService().update(entity), "Updated Successfully");
+ }
+}
+
diff --git a/platform/starters/zk-starter/pom.xml b/platform/starters/zk-starter/pom.xml
index 35f85f3c..c874f28a 100644
--- a/platform/starters/zk-starter/pom.xml
+++ b/platform/starters/zk-starter/pom.xml
@@ -4,7 +4,7 @@
tools.dynamia
tools.dynamia.parent
- 26.2.3
+ 26.3.0
../../../pom.xml
@@ -28,22 +28,22 @@
tools.dynamia
tools.dynamia.app
- 26.2.3
+ 26.3.0
tools.dynamia
tools.dynamia.commons
- 26.2.3
+ 26.3.0
tools.dynamia
tools.dynamia.zk
- 26.2.3
+ 26.3.0
tools.dynamia
tools.dynamia.domain.jpa
- 26.2.3
+ 26.3.0
org.hibernate.validator
diff --git a/platform/ui/ui-shared/pom.xml b/platform/ui/ui-shared/pom.xml
index 75461ff7..2631112f 100644
--- a/platform/ui/ui-shared/pom.xml
+++ b/platform/ui/ui-shared/pom.xml
@@ -23,7 +23,7 @@
tools.dynamia
tools.dynamia.parent
- 26.2.3
+ 26.3.0
../../../pom.xml
@@ -64,17 +64,17 @@
tools.dynamia
tools.dynamia.integration
- 26.2.3
+ 26.3.0
tools.dynamia
tools.dynamia.commons
- 26.2.3
+ 26.3.0
tools.dynamia
tools.dynamia.io
- 26.2.3
+ 26.3.0
diff --git a/platform/ui/zk/pom.xml b/platform/ui/zk/pom.xml
index fcade802..998e96a7 100644
--- a/platform/ui/zk/pom.xml
+++ b/platform/ui/zk/pom.xml
@@ -21,7 +21,7 @@
tools.dynamia.parent
tools.dynamia
- 26.2.3
+ 26.3.0
../../../pom.xml
4.0.0
@@ -99,31 +99,31 @@
tools.dynamia
tools.dynamia.web
- 26.2.3
+ 26.3.0
tools.dynamia
tools.dynamia.navigation
- 26.2.3
+ 26.3.0
tools.dynamia
tools.dynamia.ui
- 26.2.3
+ 26.3.0
tools.dynamia
tools.dynamia.domain
- 26.2.3
+ 26.3.0
tools.dynamia
tools.dynamia.viewers
- 26.2.3
+ 26.3.0
org.yaml
@@ -134,19 +134,19 @@
tools.dynamia
tools.dynamia.crud
- 26.2.3
+ 26.3.0
tools.dynamia
tools.dynamia.reports
- 26.2.3
+ 26.3.0
compile
tools.dynamia
tools.dynamia.templates
- 26.2.3
+ 26.3.0
compile
diff --git a/platform/ui/zk/src/main/java/tools/dynamia/zk/ui/InputPanel.java b/platform/ui/zk/src/main/java/tools/dynamia/zk/ui/InputPanel.java
index 2f13afc4..ac7e1d40 100644
--- a/platform/ui/zk/src/main/java/tools/dynamia/zk/ui/InputPanel.java
+++ b/platform/ui/zk/src/main/java/tools/dynamia/zk/ui/InputPanel.java
@@ -20,38 +20,32 @@
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.HtmlBasedComponent;
import org.zkoss.zk.ui.event.Events;
-import org.zkoss.zul.Button;
-import org.zkoss.zul.Decimalbox;
-import org.zkoss.zul.Div;
-import org.zkoss.zul.Doublebox;
-import org.zkoss.zul.Label;
-import org.zkoss.zul.Textbox;
-import org.zkoss.zul.Vbox;
-import org.zkoss.zul.Window;
+import org.zkoss.zul.*;
import tools.dynamia.commons.ObjectOperations;
import tools.dynamia.commons.logger.Loggable;
-import tools.dynamia.integration.Containers;
-import tools.dynamia.viewers.ComponentCustomizer;
import tools.dynamia.viewers.Field;
-import tools.dynamia.viewers.FieldCustomizer;
import tools.dynamia.viewers.util.ComponentCustomizerUtil;
+import tools.dynamia.viewers.util.Viewers;
import tools.dynamia.web.util.HttpUtils;
import tools.dynamia.zk.util.ZKBindingUtil;
import tools.dynamia.zk.util.ZKUtil;
-import java.util.Collection;
+import java.io.Serial;
@SuppressWarnings("rawtypes")
public class InputPanel extends Div implements Loggable {
public static final String ON_INPUT = "onInput";
+ @Serial
private static final long serialVersionUID = 7388726856898185544L;
+ public static final String BINDING_ATTRIBUTE = "bindingAttribute";
private HtmlBasedComponent textbox;
private Label label;
private Button okButton;
private Object value;
private final Class inputClass;
+ private Field inputField;
public InputPanel() {
this(null, null, String.class);
@@ -70,9 +64,10 @@ public InputPanel(String label, Object value, Class inputClass) {
this.value = value;
renderView(label);
+ String bindingAttribute = inputField != null && inputField.getParam(BINDING_ATTRIBUTE) != null ? inputField.getParam(BINDING_ATTRIBUTE).toString() : null;
Binder binder = ZKBindingUtil.createBinder();
ZKBindingUtil.initBinder(binder, this, this);
- ZKBindingUtil.bindComponent(binder, textbox, "inputPanel.value", null);
+ ZKBindingUtil.bindComponent(binder, textbox, bindingAttribute, "inputPanel.value", null);
ZKBindingUtil.bindBean(this, "inputPanel", this);
binder.loadComponent(this, false);
addListeners();
@@ -127,29 +122,24 @@ private void renderView(String label) {
private Component buildTextbox() {
Class componClass = null;
- Field field = new Field("field", inputClass);
- Collection customizers = Containers.get().findObjects(FieldCustomizer.class);
- if (customizers != null) {
- for (FieldCustomizer fieldCustomizer : customizers) {
- fieldCustomizer.customize("form", field);
- }
- }
+ inputField = new Field("field", inputClass);
+ Viewers.customizeField("form", inputField);
- if (field.getComponentClass() != null) {
- componClass = field.getComponentClass();
+ if (inputField.getComponentClass() != null) {
+ componClass = inputField.getComponentClass();
} else {
componClass = Textbox.class;
}
Component comp = (Component) ObjectOperations.newInstance(componClass);
- if (field.getComponentCustomizer() != null) {
+ if (inputField.getComponentCustomizer() != null) {
try {
- ComponentCustomizerUtil.customizeComponent(field, comp, field.getComponentCustomizer());
+ ComponentCustomizerUtil.customizeComponent(inputField, comp, inputField.getComponentCustomizer());
} catch (Exception e) {
log("Cannot create component customizer", e);
}
}
- ObjectOperations.setupBean(comp, field.getParams());
+ ObjectOperations.setupBean(comp, inputField.getParams());
return comp;
}
diff --git a/pom.xml b/pom.xml
index f8a0e5fd..b93dc3b5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,7 +24,7 @@
4.0.0
tools.dynamia
tools.dynamia.parent
- 26.2.3
+ 26.3.0
pom
Dynamia Soluciones IT SAS
@@ -57,7 +57,7 @@
${project.baseUri}
- 4.0.2
+ 4.0.3
2.2.38
1
diff --git a/themes/pom.xml b/themes/pom.xml
index 5eddb11d..000ddadf 100644
--- a/themes/pom.xml
+++ b/themes/pom.xml
@@ -6,7 +6,7 @@
tools.dynamia
tools.dynamia.parent
- 26.2.3
+ 26.3.0
../pom.xml
diff --git a/themes/theme-dynamical/sources/pom.xml b/themes/theme-dynamical/sources/pom.xml
index c714dffc..44b512f1 100644
--- a/themes/theme-dynamical/sources/pom.xml
+++ b/themes/theme-dynamical/sources/pom.xml
@@ -24,7 +24,7 @@
tools.dynamia.themes
tools.dynamia.themes.parent
- 26.2.3
+ 26.3.0
../../pom.xml
@@ -102,7 +102,7 @@
tools.dynamia
tools.dynamia.zk
- 26.2.3
+ 26.3.0
provided