diff --git a/pom.xml b/pom.xml
index 19e4e2e..959f179 100644
--- a/pom.xml
+++ b/pom.xml
@@ -185,6 +185,18 @@
false
+
+ exim-v1
+
+ generate
+
+
+ src/main/openapi/onecx-theme-exim-v1-openapi.yaml
+ gen.io.github.onecx.theme.rs.exim.v1
+ gen.io.github.onecx.theme.rs.exim.v1.model
+ DTOV1
+
+
diff --git a/src/main/java/io/github/onecx/theme/domain/daos/ThemeDAO.java b/src/main/java/io/github/onecx/theme/domain/daos/ThemeDAO.java
index db59779..cca50ba 100644
--- a/src/main/java/io/github/onecx/theme/domain/daos/ThemeDAO.java
+++ b/src/main/java/io/github/onecx/theme/domain/daos/ThemeDAO.java
@@ -1,5 +1,8 @@
package io.github.onecx.theme.domain.daos;
+import java.util.Set;
+import java.util.stream.Stream;
+
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.NoResultException;
import jakarta.transaction.Transactional;
@@ -11,9 +14,27 @@
import io.github.onecx.theme.domain.models.Theme_;
@ApplicationScoped
+@Transactional(Transactional.TxType.NOT_SUPPORTED)
public class ThemeDAO extends AbstractDAO {
@Transactional(Transactional.TxType.SUPPORTS)
+ public Stream findThemeByNames(Set themeNames) {
+ try {
+ var cb = this.getEntityManager().getCriteriaBuilder();
+ var cq = cb.createQuery(Theme.class);
+ var root = cq.from(Theme.class);
+
+ if (themeNames != null && !themeNames.isEmpty()) {
+ cq.where(root.get(Theme_.name).in(themeNames));
+ }
+
+ return this.getEntityManager().createQuery(cq).getResultStream();
+
+ } catch (Exception ex) {
+ throw new DAOException(ErrorKeys.ERROR_FIND_THEME_BY_NAMES, ex);
+ }
+ }
+
public Theme findThemeByName(String themeName) {
try {
var cb = this.getEntityManager().getCriteriaBuilder();
@@ -21,7 +42,7 @@ public Theme findThemeByName(String themeName) {
var root = cq.from(Theme.class);
cq.where(cb.equal(root.get(Theme_.name), themeName));
- return this.em.createQuery(cq).getSingleResult();
+ return this.getEntityManager().createQuery(cq).getSingleResult();
} catch (NoResultException nre) {
return null;
@@ -31,6 +52,8 @@ public Theme findThemeByName(String themeName) {
}
public enum ErrorKeys {
+
+ ERROR_FIND_THEME_BY_NAMES,
ERROR_FIND_THEME_BY_NAME,
}
}
diff --git a/src/main/java/io/github/onecx/theme/domain/di/mappers/DataImportMapperV1.java b/src/main/java/io/github/onecx/theme/domain/di/mappers/DataImportMapperV1.java
index 4ad5e6e..18b13b0 100644
--- a/src/main/java/io/github/onecx/theme/domain/di/mappers/DataImportMapperV1.java
+++ b/src/main/java/io/github/onecx/theme/domain/di/mappers/DataImportMapperV1.java
@@ -1,10 +1,10 @@
package io.github.onecx.theme.domain.di.mappers;
+import java.util.ArrayList;
import java.util.List;
import jakarta.inject.Inject;
-import org.mapstruct.IterableMapping;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
@@ -14,6 +14,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import gen.io.github.onecx.theme.di.v1.model.DataImportThemeDTOV1;
+import gen.io.github.onecx.theme.di.v1.model.DataImportThemesDTOV1;
import io.github.onecx.theme.domain.models.Theme;
@Mapper(uses = OffsetDateTimeMapper.class)
@@ -22,11 +23,23 @@ public abstract class DataImportMapperV1 {
@Inject
ObjectMapper mapper;
- @IterableMapping(qualifiedByName = "import")
- public abstract List importThemes(List dto);
+ public List importThemes(DataImportThemesDTOV1 request) {
+ List result = new ArrayList<>();
+ if (request == null) {
+ return result;
+ }
+ request.forEach((name, dto) -> {
+ var theme = importTheme(dto);
+ theme.setName(name);
+ result.add(theme);
+ });
+
+ return result;
+ }
@Named("import")
@Mapping(target = "id", ignore = true)
+ @Mapping(target = "name", ignore = true)
@Mapping(target = "properties", qualifiedByName = "properties")
@Mapping(target = "creationDate", ignore = true)
@Mapping(target = "creationUser", ignore = true)
@@ -35,7 +48,7 @@ public abstract class DataImportMapperV1 {
@Mapping(target = "controlTraceabilityManual", ignore = true)
@Mapping(target = "modificationCount", ignore = true)
@Mapping(target = "persisted", ignore = true)
- public abstract Theme theme(DataImportThemeDTOV1 dto);
+ public abstract Theme importTheme(DataImportThemeDTOV1 dto);
@Named("properties")
String properties(Object properties) {
diff --git a/src/main/java/io/github/onecx/theme/rs/exim/v1/controllers/ExportImportRestControllerV1.java b/src/main/java/io/github/onecx/theme/rs/exim/v1/controllers/ExportImportRestControllerV1.java
new file mode 100644
index 0000000..daaa93a
--- /dev/null
+++ b/src/main/java/io/github/onecx/theme/rs/exim/v1/controllers/ExportImportRestControllerV1.java
@@ -0,0 +1,86 @@
+package io.github.onecx.theme.rs.exim.v1.controllers;
+
+import java.util.stream.Collectors;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+import jakarta.validation.ConstraintViolationException;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.core.Response;
+
+import org.jboss.resteasy.reactive.RestResponse;
+import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
+
+import gen.io.github.onecx.theme.rs.exim.v1.ThemesExportImportApi;
+import gen.io.github.onecx.theme.rs.exim.v1.model.*;
+import io.github.onecx.theme.domain.daos.ThemeDAO;
+import io.github.onecx.theme.domain.models.Theme;
+import io.github.onecx.theme.rs.exim.v1.mappers.ExportImportExceptionMapperV1;
+import io.github.onecx.theme.rs.exim.v1.mappers.ExportImportMapperV1;
+
+@Path("/exim/v1/themes")
+@ApplicationScoped
+@Transactional(Transactional.TxType.NOT_SUPPORTED)
+public class ExportImportRestControllerV1 implements ThemesExportImportApi {
+
+ @Inject
+ ThemeDAO dao;
+ @Inject
+ ExportImportExceptionMapperV1 exceptionMapper;
+
+ @Inject
+ ExportImportMapperV1 mapper;
+
+ @Override
+ public Response exportThemes(EximExportRequestDTOV1 request) {
+ var themes = dao.findThemeByNames(request.getNames());
+
+ var data = themes.collect(Collectors.toMap(Theme::getName, theme -> theme));
+
+ if (data.isEmpty()) {
+ return Response.status(Response.Status.NOT_FOUND).build();
+ }
+ return Response.ok(mapper.create(data)).build();
+ }
+
+ @Override
+ @Transactional(Transactional.TxType.REQUIRES_NEW)
+ public Response importThemes(EximImportRequestDTOV1 request) {
+ var keys = request.getThemes().keySet();
+ var themes = dao.findThemeByNames(keys);
+ var map = themes.collect(Collectors.toMap(Theme::getName, theme -> theme));
+
+ EximImportResultThemesDTOV1 items = new EximImportResultThemesDTOV1();
+
+ request.getThemes().forEach((name, dto) -> {
+
+ var theme = map.get(name);
+ if (theme == null) {
+
+ theme = mapper.create(dto);
+ theme.setName(name);
+ dao.create(theme);
+ items.put(name, mapper.create(EximThemeResultStatusDTOV1.CREATED));
+
+ } else {
+
+ mapper.update(dto, theme);
+ dao.update(theme);
+ items.put(name, mapper.create(EximThemeResultStatusDTOV1.UPDATE));
+ }
+ });
+
+ return Response.ok(mapper.create(request, items)).build();
+ }
+
+ @ServerExceptionMapper
+ public RestResponse exception(Exception ex) {
+ return exceptionMapper.exception(ex);
+ }
+
+ @ServerExceptionMapper
+ public RestResponse constraint(ConstraintViolationException ex) {
+ return exceptionMapper.constraint(ex);
+ }
+}
diff --git a/src/main/java/io/github/onecx/theme/rs/exim/v1/mappers/ExportImportExceptionMapperV1.java b/src/main/java/io/github/onecx/theme/rs/exim/v1/mappers/ExportImportExceptionMapperV1.java
new file mode 100644
index 0000000..433d152
--- /dev/null
+++ b/src/main/java/io/github/onecx/theme/rs/exim/v1/mappers/ExportImportExceptionMapperV1.java
@@ -0,0 +1,69 @@
+package io.github.onecx.theme.rs.exim.v1.mappers;
+
+import java.util.List;
+import java.util.Set;
+
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import jakarta.validation.Path;
+import jakarta.ws.rs.core.Response;
+
+import org.jboss.resteasy.reactive.RestResponse;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.tkit.quarkus.jpa.exceptions.DAOException;
+import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper;
+
+import gen.io.github.onecx.theme.rs.exim.v1.model.*;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Mapper(uses = { OffsetDateTimeMapper.class })
+public abstract class ExportImportExceptionMapperV1 {
+
+ public RestResponse constraint(ConstraintViolationException ex) {
+ log.error("Processing theme export import rest controller error: {}", ex.getMessage());
+
+ var dto = exception("CONSTRAINT_VIOLATIONS", ex.getMessage());
+ dto.setValidations(createErrorValidationResponse(ex.getConstraintViolations()));
+ return RestResponse.status(Response.Status.BAD_REQUEST, dto);
+ }
+
+ public RestResponse exception(Exception ex) {
+ log.error("Processing theme export import rest controller error: {}", ex.getMessage());
+
+ if (ex instanceof DAOException de) {
+ return RestResponse.status(Response.Status.BAD_REQUEST,
+ exception(de.getMessageKey().name(), ex.getMessage(), de.parameters));
+ }
+ return RestResponse.status(Response.Status.INTERNAL_SERVER_ERROR,
+ exception("UNDEFINED_ERROR_CODE", ex.getMessage()));
+
+ }
+
+ @Mapping(target = "removeParametersItem", ignore = true)
+ @Mapping(target = "namedParameters", ignore = true)
+ @Mapping(target = "removeNamedParametersItem", ignore = true)
+ @Mapping(target = "parameters", ignore = true)
+ @Mapping(target = "validations", ignore = true)
+ @Mapping(target = "removeValidationsItem", ignore = true)
+ public abstract EximRestExceptionDTOV1 exception(String errorCode, String message);
+
+ @Mapping(target = "removeParametersItem", ignore = true)
+ @Mapping(target = "namedParameters", ignore = true)
+ @Mapping(target = "removeNamedParametersItem", ignore = true)
+ @Mapping(target = "validations", ignore = true)
+ @Mapping(target = "removeValidationsItem", ignore = true)
+ public abstract EximRestExceptionDTOV1 exception(String errorCode, String message, List