From 51eb98dcd90d034766c2f23c420a10ac21ea80e2 Mon Sep 17 00:00:00 2001 From: adrian-carneiro-imatia Date: Wed, 3 Sep 2025 11:15:22 +0200 Subject: [PATCH 01/65] Implemented open ai client for image analysis --- ontimize-jee-webclient-addons/pom.xml | 5 + .../webclient/openai/client/OpenAiClient.java | 28 ++++++ .../openai/model/OpenAiClientConfig.java | 47 +++++++++ .../webclient/openai/model/OpenAiModel.java | 47 +++++++++ .../openai/model/ProcessRequest.java | 43 +++++++++ .../webclient/openai/model/ProcessResult.java | 30 ++++++ .../service/OpenAiImageProcessorService.java | 95 +++++++++++++++++++ .../openai/util/JsonSchemaValidator.java | 30 ++++++ .../webclient/openai/util/PromptBuilder.java | 23 +++++ 9 files changed, 348 insertions(+) create mode 100644 ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/client/OpenAiClient.java create mode 100644 ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/OpenAiClientConfig.java create mode 100644 ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/OpenAiModel.java create mode 100644 ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/ProcessRequest.java create mode 100644 ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/ProcessResult.java create mode 100644 ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java create mode 100644 ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java create mode 100644 ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/PromptBuilder.java diff --git a/ontimize-jee-webclient-addons/pom.xml b/ontimize-jee-webclient-addons/pom.xml index 60c22f9a..6fc86acc 100644 --- a/ontimize-jee-webclient-addons/pom.xml +++ b/ontimize-jee-webclient-addons/pom.xml @@ -165,6 +165,11 @@ org-junit pom + + com.github.erosb + everit-json-schema + 1.14.6 + diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/client/OpenAiClient.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/client/OpenAiClient.java new file mode 100644 index 00000000..2507e2e3 --- /dev/null +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/client/OpenAiClient.java @@ -0,0 +1,28 @@ +package com.ontimize.jee.webclient.openai.client; + + +import com.ontimize.jee.webclient.openai.model.OpenAiClientConfig; +import com.ontimize.jee.webclient.openai.model.ProcessRequest; +import com.ontimize.jee.webclient.openai.model.ProcessResult; +import com.ontimize.jee.webclient.openai.service.OpenAiImageProcessorService; +import com.ontimize.jee.webclient.openai.util.JsonSchemaValidator; +import com.ontimize.jee.webclient.openai.util.PromptBuilder; + +public class OpenAiClient { + + private final OpenAiClientConfig config; + + public OpenAiClient(OpenAiClientConfig config) { + this.config = config; + } + + public ProcessResult processImage(ProcessRequest request) { + OpenAiImageProcessorService service = new OpenAiImageProcessorService<>( + config, + new PromptBuilder(), + new JsonSchemaValidator() + ); + + return service.processImage(request); + } +} \ No newline at end of file diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/OpenAiClientConfig.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/OpenAiClientConfig.java new file mode 100644 index 00000000..207f55f8 --- /dev/null +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/OpenAiClientConfig.java @@ -0,0 +1,47 @@ +package com.ontimize.jee.webclient.openai.model; + +public class OpenAiClientConfig { + private String apiKey; + private OpenAiModel model; + private int maxTokens; + private double temperature; + + public OpenAiClientConfig(String apiKey, OpenAiModel model, int maxTokens, double temperature) { + this.apiKey = apiKey; + this.model = model; + this.maxTokens = maxTokens; + this.temperature = temperature; + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + public OpenAiModel getModel() { + return model; + } + + public void setModel(OpenAiModel model) { + this.model = model; + } + + public int getMaxTokens() { + return maxTokens; + } + + public void setMaxTokens(int maxTokens) { + this.maxTokens = maxTokens; + } + + public double getTemperature() { + return temperature; + } + + public void setTemperature(double temperature) { + this.temperature = temperature; + } +} \ No newline at end of file diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/OpenAiModel.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/OpenAiModel.java new file mode 100644 index 00000000..cdfbcbc7 --- /dev/null +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/OpenAiModel.java @@ -0,0 +1,47 @@ +package com.ontimize.jee.webclient.openai.model; + +public enum OpenAiModel { + GPT_3_5_TURBO("gpt-3.5-turbo"), + GPT_3_5_TURBO_16K_0613("gpt-3.5-turbo-16k-0613"), + GPT_4("gpt-4"), + GPT_4_TURBO("gpt-4-turbo"), + GPT_4_TURBO_PREVIEW("gpt-4-turbo-preview"), + GPT_4_1("gpt-4.1"), + GPT_4_1_MINI("gpt-4.1-mini"), + GPT_4_1_NANO("gpt-4.1-nano"), + GPT_4_5_PREVIEW("gpt-4.5-preview"), + GPT_4O("gpt-4o"), + GPT_4O_MINI("gpt-4o-mini"), + GPT_4O_SEARCH_PREVIEW("gpt-4o-search-preview"), + GPT_IMAGE_1("gpt-image-1"), + DALL_E_2("dall-e-2"), + DALL_E_3("dall-e-3"), + O1("o1"), + O1_MINI("o1-mini"), + O1_PREVIEW("o1-preview"), + O1_PRO("o1-pro"), + O3("o3"), + O3_MINI("o3-mini"), + O3_PRO("o3-pro"), + O3_DEEP_RESEARCH("o3-deep-research"), + O4_MINI("o4-mini"), + O4_MINI_DEEP_RESEARCH("o4-mini-deep-research"), + GPT_OSS_120B("gpt-oss-120b"), + GPT_OSS_20B("gpt-oss-20b"), + TEXT_EMBEDDING_ADA_002("text-embedding-ada-002"), + TEXT_EMBEDDING_3_SMALL("text-embedding-3-small"), + TEXT_EMBEDDING_3_LARGE("text-embedding-3-large"), + TEXT_MODERATION_STABLE("text-moderation-stable"), + TEXT_MODERATION_LATEST("text-moderation-latest"), + WHISPER_1("whisper-1"); + + private final String value; + + OpenAiModel(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/ProcessRequest.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/ProcessRequest.java new file mode 100644 index 00000000..e0ab4db2 --- /dev/null +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/ProcessRequest.java @@ -0,0 +1,43 @@ +package com.ontimize.jee.webclient.openai.model; + +import org.springframework.web.multipart.MultipartFile; + +public class ProcessRequest { + + private MultipartFile file; + private String prompt; + private int retries; + private Class outputClass; + + public MultipartFile getFile() { + return file; + } + + public void setFile(MultipartFile file) { + this.file = file; + } + + public String getPrompt() { + return prompt; + } + + public void setPrompt(String prompt) { + this.prompt = prompt; + } + + public Class getOutputClass() { + return outputClass; + } + + public void setOutputClass(Class outputClass) { + this.outputClass = outputClass; + } + + public int getRetries() { + return retries; + } + + public void setRetries(int retries) { + this.retries = retries; + } +} \ No newline at end of file diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/ProcessResult.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/ProcessResult.java new file mode 100644 index 00000000..5e7bb836 --- /dev/null +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/ProcessResult.java @@ -0,0 +1,30 @@ +package com.ontimize.jee.webclient.openai.model; + +import java.util.List; + +public class ProcessResult { + private T data; + private List errors; + private int retries; + + public ProcessResult() { + } + + public ProcessResult(T data, List errors, int retries) { + this.data = data; + this.errors = errors; + this.retries = retries; + } + + public T getData() { + return data; + } + + public List getErrors() { + return errors; + } + + public int getRetries() { + return retries; + } +} diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java new file mode 100644 index 00000000..c9cccf2e --- /dev/null +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java @@ -0,0 +1,95 @@ +package com.ontimize.jee.webclient.openai.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ontimize.jee.webclient.openai.model.OpenAiClientConfig; +import com.ontimize.jee.webclient.openai.model.ProcessRequest; +import com.ontimize.jee.webclient.openai.model.ProcessResult; +import com.ontimize.jee.webclient.openai.util.JsonSchemaValidator; +import com.ontimize.jee.webclient.openai.util.PromptBuilder; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.multipart.MultipartFile; + +import java.util.*; + +public class OpenAiImageProcessorService { + private final OpenAiClientConfig config; + private final PromptBuilder promptBuilder; + private final JsonSchemaValidator jsonSchemaValidator; + private final ObjectMapper objectMapper = new ObjectMapper(); + + public OpenAiImageProcessorService(OpenAiClientConfig config, + PromptBuilder promptBuilder, + JsonSchemaValidator jsonSchemaValidator) { + this.config = config; + this.promptBuilder = promptBuilder; + this.jsonSchemaValidator = jsonSchemaValidator; + } + + public ProcessResult processImage(ProcessRequest request) { + int actualTry = 0; + List errors = new ArrayList<>(); + MultipartFile file = request.getFile(); + Class outputClass = request.getOutputClass(); + while (actualTry < request.getRetries()) { + try { + T emptyDto = outputClass.getDeclaredConstructor().newInstance(); + String outputSchemaJson = objectMapper.writeValueAsString(emptyDto); + String prompt = promptBuilder.buildPrompt( + request.getPrompt(), + outputSchemaJson, + actualTry > 0 ? errors.get(errors.size() - 1) : null + ); + String responseJson = callVisionApi(prompt, file); + T result = objectMapper.readValue(responseJson, outputClass); + jsonSchemaValidator.validate(result, outputSchemaJson); + return new ProcessResult<>(result, errors, actualTry); + } catch (Exception e) { + errors.add(e.getMessage()); + actualTry++; + } + } + return new ProcessResult<>(null, errors, actualTry); + } + + private String callVisionApi(String promptText, MultipartFile image) throws Exception { + byte[] imageBytes = image.getBytes(); + String base64Image = Base64.getEncoder().encodeToString(imageBytes); + Map payload = new HashMap<>(); + payload.put("model", config.getModel().getValue()); + payload.put("messages", List.of( + Map.of( + "role", "user", + "content", List.of( + Map.of("type", "text", "text", promptText), + Map.of("type", "image_url", "image_url", Map.of( + "url", "data:image/jpeg;base64," + base64Image, + "detail", "high" + )) + ) + ) + )); + payload.put("max_tokens", config.getMaxTokens()); + payload.put("temperature", config.getTemperature()); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(config.getApiKey()); + HttpEntity> request = new HttpEntity<>(payload, headers); + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity response = restTemplate.postForEntity( + "https://api.openai.com/v1/chat/completions", + request, + String.class + ); + if (!response.getStatusCode().is2xxSuccessful()) { + throw new Exception("OpenAI API error: " + response.getStatusCode() + " - " + response.getBody()); + } + return objectMapper.readTree(response.getBody()) + .path("choices").get(0) + .path("message").path("content") + .asText(); + } +} diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java new file mode 100644 index 00000000..957868c8 --- /dev/null +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java @@ -0,0 +1,30 @@ +package com.ontimize.jee.webclient.openai.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.everit.json.schema.Schema; +import org.everit.json.schema.ValidationException; +import org.everit.json.schema.loader.SchemaLoader; +import org.json.JSONObject; +import org.springframework.stereotype.Component; + +@Component +public class JsonSchemaValidator { + + private final ObjectMapper mapper = new ObjectMapper(); + + public void validate(Object dataObject, String schemaJson) { + try { + JSONObject json = new JSONObject(mapper.writeValueAsString(dataObject)); + JSONObject rawSchema = new JSONObject(schemaJson); + + Schema schema = SchemaLoader.load(rawSchema); + schema.validate(json); + + } catch (ValidationException ve) { + String message = "Error de validación JSON: " + String.join("; ", ve.getAllMessages()); + throw new RuntimeException(message, ve); + } catch (Exception e) { + throw new RuntimeException("Error inesperado durante la validación JSON: " + e.getMessage(), e); + } + } +} diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/PromptBuilder.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/PromptBuilder.java new file mode 100644 index 00000000..14cd8237 --- /dev/null +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/PromptBuilder.java @@ -0,0 +1,23 @@ +package com.ontimize.jee.webclient.openai.util; + +import org.springframework.stereotype.Component; + +@Component +public class PromptBuilder { + + public String buildPrompt(String userPrompt, String jsonSchema, String error) { + if (error == null) { + return String.format( + "Analiza la imagen adjunta. %s\n\nDevuelve la información en el siguiente formato JSON:\n%s", + userPrompt, + jsonSchema + ); + } else { + return String.format( + "La respuesta anterior no cumple con el formato esperado. El error fue:\n%s\n\nVuelve a intentarlo. La estructura esperada es:\n%s", + error, + jsonSchema + ); + } + } +} From ccdefe60d73f3ef76c24f06d6d11d06161fae417 Mon Sep 17 00:00:00 2001 From: adrian-carneiro-imatia Date: Wed, 3 Sep 2025 13:03:54 +0200 Subject: [PATCH 02/65] Resolve comments --- ontimize-jee-webclient-addons/pom.xml | 1 - .../webclient/openai/naming/OpenAINaming.java | 26 +++++++ .../service/OpenAiImageProcessorService.java | 67 ++++++++++++------- .../webclient/openai/util/PromptBuilder.java | 19 ++---- pom.xml | 6 ++ 5 files changed, 79 insertions(+), 40 deletions(-) create mode 100644 ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/naming/OpenAINaming.java diff --git a/ontimize-jee-webclient-addons/pom.xml b/ontimize-jee-webclient-addons/pom.xml index 6fc86acc..7273dcf3 100644 --- a/ontimize-jee-webclient-addons/pom.xml +++ b/ontimize-jee-webclient-addons/pom.xml @@ -168,7 +168,6 @@ com.github.erosb everit-json-schema - 1.14.6 diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/naming/OpenAINaming.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/naming/OpenAINaming.java new file mode 100644 index 00000000..41d5dc39 --- /dev/null +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/naming/OpenAINaming.java @@ -0,0 +1,26 @@ +package com.ontimize.jee.webclient.openai.naming; + +public class OpenAINaming { + public static final String MODEL = "model"; + public static final String MESSAGES = "messages"; + public static final String ROLE = "role"; + public static final String USER = "user"; + public static final String CONTENT = "content"; + public static final String TYPE = "type"; + public static final String TEXT = "text"; + public static final String IMAGE_URL = "image_url"; + public static final String IMAGE_TYPE = "data:image/jpeg;base64,"; + public static final String URL = "url"; + public static final String DETAIL = "detail"; + public static final String HIGH = "high"; + public static final String MAX_TOKENS = "max_tokens"; + public static final String TEMPERATURE = "temperature"; + public static final String COMPLETIONS_URL = "https://api.openai.com/v1/chat/completions"; + public static final String CHOICES = "choices"; + public static final String MESSAGE = "message"; + public static final String INITIAL_PROMPT_FORMAT = + "Analiza la imagen adjunta. %s\n\nDevuelve la información en el siguiente formato JSON:\n%s"; + public static final String RETRY_PROMPT_FORMAT = + "La respuesta anterior no cumple con el formato esperado. El error fue:\n%s\n\nVuelve a intentarlo. La estructura esperada es:\n%s"; + public static final String OPENAI_API_ERROR = "OpenAI API error: "; +} diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java index c9cccf2e..18b8b3c6 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java @@ -13,8 +13,11 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.util.*; +import static com.ontimize.jee.webclient.openai.naming.OpenAINaming.*; + public class OpenAiImageProcessorService { private final OpenAiClientConfig config; private final PromptBuilder promptBuilder; @@ -56,40 +59,52 @@ public ProcessResult processImage(ProcessRequest request) { } private String callVisionApi(String promptText, MultipartFile image) throws Exception { - byte[] imageBytes = image.getBytes(); - String base64Image = Base64.getEncoder().encodeToString(imageBytes); - Map payload = new HashMap<>(); - payload.put("model", config.getModel().getValue()); - payload.put("messages", List.of( - Map.of( - "role", "user", - "content", List.of( - Map.of("type", "text", "text", promptText), - Map.of("type", "image_url", "image_url", Map.of( - "url", "data:image/jpeg;base64," + base64Image, - "detail", "high" - )) - ) - ) - )); - payload.put("max_tokens", config.getMaxTokens()); - payload.put("temperature", config.getTemperature()); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - headers.setBearerAuth(config.getApiKey()); - HttpEntity> request = new HttpEntity<>(payload, headers); + HttpEntity> request = prepareRequest(promptText, image); RestTemplate restTemplate = new RestTemplate(); + ResponseEntity response = restTemplate.postForEntity( - "https://api.openai.com/v1/chat/completions", + COMPLETIONS_URL, request, String.class ); + if (!response.getStatusCode().is2xxSuccessful()) { - throw new Exception("OpenAI API error: " + response.getStatusCode() + " - " + response.getBody()); + throw new Exception(OPENAI_API_ERROR + response.getStatusCode() + " - " + response.getBody()); } + return objectMapper.readTree(response.getBody()) - .path("choices").get(0) - .path("message").path("content") + .path(CHOICES).get(0) + .path(MESSAGE).path(CONTENT) .asText(); } + + private HttpEntity> prepareRequest(String promptText, MultipartFile image) throws IOException { + byte[] imageBytes = image.getBytes(); + String base64Image = Base64.getEncoder().encodeToString(imageBytes); + + Map imageUrlContent = Map.of( + URL, IMAGE_TYPE + base64Image, + DETAIL, HIGH + ); + + Map contentItem1 = Map.of(TYPE, TEXT, TEXT, promptText); + Map contentItem2 = Map.of(TYPE, IMAGE_URL, IMAGE_URL, imageUrlContent); + + Map message = Map.of( + ROLE, USER, + CONTENT, List.of(contentItem1, contentItem2) + ); + + Map payload = new HashMap<>(); + payload.put(MODEL, config.getModel().getValue()); + payload.put(MESSAGES, List.of(message)); + payload.put(MAX_TOKENS, config.getMaxTokens()); + payload.put(TEMPERATURE, config.getTemperature()); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(config.getApiKey()); + + return new HttpEntity<>(payload, headers); + } } diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/PromptBuilder.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/PromptBuilder.java index 14cd8237..5244766d 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/PromptBuilder.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/PromptBuilder.java @@ -2,22 +2,15 @@ import org.springframework.stereotype.Component; +import static com.ontimize.jee.webclient.openai.naming.OpenAINaming.INITIAL_PROMPT_FORMAT; +import static com.ontimize.jee.webclient.openai.naming.OpenAINaming.RETRY_PROMPT_FORMAT; + @Component public class PromptBuilder { public String buildPrompt(String userPrompt, String jsonSchema, String error) { - if (error == null) { - return String.format( - "Analiza la imagen adjunta. %s\n\nDevuelve la información en el siguiente formato JSON:\n%s", - userPrompt, - jsonSchema - ); - } else { - return String.format( - "La respuesta anterior no cumple con el formato esperado. El error fue:\n%s\n\nVuelve a intentarlo. La estructura esperada es:\n%s", - error, - jsonSchema - ); - } + return error == null + ? String.format(INITIAL_PROMPT_FORMAT, userPrompt, jsonSchema) + : String.format(RETRY_PROMPT_FORMAT, error, jsonSchema); } } diff --git a/pom.xml b/pom.xml index 20941cb5..dddfe6e2 100644 --- a/pom.xml +++ b/pom.xml @@ -203,6 +203,7 @@ 5.3.18 2.4.0 1.3.2 + 1.14.6 ontimize @@ -603,6 +604,11 @@ hsqldb ${hsqldb.version} + + com.github.erosb + everit-json-schema + ${everit-json-schema.version} + From c84b995aa0c106d75316b31f53b943e3e92c0301 Mon Sep 17 00:00:00 2001 From: adrian-carneiro-imatia Date: Wed, 3 Sep 2025 13:31:46 +0200 Subject: [PATCH 03/65] Remove enum --- .../openai/model/OpenAiClientConfig.java | 8 ++-- .../webclient/openai/model/OpenAiModel.java | 47 ------------------- .../service/OpenAiImageProcessorService.java | 2 +- 3 files changed, 5 insertions(+), 52 deletions(-) delete mode 100644 ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/OpenAiModel.java diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/OpenAiClientConfig.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/OpenAiClientConfig.java index 207f55f8..10b6d71b 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/OpenAiClientConfig.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/OpenAiClientConfig.java @@ -2,11 +2,11 @@ public class OpenAiClientConfig { private String apiKey; - private OpenAiModel model; + private String model; private int maxTokens; private double temperature; - public OpenAiClientConfig(String apiKey, OpenAiModel model, int maxTokens, double temperature) { + public OpenAiClientConfig(String apiKey, String model, int maxTokens, double temperature) { this.apiKey = apiKey; this.model = model; this.maxTokens = maxTokens; @@ -21,11 +21,11 @@ public void setApiKey(String apiKey) { this.apiKey = apiKey; } - public OpenAiModel getModel() { + public String getModel() { return model; } - public void setModel(OpenAiModel model) { + public void setModel(String model) { this.model = model; } diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/OpenAiModel.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/OpenAiModel.java deleted file mode 100644 index cdfbcbc7..00000000 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/OpenAiModel.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.ontimize.jee.webclient.openai.model; - -public enum OpenAiModel { - GPT_3_5_TURBO("gpt-3.5-turbo"), - GPT_3_5_TURBO_16K_0613("gpt-3.5-turbo-16k-0613"), - GPT_4("gpt-4"), - GPT_4_TURBO("gpt-4-turbo"), - GPT_4_TURBO_PREVIEW("gpt-4-turbo-preview"), - GPT_4_1("gpt-4.1"), - GPT_4_1_MINI("gpt-4.1-mini"), - GPT_4_1_NANO("gpt-4.1-nano"), - GPT_4_5_PREVIEW("gpt-4.5-preview"), - GPT_4O("gpt-4o"), - GPT_4O_MINI("gpt-4o-mini"), - GPT_4O_SEARCH_PREVIEW("gpt-4o-search-preview"), - GPT_IMAGE_1("gpt-image-1"), - DALL_E_2("dall-e-2"), - DALL_E_3("dall-e-3"), - O1("o1"), - O1_MINI("o1-mini"), - O1_PREVIEW("o1-preview"), - O1_PRO("o1-pro"), - O3("o3"), - O3_MINI("o3-mini"), - O3_PRO("o3-pro"), - O3_DEEP_RESEARCH("o3-deep-research"), - O4_MINI("o4-mini"), - O4_MINI_DEEP_RESEARCH("o4-mini-deep-research"), - GPT_OSS_120B("gpt-oss-120b"), - GPT_OSS_20B("gpt-oss-20b"), - TEXT_EMBEDDING_ADA_002("text-embedding-ada-002"), - TEXT_EMBEDDING_3_SMALL("text-embedding-3-small"), - TEXT_EMBEDDING_3_LARGE("text-embedding-3-large"), - TEXT_MODERATION_STABLE("text-moderation-stable"), - TEXT_MODERATION_LATEST("text-moderation-latest"), - WHISPER_1("whisper-1"); - - private final String value; - - OpenAiModel(String value) { - this.value = value; - } - - public String getValue() { - return value; - } -} diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java index 18b8b3c6..9b3d82f0 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java @@ -96,7 +96,7 @@ private HttpEntity> prepareRequest(String promptText, Multip ); Map payload = new HashMap<>(); - payload.put(MODEL, config.getModel().getValue()); + payload.put(MODEL, config.getModel()); payload.put(MESSAGES, List.of(message)); payload.put(MAX_TOKENS, config.getMaxTokens()); payload.put(TEMPERATURE, config.getTemperature()); From 4769c47ca1c1915b53623990ca3c2b82d847adbe Mon Sep 17 00:00:00 2001 From: adrian-carneiro-imatia Date: Wed, 3 Sep 2025 15:44:36 +0200 Subject: [PATCH 04/65] Remove parameters from client --- .../openai/model/OpenAiClientConfig.java | 47 ------------------- 1 file changed, 47 deletions(-) delete mode 100644 ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/OpenAiClientConfig.java diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/OpenAiClientConfig.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/OpenAiClientConfig.java deleted file mode 100644 index 10b6d71b..00000000 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/OpenAiClientConfig.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.ontimize.jee.webclient.openai.model; - -public class OpenAiClientConfig { - private String apiKey; - private String model; - private int maxTokens; - private double temperature; - - public OpenAiClientConfig(String apiKey, String model, int maxTokens, double temperature) { - this.apiKey = apiKey; - this.model = model; - this.maxTokens = maxTokens; - this.temperature = temperature; - } - - public String getApiKey() { - return apiKey; - } - - public void setApiKey(String apiKey) { - this.apiKey = apiKey; - } - - public String getModel() { - return model; - } - - public void setModel(String model) { - this.model = model; - } - - public int getMaxTokens() { - return maxTokens; - } - - public void setMaxTokens(int maxTokens) { - this.maxTokens = maxTokens; - } - - public double getTemperature() { - return temperature; - } - - public void setTemperature(double temperature) { - this.temperature = temperature; - } -} \ No newline at end of file From ce275797561489bd8d27df37e0a3bc2224d3e88a Mon Sep 17 00:00:00 2001 From: adrian-carneiro-imatia Date: Wed, 3 Sep 2025 15:44:58 +0200 Subject: [PATCH 05/65] Missing from last commit --- .../webclient/openai/client/OpenAiClient.java | 10 +++---- .../openai/model/ProcessRequest.java | 27 +++++++++++++++++++ .../service/OpenAiImageProcessorService.java | 26 +++++++++--------- 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/client/OpenAiClient.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/client/OpenAiClient.java index 2507e2e3..dd5581df 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/client/OpenAiClient.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/client/OpenAiClient.java @@ -1,7 +1,6 @@ package com.ontimize.jee.webclient.openai.client; -import com.ontimize.jee.webclient.openai.model.OpenAiClientConfig; import com.ontimize.jee.webclient.openai.model.ProcessRequest; import com.ontimize.jee.webclient.openai.model.ProcessResult; import com.ontimize.jee.webclient.openai.service.OpenAiImageProcessorService; @@ -10,19 +9,18 @@ public class OpenAiClient { - private final OpenAiClientConfig config; + private final String apiKey; - public OpenAiClient(OpenAiClientConfig config) { - this.config = config; + public OpenAiClient(String apiKey) { + this.apiKey = apiKey; } public ProcessResult processImage(ProcessRequest request) { OpenAiImageProcessorService service = new OpenAiImageProcessorService<>( - config, + this.apiKey, new PromptBuilder(), new JsonSchemaValidator() ); - return service.processImage(request); } } \ No newline at end of file diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/ProcessRequest.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/ProcessRequest.java index e0ab4db2..a7eef505 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/ProcessRequest.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/model/ProcessRequest.java @@ -8,6 +8,9 @@ public class ProcessRequest { private String prompt; private int retries; private Class outputClass; + private String model; + private int maxTokens; + private double temperature; public MultipartFile getFile() { return file; @@ -40,4 +43,28 @@ public int getRetries() { public void setRetries(int retries) { this.retries = retries; } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public int getMaxTokens() { + return maxTokens; + } + + public void setMaxTokens(int maxTokens) { + this.maxTokens = maxTokens; + } + + public double getTemperature() { + return temperature; + } + + public void setTemperature(double temperature) { + this.temperature = temperature; + } } \ No newline at end of file diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java index 9b3d82f0..2608f956 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java @@ -1,7 +1,6 @@ package com.ontimize.jee.webclient.openai.service; import com.fasterxml.jackson.databind.ObjectMapper; -import com.ontimize.jee.webclient.openai.model.OpenAiClientConfig; import com.ontimize.jee.webclient.openai.model.ProcessRequest; import com.ontimize.jee.webclient.openai.model.ProcessResult; import com.ontimize.jee.webclient.openai.util.JsonSchemaValidator; @@ -19,17 +18,17 @@ import static com.ontimize.jee.webclient.openai.naming.OpenAINaming.*; public class OpenAiImageProcessorService { - private final OpenAiClientConfig config; private final PromptBuilder promptBuilder; private final JsonSchemaValidator jsonSchemaValidator; private final ObjectMapper objectMapper = new ObjectMapper(); + private final String apiKey; - public OpenAiImageProcessorService(OpenAiClientConfig config, + public OpenAiImageProcessorService(String apiKey, PromptBuilder promptBuilder, JsonSchemaValidator jsonSchemaValidator) { - this.config = config; this.promptBuilder = promptBuilder; this.jsonSchemaValidator = jsonSchemaValidator; + this.apiKey = apiKey; } public ProcessResult processImage(ProcessRequest request) { @@ -46,7 +45,8 @@ public ProcessResult processImage(ProcessRequest request) { outputSchemaJson, actualTry > 0 ? errors.get(errors.size() - 1) : null ); - String responseJson = callVisionApi(prompt, file); + String responseJson = callVisionApi(prompt, file, request.getModel(), request.getMaxTokens(), + request.getTemperature()); T result = objectMapper.readValue(responseJson, outputClass); jsonSchemaValidator.validate(result, outputSchemaJson); return new ProcessResult<>(result, errors, actualTry); @@ -58,8 +58,9 @@ public ProcessResult processImage(ProcessRequest request) { return new ProcessResult<>(null, errors, actualTry); } - private String callVisionApi(String promptText, MultipartFile image) throws Exception { - HttpEntity> request = prepareRequest(promptText, image); + private String callVisionApi(String promptText, MultipartFile image, String model, int maxTokens, + double temperature) throws Exception { + HttpEntity> request = prepareRequest(promptText, image, model, maxTokens, temperature); RestTemplate restTemplate = new RestTemplate(); ResponseEntity response = restTemplate.postForEntity( @@ -78,7 +79,8 @@ private String callVisionApi(String promptText, MultipartFile image) throws Exce .asText(); } - private HttpEntity> prepareRequest(String promptText, MultipartFile image) throws IOException { + private HttpEntity> prepareRequest(String promptText, MultipartFile image, String model, + int maxTokens, double temperature) throws IOException { byte[] imageBytes = image.getBytes(); String base64Image = Base64.getEncoder().encodeToString(imageBytes); @@ -96,14 +98,14 @@ private HttpEntity> prepareRequest(String promptText, Multip ); Map payload = new HashMap<>(); - payload.put(MODEL, config.getModel()); + payload.put(MODEL, model); payload.put(MESSAGES, List.of(message)); - payload.put(MAX_TOKENS, config.getMaxTokens()); - payload.put(TEMPERATURE, config.getTemperature()); + payload.put(MAX_TOKENS, maxTokens); + payload.put(TEMPERATURE, temperature); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); - headers.setBearerAuth(config.getApiKey()); + headers.setBearerAuth(this.apiKey); return new HttpEntity<>(payload, headers); } From 8a44eb5a720c96325b8b375287da285f2d805934 Mon Sep 17 00:00:00 2001 From: supportontimize Date: Thu, 11 Sep 2025 12:13:47 +0000 Subject: [PATCH 06/65] =?UTF-8?q?New=20develop=20version=20=E2=86=92=205.1?= =?UTF-8?q?4.0-SNAPSHOT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ontimize-jee-common/pom.xml | 2 +- ontimize-jee-server-jdbc/pom.xml | 2 +- ontimize-jee-server-keycloak/pom.xml | 2 +- ontimize-jee-server-rest/pom.xml | 2 +- ontimize-jee-server/pom.xml | 2 +- ontimize-jee-webclient-addons/pom.xml | 2 +- pom.xml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ontimize-jee-common/pom.xml b/ontimize-jee-common/pom.xml index 816e6d8d..fb8240b8 100644 --- a/ontimize-jee-common/pom.xml +++ b/ontimize-jee-common/pom.xml @@ -4,7 +4,7 @@ com.ontimize.jee ontimize-jee - 5.13.0 + 5.14.0-SNAPSHOT ontimize-jee-common diff --git a/ontimize-jee-server-jdbc/pom.xml b/ontimize-jee-server-jdbc/pom.xml index 4d42295f..51e2a7f5 100644 --- a/ontimize-jee-server-jdbc/pom.xml +++ b/ontimize-jee-server-jdbc/pom.xml @@ -4,7 +4,7 @@ com.ontimize.jee ontimize-jee - 5.13.0 + 5.14.0-SNAPSHOT ontimize-jee-server-jdbc diff --git a/ontimize-jee-server-keycloak/pom.xml b/ontimize-jee-server-keycloak/pom.xml index ebb03e7f..2d718919 100644 --- a/ontimize-jee-server-keycloak/pom.xml +++ b/ontimize-jee-server-keycloak/pom.xml @@ -4,7 +4,7 @@ com.ontimize.jee ontimize-jee - 5.13.0 + 5.14.0-SNAPSHOT ontimize-jee-server-keycloak diff --git a/ontimize-jee-server-rest/pom.xml b/ontimize-jee-server-rest/pom.xml index 5c3444f1..a78bcd61 100644 --- a/ontimize-jee-server-rest/pom.xml +++ b/ontimize-jee-server-rest/pom.xml @@ -4,7 +4,7 @@ com.ontimize.jee ontimize-jee - 5.13.0 + 5.14.0-SNAPSHOT ontimize-jee-server-rest diff --git a/ontimize-jee-server/pom.xml b/ontimize-jee-server/pom.xml index 11cac425..50c314d4 100644 --- a/ontimize-jee-server/pom.xml +++ b/ontimize-jee-server/pom.xml @@ -4,7 +4,7 @@ com.ontimize.jee ontimize-jee - 5.13.0 + 5.14.0-SNAPSHOT ontimize-jee-server diff --git a/ontimize-jee-webclient-addons/pom.xml b/ontimize-jee-webclient-addons/pom.xml index 88e379fb..c30539c6 100644 --- a/ontimize-jee-webclient-addons/pom.xml +++ b/ontimize-jee-webclient-addons/pom.xml @@ -4,7 +4,7 @@ com.ontimize.jee ontimize-jee - 5.13.0 + 5.14.0-SNAPSHOT ontimize-jee-webclient-addons diff --git a/pom.xml b/pom.xml index 9ce99933..4c509296 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.ontimize.jee ontimize-jee - 5.13.0 + 5.14.0-SNAPSHOT pom Ontimize EE From a93ae7d2cde4b6d29f40a53ddff88322b6a14819 Mon Sep 17 00:00:00 2001 From: adrian-carneiro-imatia Date: Mon, 15 Sep 2025 14:53:15 +0200 Subject: [PATCH 07/65] Multiple changes --- .../{OpenAiClient.java => OpenAIClient.java} | 7 +- .../webclient/openai/naming/OpenAINaming.java | 11 ++- .../service/OpenAiImageProcessorService.java | 83 +++++++++---------- .../openai/util/JsonSchemaValidator.java | 50 +++++++++-- .../util/{PromptBuilder.java => Utils.java} | 4 +- 5 files changed, 96 insertions(+), 59 deletions(-) rename ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/client/{OpenAiClient.java => OpenAIClient.java} (76%) rename ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/{PromptBuilder.java => Utils.java} (80%) diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/client/OpenAiClient.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/client/OpenAIClient.java similarity index 76% rename from ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/client/OpenAiClient.java rename to ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/client/OpenAIClient.java index dd5581df..05ec2d4f 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/client/OpenAiClient.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/client/OpenAIClient.java @@ -5,7 +5,6 @@ import com.ontimize.jee.webclient.openai.model.ProcessResult; import com.ontimize.jee.webclient.openai.service.OpenAiImageProcessorService; import com.ontimize.jee.webclient.openai.util.JsonSchemaValidator; -import com.ontimize.jee.webclient.openai.util.PromptBuilder; public class OpenAiClient { @@ -16,11 +15,7 @@ public OpenAiClient(String apiKey) { } public ProcessResult processImage(ProcessRequest request) { - OpenAiImageProcessorService service = new OpenAiImageProcessorService<>( - this.apiKey, - new PromptBuilder(), - new JsonSchemaValidator() - ); + OpenAiImageProcessorService service = new OpenAiImageProcessorService<>(this.apiKey, new JsonSchemaValidator()); return service.processImage(request); } } \ No newline at end of file diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/naming/OpenAINaming.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/naming/OpenAINaming.java index 41d5dc39..d088bc32 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/naming/OpenAINaming.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/naming/OpenAINaming.java @@ -18,9 +18,12 @@ public class OpenAINaming { public static final String COMPLETIONS_URL = "https://api.openai.com/v1/chat/completions"; public static final String CHOICES = "choices"; public static final String MESSAGE = "message"; - public static final String INITIAL_PROMPT_FORMAT = - "Analiza la imagen adjunta. %s\n\nDevuelve la información en el siguiente formato JSON:\n%s"; - public static final String RETRY_PROMPT_FORMAT = - "La respuesta anterior no cumple con el formato esperado. El error fue:\n%s\n\nVuelve a intentarlo. La estructura esperada es:\n%s"; + public static final String INITIAL_PROMPT_FORMAT = "Analiza la imagen adjunta. %s\n\nDevuelve la información en el siguiente formato JSON:\n%s"; + public static final String RETRY_PROMPT_FORMAT = "La respuesta anterior no cumple con el formato esperado. El error fue:\n%s\n\nVuelve a intentarlo. La estructura esperada es:\n%s"; public static final String OPENAI_API_ERROR = "OpenAI API error: "; + public static final String OPENAI_API_NO_JSON_ERROR = "No se encontró JSON en la cadena de entrada"; + public static final String OPENAI_API_SCHEMA_GENERATION_ERROR = "Error generando schema: "; + public static final String OPENAI_API_SCHEMA_SERIALIZATION_ERROR = "Error serializando schema: "; + + public static final String PROPERTIES = "properties"; } diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java index 2608f956..caf6f500 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java @@ -1,10 +1,10 @@ package com.ontimize.jee.webclient.openai.service; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.ontimize.jee.webclient.openai.model.ProcessRequest; import com.ontimize.jee.webclient.openai.model.ProcessResult; import com.ontimize.jee.webclient.openai.util.JsonSchemaValidator; -import com.ontimize.jee.webclient.openai.util.PromptBuilder; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -18,15 +18,11 @@ import static com.ontimize.jee.webclient.openai.naming.OpenAINaming.*; public class OpenAiImageProcessorService { - private final PromptBuilder promptBuilder; private final JsonSchemaValidator jsonSchemaValidator; private final ObjectMapper objectMapper = new ObjectMapper(); private final String apiKey; - public OpenAiImageProcessorService(String apiKey, - PromptBuilder promptBuilder, - JsonSchemaValidator jsonSchemaValidator) { - this.promptBuilder = promptBuilder; + public OpenAiImageProcessorService(String apiKey, JsonSchemaValidator jsonSchemaValidator) { this.jsonSchemaValidator = jsonSchemaValidator; this.apiKey = apiKey; } @@ -36,20 +32,43 @@ public ProcessResult processImage(ProcessRequest request) { List errors = new ArrayList<>(); MultipartFile file = request.getFile(); Class outputClass = request.getOutputClass(); + + SchemaGeneratorConfigBuilder cfgBuilder = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2019_09, OptionPreset.PLAIN_JSON); + cfgBuilder.forTypesInGeneral().withAdditionalPropertiesResolver(scope -> null); + + SchemaGenerator generator = new SchemaGenerator(cfgBuilder.build()); + JsonNode jsonSchema; + try { + jsonSchema = generator.generateSchema(outputClass); + } catch (Exception e) { + errors.add(OPENAI_API_SCHEMA_GENERATION_ERROR + e.getMessage()); + return new ProcessResult<>(null, errors, actualTry); + } + + String schemaStr; + try { + schemaStr = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonSchema); + } catch (Exception e) { + errors.add(OPENAI_API_SCHEMA_SERIALIZATION_ERROR + e.getMessage()); + return new ProcessResult<>(null, errors, actualTry); + } + while (actualTry < request.getRetries()) { try { - T emptyDto = outputClass.getDeclaredConstructor().newInstance(); - String outputSchemaJson = objectMapper.writeValueAsString(emptyDto); - String prompt = promptBuilder.buildPrompt( - request.getPrompt(), - outputSchemaJson, - actualTry > 0 ? errors.get(errors.size() - 1) : null - ); - String responseJson = callVisionApi(prompt, file, request.getModel(), request.getMaxTokens(), - request.getTemperature()); + String prompt = Utils.buildPrompt(request.getPrompt(), schemaStr, actualTry > 0 ? errors.get(errors.size() - 1) : null); + + String responseJsonRaw = callVisionApi(prompt, file, request.getModel(), request.getMaxTokens(), request.getTemperature()); + + String responseJson = JsonSchemaValidator.extractRawJson(responseJsonRaw); + if (responseJson == null || responseJson.isBlank()) { + throw new IllegalStateException(OPENAI_API_NO_JSON_ERROR); + } + + jsonSchemaValidator.validate(responseJson, schemaStr); + T result = objectMapper.readValue(responseJson, outputClass); - jsonSchemaValidator.validate(result, outputSchemaJson); return new ProcessResult<>(result, errors, actualTry); + } catch (Exception e) { errors.add(e.getMessage()); actualTry++; @@ -58,44 +77,24 @@ public ProcessResult processImage(ProcessRequest request) { return new ProcessResult<>(null, errors, actualTry); } - private String callVisionApi(String promptText, MultipartFile image, String model, int maxTokens, - double temperature) throws Exception { + private String callVisionApi(String promptText, MultipartFile image, String model, int maxTokens, double temperature) throws Exception { HttpEntity> request = prepareRequest(promptText, image, model, maxTokens, temperature); RestTemplate restTemplate = new RestTemplate(); - - ResponseEntity response = restTemplate.postForEntity( - COMPLETIONS_URL, - request, - String.class - ); - + ResponseEntity response = restTemplate.postForEntity(COMPLETIONS_URL, request, String.class); if (!response.getStatusCode().is2xxSuccessful()) { throw new Exception(OPENAI_API_ERROR + response.getStatusCode() + " - " + response.getBody()); } - - return objectMapper.readTree(response.getBody()) - .path(CHOICES).get(0) - .path(MESSAGE).path(CONTENT) - .asText(); + return objectMapper.readTree(response.getBody()).path(CHOICES).get(0).path(MESSAGE).path(CONTENT).asText(); } - private HttpEntity> prepareRequest(String promptText, MultipartFile image, String model, - int maxTokens, double temperature) throws IOException { + private HttpEntity> prepareRequest(String promptText, MultipartFile image, String model, int maxTokens, double temperature) throws IOException { byte[] imageBytes = image.getBytes(); String base64Image = Base64.getEncoder().encodeToString(imageBytes); - Map imageUrlContent = Map.of( - URL, IMAGE_TYPE + base64Image, - DETAIL, HIGH - ); - + Map imageUrlContent = Map.of(URL, IMAGE_TYPE + base64Image, DETAIL, HIGH); Map contentItem1 = Map.of(TYPE, TEXT, TEXT, promptText); Map contentItem2 = Map.of(TYPE, IMAGE_URL, IMAGE_URL, imageUrlContent); - - Map message = Map.of( - ROLE, USER, - CONTENT, List.of(contentItem1, contentItem2) - ); + Map message = Map.of(ROLE, USER, CONTENT, List.of(contentItem1, contentItem2)); Map payload = new HashMap<>(); payload.put(MODEL, model); diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java index 957868c8..7efc4fa3 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java @@ -1,24 +1,64 @@ package com.ontimize.jee.webclient.openai.util; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.everit.json.schema.Schema; import org.everit.json.schema.ValidationException; import org.everit.json.schema.loader.SchemaLoader; +import org.json.JSONArray; import org.json.JSONObject; import org.springframework.stereotype.Component; @Component public class JsonSchemaValidator { - + private static final ObjectMapper LENIENT = new ObjectMapper().configure(JsonParser.Feature.ALLOW_COMMENTS, true).configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true).configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true).configure(JsonParser.Feature.ALLOW_NUMERIC_LEADING_ZEROS, true).configure(JsonParser.Feature.ALLOW_TRAILING_COMMA, true); private final ObjectMapper mapper = new ObjectMapper(); + public static String extractRawJson(String content) { + if (content == null) return null; + String s = content.replace("\uFEFF", "").replace('\u201C', '"').replace('\u201D', '"').replace('\u2018', '\'').replace('\u2019', '\'').trim(); + + if (s.startsWith("```")) { + int first = s.indexOf('\n'); + int last = s.lastIndexOf("```"); + if (first >= 0 && last > first) { + s = s.substring(first + 1, last).trim(); + } else { + s = s.replaceFirst("^```(?:json)?", "").replaceFirst("```$", "").trim(); + } + } + + int open = s.indexOf('{'); + int openArr = s.indexOf('['); + int start = (open == -1) ? openArr : (openArr == -1 ? open : Math.min(open, openArr)); + int end = Math.max(s.lastIndexOf('}'), s.lastIndexOf(']')); + if (start >= 0 && end > start) { + String candidate = s.substring(start, end + 1).trim(); + if (candidate.startsWith("{") || candidate.startsWith("[")) { + return candidate; + } + } + return null; + } + public void validate(Object dataObject, String schemaJson) { try { - JSONObject json = new JSONObject(mapper.writeValueAsString(dataObject)); - JSONObject rawSchema = new JSONObject(schemaJson); + String raw = (dataObject instanceof String) ? (String) dataObject : mapper.writeValueAsString(dataObject); - Schema schema = SchemaLoader.load(rawSchema); - schema.validate(json); + String extracted = extractRawJson(raw); + if (extracted == null) throw new IllegalArgumentException(OPENAI_API_NO_JSON_ERROR); + JsonNode node = LENIENT.readTree(extracted); + String normalized = mapper.writeValueAsString(node); + JSONObject schemaObj = new JSONObject(schemaJson); + JSONObject rawSchema = schemaObj.getJSONObject(PROPERTIES); + JSONObject jsonSchema = new JSONObject(mapper.writeValueAsString(rawSchema)); + Schema schema = SchemaLoader.load(jsonSchema); + if (normalized.trim().startsWith("{")) { + schema.validate(new JSONObject(normalized)); + } else { + schema.validate(new JSONArray(normalized)); + } } catch (ValidationException ve) { String message = "Error de validación JSON: " + String.join("; ", ve.getAllMessages()); diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/PromptBuilder.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/Utils.java similarity index 80% rename from ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/PromptBuilder.java rename to ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/Utils.java index 5244766d..32371f1c 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/PromptBuilder.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/Utils.java @@ -6,9 +6,9 @@ import static com.ontimize.jee.webclient.openai.naming.OpenAINaming.RETRY_PROMPT_FORMAT; @Component -public class PromptBuilder { +public class Utils { - public String buildPrompt(String userPrompt, String jsonSchema, String error) { + public static String buildPrompt(String userPrompt, String jsonSchema, String error) { return error == null ? String.format(INITIAL_PROMPT_FORMAT, userPrompt, jsonSchema) : String.format(RETRY_PROMPT_FORMAT, error, jsonSchema); From 69420ccf17356d97958319c03141971a019244ff Mon Sep 17 00:00:00 2001 From: adrian-carneiro-imatia Date: Mon, 15 Sep 2025 14:57:20 +0200 Subject: [PATCH 08/65] add missing dependency --- ontimize-jee-webclient-addons/pom.xml | 335 +++++++++++++------------- pom.xml | 26 +- 2 files changed, 185 insertions(+), 176 deletions(-) diff --git a/ontimize-jee-webclient-addons/pom.xml b/ontimize-jee-webclient-addons/pom.xml index 7273dcf3..4bd94d2d 100644 --- a/ontimize-jee-webclient-addons/pom.xml +++ b/ontimize-jee-webclient-addons/pom.xml @@ -1,175 +1,178 @@ - - 4.0.0 - - com.ontimize.jee - ontimize-jee - 5.13.0-SNAPSHOT - - ontimize-jee-webclient-addons + + 4.0.0 + + com.ontimize.jee + ontimize-jee + 5.13.0-SNAPSHOT + + ontimize-jee-webclient-addons - Ontimize EE (WebClient Addons module) - Ontimize EE (WebClient Addons module) - https://www.ontimize.com + Ontimize EE (WebClient Addons module) + Ontimize EE (WebClient Addons module) + https://www.ontimize.com - - Imatia Innovation - http://imatia.com - + + Imatia Innovation + http://imatia.com + - - - The Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - - + + + The Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + - - - Alberto Quintela Trabazos - alberto.quintela@imatia.com - Imatia Innovation - http://imatia.com - - - Ángel Vázquez Vázquez - angel.vazquez@imatia.com - Imatia Innovation - http://imatia.com - - - Daniel Graña Cousido - daniel.grana@imatia.com - Imatia Innovation - http://imatia.com - - - Enrique Álvarez Pereira - enrique.alvarez@imatia.com - Imatia Innovation - http://imatia.com - - - Faustino Lage Rego - faustino.lage@imatia.com - Imatia Innovation - http://imatia.com - - - Gonzalo Martínez Fernández - gonzalo.martinez@imatia.com - Imatia Innovation - http://imatia.com - - - Joaquín Romero Riveiro - joaquin.romero@imatia.com - Imatia Innovation - http://imatia.com - - - Jorge Diaz Seijo - jorge.diaz@imatia.com - Imatia Innovation - http://imatia.com - - - Pablo Martínez Kirsten - pablo.martinez@imatia.com - Imatia Innovation - http://imatia.com - - - Senén Diéguez López - senen.dieguez@imatia.com - Imatia Innovation - http://imatia.com - - - Tomás Fuentes Facal - tomas.fuentes@imatia.com - Imatia Innovation - http://imatia.com - - - Xoán Loureiro Santamaría - xoan.loureiro@imatia.com - Imatia Innovation - http://imatia.com - - + + + Alberto Quintela Trabazos + alberto.quintela@imatia.com + Imatia Innovation + http://imatia.com + + + Ángel Vázquez Vázquez + angel.vazquez@imatia.com + Imatia Innovation + http://imatia.com + + + Daniel Graña Cousido + daniel.grana@imatia.com + Imatia Innovation + http://imatia.com + + + Enrique Álvarez Pereira + enrique.alvarez@imatia.com + Imatia Innovation + http://imatia.com + + + Faustino Lage Rego + faustino.lage@imatia.com + Imatia Innovation + http://imatia.com + + + Gonzalo Martínez Fernández + gonzalo.martinez@imatia.com + Imatia Innovation + http://imatia.com + + + Joaquín Romero Riveiro + joaquin.romero@imatia.com + Imatia Innovation + http://imatia.com + + + Jorge Diaz Seijo + jorge.diaz@imatia.com + Imatia Innovation + http://imatia.com + + + Pablo Martínez Kirsten + pablo.martinez@imatia.com + Imatia Innovation + http://imatia.com + + + Senén Diéguez López + senen.dieguez@imatia.com + Imatia Innovation + http://imatia.com + + + Tomás Fuentes Facal + tomas.fuentes@imatia.com + Imatia Innovation + http://imatia.com + + + Xoán Loureiro Santamaría + xoan.loureiro@imatia.com + Imatia Innovation + http://imatia.com + + - - scm:git:git://github.com/ontimize/ontimize-jee.git - scm:git:ssh://github.com:ontimize/ontimize-jee.git - https://github.com/ontimize/ontimize-jee/tree/master - - - - - ${projectGroupId} - ontimize-jee-server-rest - - - - javax.servlet - javax.servlet-api - true - - - - org.apache.poi - poi - true - - - org.apache.poi - poi-ooxml - true - - - org.apache.poi - poi-ooxml-schemas - - - xmlbeans - org.apache.xmlbeans - - - true - - - - org.dom4j - dom4j - provided - - - com.itextpdf - kernel - - - com.itextpdf - layout - - - com.ontimize.jee - ontimize-jee-common - - - org.apache.commons - commons-collections4 - - - org.kie.modules - org-junit - pom - - - com.github.erosb - everit-json-schema - - + + scm:git:git://github.com/ontimize/ontimize-jee.git + scm:git:ssh://github.com:ontimize/ontimize-jee.git + https://github.com/ontimize/ontimize-jee/tree/master + + + + ${projectGroupId} + ontimize-jee-server-rest + + + + javax.servlet + javax.servlet-api + true + + + + org.apache.poi + poi + true + + + org.apache.poi + poi-ooxml + true + + + org.apache.poi + poi-ooxml-schemas + + + xmlbeans + org.apache.xmlbeans + + + true + + + org.dom4j + dom4j + provided + + + com.itextpdf + kernel + + + com.itextpdf + layout + + + com.ontimize.jee + ontimize-jee-common + + + org.apache.commons + commons-collections4 + + + org.kie.modules + org-junit + pom + + + com.github.erosb + everit-json-schema + + + com.github.victools + jsonschema-generator + + diff --git a/pom.xml b/pom.xml index 6f077a65..d18d01e6 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,5 @@ - 4.0.0 com.ontimize.jee @@ -119,7 +119,7 @@ ontimize-jee-server-jdbc ontimize-jee-server-keycloak ontimize-jee-webclient-addons - + @@ -204,6 +204,7 @@ 2.4.0 1.3.2 1.14.6 + 4.37.0 ontimize @@ -610,6 +611,11 @@ everit-json-schema ${everit-json-schema.version} + + com.github.victools + jsonschema-generator + ${victools-jsonschema-generator.version} + @@ -763,14 +769,14 @@ maven-javadoc-plugin - org.sonatype.central - central-publishing-maven-plugin - ${central-publishing-maven-plugin.version} - true - - central - true - + org.sonatype.central + central-publishing-maven-plugin + ${central-publishing-maven-plugin.version} + true + + central + true + org.apache.maven.plugins From 79347d362ebb2f3669aa4fa2ca6bfaab0dc723fc Mon Sep 17 00:00:00 2001 From: adrian-carneiro-imatia Date: Mon, 15 Sep 2025 14:58:22 +0200 Subject: [PATCH 09/65] fix typo --- .../ontimize/jee/webclient/openai/client/OpenAIClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/client/OpenAIClient.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/client/OpenAIClient.java index 05ec2d4f..5c84fbfd 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/client/OpenAIClient.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/client/OpenAIClient.java @@ -6,11 +6,11 @@ import com.ontimize.jee.webclient.openai.service.OpenAiImageProcessorService; import com.ontimize.jee.webclient.openai.util.JsonSchemaValidator; -public class OpenAiClient { +public class OpenAIClient { private final String apiKey; - public OpenAiClient(String apiKey) { + public OpenAIClient(String apiKey) { this.apiKey = apiKey; } From 175b9c35e11fd23f0a252dd08fdb6795ff36a140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Mon, 15 Sep 2025 15:13:48 +0200 Subject: [PATCH 10/65] new temporal version --- ontimize-jee-common/pom.xml | 2 +- ontimize-jee-server-jdbc/pom.xml | 2 +- ontimize-jee-server-keycloak/pom.xml | 2 +- ontimize-jee-server-rest/pom.xml | 2 +- ontimize-jee-server/pom.xml | 2 +- ontimize-jee-webclient-addons/pom.xml | 2 +- pom.xml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ontimize-jee-common/pom.xml b/ontimize-jee-common/pom.xml index fb8240b8..705e9416 100644 --- a/ontimize-jee-common/pom.xml +++ b/ontimize-jee-common/pom.xml @@ -4,7 +4,7 @@ com.ontimize.jee ontimize-jee - 5.14.0-SNAPSHOT + 5.15.0-SNAPSHOT ontimize-jee-common diff --git a/ontimize-jee-server-jdbc/pom.xml b/ontimize-jee-server-jdbc/pom.xml index 51e2a7f5..3306b6d2 100644 --- a/ontimize-jee-server-jdbc/pom.xml +++ b/ontimize-jee-server-jdbc/pom.xml @@ -4,7 +4,7 @@ com.ontimize.jee ontimize-jee - 5.14.0-SNAPSHOT + 5.15.0-SNAPSHOT ontimize-jee-server-jdbc diff --git a/ontimize-jee-server-keycloak/pom.xml b/ontimize-jee-server-keycloak/pom.xml index 2d718919..47c0c5f3 100644 --- a/ontimize-jee-server-keycloak/pom.xml +++ b/ontimize-jee-server-keycloak/pom.xml @@ -4,7 +4,7 @@ com.ontimize.jee ontimize-jee - 5.14.0-SNAPSHOT + 5.15.0-SNAPSHOT ontimize-jee-server-keycloak diff --git a/ontimize-jee-server-rest/pom.xml b/ontimize-jee-server-rest/pom.xml index a78bcd61..63f7cb9c 100644 --- a/ontimize-jee-server-rest/pom.xml +++ b/ontimize-jee-server-rest/pom.xml @@ -4,7 +4,7 @@ com.ontimize.jee ontimize-jee - 5.14.0-SNAPSHOT + 5.15.0-SNAPSHOT ontimize-jee-server-rest diff --git a/ontimize-jee-server/pom.xml b/ontimize-jee-server/pom.xml index 50c314d4..bc5ae8a8 100644 --- a/ontimize-jee-server/pom.xml +++ b/ontimize-jee-server/pom.xml @@ -4,7 +4,7 @@ com.ontimize.jee ontimize-jee - 5.14.0-SNAPSHOT + 5.15.0-SNAPSHOT ontimize-jee-server diff --git a/ontimize-jee-webclient-addons/pom.xml b/ontimize-jee-webclient-addons/pom.xml index c30539c6..147f143c 100644 --- a/ontimize-jee-webclient-addons/pom.xml +++ b/ontimize-jee-webclient-addons/pom.xml @@ -4,7 +4,7 @@ com.ontimize.jee ontimize-jee - 5.14.0-SNAPSHOT + 5.15.0-SNAPSHOT ontimize-jee-webclient-addons diff --git a/pom.xml b/pom.xml index f3a0588e..f13bdb42 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.ontimize.jee ontimize-jee - 5.14.0-SNAPSHOT + 5.15.0-SNAPSHOT pom Ontimize EE From 6dd8beaaf62970c824576a4f7bcae573276defc3 Mon Sep 17 00:00:00 2001 From: adrian-carneiro-imatia Date: Mon, 15 Sep 2025 16:12:23 +0200 Subject: [PATCH 11/65] missing imports --- ontimize-jee-webclient-addons/pom.xml | 329 +++++++++--------- .../service/OpenAiImageProcessorService.java | 5 + .../openai/util/JsonSchemaValidator.java | 3 + 3 files changed, 177 insertions(+), 160 deletions(-) diff --git a/ontimize-jee-webclient-addons/pom.xml b/ontimize-jee-webclient-addons/pom.xml index 147f143c..f3b19433 100644 --- a/ontimize-jee-webclient-addons/pom.xml +++ b/ontimize-jee-webclient-addons/pom.xml @@ -1,171 +1,180 @@ - - 4.0.0 - - com.ontimize.jee - ontimize-jee - 5.15.0-SNAPSHOT - - ontimize-jee-webclient-addons + + 4.0.0 + + com.ontimize.jee + ontimize-jee + 5.15.0-SNAPSHOT + + ontimize-jee-webclient-addons - Ontimize EE (WebClient Addons module) - Ontimize EE (WebClient Addons module) - https://www.ontimize.com + Ontimize EE (WebClient Addons module) + Ontimize EE (WebClient Addons module) + https://www.ontimize.com - - Imatia Innovation - http://imatia.com - + + Imatia Innovation + http://imatia.com + - - - The Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - - + + + The Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + - - - Alberto Quintela Trabazos - alberto.quintela@imatia.com - Imatia Innovation - http://imatia.com - - - Ángel Vázquez Vázquez - angel.vazquez@imatia.com - Imatia Innovation - http://imatia.com - - - Daniel Graña Cousido - daniel.grana@imatia.com - Imatia Innovation - http://imatia.com - - - Enrique Álvarez Pereira - enrique.alvarez@imatia.com - Imatia Innovation - http://imatia.com - - - Faustino Lage Rego - faustino.lage@imatia.com - Imatia Innovation - http://imatia.com - - - Gonzalo Martínez Fernández - gonzalo.martinez@imatia.com - Imatia Innovation - http://imatia.com - - - Joaquín Romero Riveiro - joaquin.romero@imatia.com - Imatia Innovation - http://imatia.com - - - Jorge Diaz Seijo - jorge.diaz@imatia.com - Imatia Innovation - http://imatia.com - - - Pablo Martínez Kirsten - pablo.martinez@imatia.com - Imatia Innovation - http://imatia.com - - - Senén Diéguez López - senen.dieguez@imatia.com - Imatia Innovation - http://imatia.com - - - Tomás Fuentes Facal - tomas.fuentes@imatia.com - Imatia Innovation - http://imatia.com - - - Xoán Loureiro Santamaría - xoan.loureiro@imatia.com - Imatia Innovation - http://imatia.com - - + + + Alberto Quintela Trabazos + alberto.quintela@imatia.com + Imatia Innovation + http://imatia.com + + + Ángel Vázquez Vázquez + angel.vazquez@imatia.com + Imatia Innovation + http://imatia.com + + + Daniel Graña Cousido + daniel.grana@imatia.com + Imatia Innovation + http://imatia.com + + + Enrique Álvarez Pereira + enrique.alvarez@imatia.com + Imatia Innovation + http://imatia.com + + + Faustino Lage Rego + faustino.lage@imatia.com + Imatia Innovation + http://imatia.com + + + Gonzalo Martínez Fernández + gonzalo.martinez@imatia.com + Imatia Innovation + http://imatia.com + + + Joaquín Romero Riveiro + joaquin.romero@imatia.com + Imatia Innovation + http://imatia.com + + + Jorge Diaz Seijo + jorge.diaz@imatia.com + Imatia Innovation + http://imatia.com + + + Pablo Martínez Kirsten + pablo.martinez@imatia.com + Imatia Innovation + http://imatia.com + + + Senén Diéguez López + senen.dieguez@imatia.com + Imatia Innovation + http://imatia.com + + + Tomás Fuentes Facal + tomas.fuentes@imatia.com + Imatia Innovation + http://imatia.com + + + Xoán Loureiro Santamaría + xoan.loureiro@imatia.com + Imatia Innovation + http://imatia.com + + - - scm:git:git://github.com/ontimize/ontimize-jee.git - scm:git:ssh://github.com:ontimize/ontimize-jee.git - https://github.com/ontimize/ontimize-jee/tree/master - + + scm:git:git://github.com/ontimize/ontimize-jee.git + scm:git:ssh://github.com:ontimize/ontimize-jee.git + https://github.com/ontimize/ontimize-jee/tree/master + - - - ${projectGroupId} - ontimize-jee-server-rest - - - - javax.servlet - javax.servlet-api - true - - - - org.apache.poi - poi - true - - - org.apache.poi - poi-ooxml - true - - - org.apache.poi - poi-ooxml-schemas - - - xmlbeans - org.apache.xmlbeans - - - true - + + + ${projectGroupId} + ontimize-jee-server-rest + + + + javax.servlet + javax.servlet-api + true + + + + org.apache.poi + poi + true + + + org.apache.poi + poi-ooxml + true + + + org.apache.poi + poi-ooxml-schemas + + + xmlbeans + org.apache.xmlbeans + + + true + - - org.dom4j - dom4j - provided - - - com.itextpdf - kernel - - - com.itextpdf - layout - - - com.ontimize.jee - ontimize-jee-common - - - org.apache.commons - commons-collections4 - - - org.kie.modules - org-junit - pom - - + + org.dom4j + dom4j + provided + + + com.itextpdf + kernel + + + com.itextpdf + layout + + + com.ontimize.jee + ontimize-jee-common + + + org.apache.commons + commons-collections4 + + + org.kie.modules + org-junit + pom + + + com.github.erosb + everit-json-schema + + + com.github.victools + jsonschema-generator + + diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java index caf6f500..9e9777c1 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java @@ -2,9 +2,14 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.victools.jsonschema.generator.OptionPreset; +import com.github.victools.jsonschema.generator.SchemaGenerator; +import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder; +import com.github.victools.jsonschema.generator.SchemaVersion; import com.ontimize.jee.webclient.openai.model.ProcessRequest; import com.ontimize.jee.webclient.openai.model.ProcessResult; import com.ontimize.jee.webclient.openai.util.JsonSchemaValidator; +import com.ontimize.jee.webclient.openai.util.Utils; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java index 7efc4fa3..dce6114b 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java @@ -10,6 +10,9 @@ import org.json.JSONObject; import org.springframework.stereotype.Component; +import static com.ontimize.jee.webclient.openai.naming.OpenAINaming.OPENAI_API_NO_JSON_ERROR; +import static com.ontimize.jee.webclient.openai.naming.OpenAINaming.PROPERTIES; + @Component public class JsonSchemaValidator { private static final ObjectMapper LENIENT = new ObjectMapper().configure(JsonParser.Feature.ALLOW_COMMENTS, true).configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true).configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true).configure(JsonParser.Feature.ALLOW_NUMERIC_LEADING_ZEROS, true).configure(JsonParser.Feature.ALLOW_TRAILING_COMMA, true); From 969a4ba9720558185123e9c78e93a30b68ba0f3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Tue, 16 Sep 2025 09:09:58 +0200 Subject: [PATCH 12/65] SonarQube --- .github/workflows/sonar-build.yml | 33 +++++++++++-------------------- pom.xml | 1 - 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/.github/workflows/sonar-build.yml b/.github/workflows/sonar-build.yml index 8b7d729e..d9b7760c 100644 --- a/.github/workflows/sonar-build.yml +++ b/.github/workflows/sonar-build.yml @@ -1,25 +1,25 @@ -name: Analyze project with Sonar Cloud +name: SonarQube on: push: branches: - - master + - main - develop pull_request: types: [opened, synchronize, reopened] jobs: - Analyze-project-with-Sonar-Cloud: - name: Analyze project with Sonar Cloud + build: + name: Build and analyze runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v4 with: - distribution: 'temurin' - java-version: 11 - - name: Cache SonarCloud packages + java-version: 17 + distribution: 'temurin' # Alternative distribution options are available. + - name: Cache SonarQube packages uses: actions/cache@v4 with: path: ~/.sonar/cache @@ -31,18 +31,7 @@ jobs: path: ~/.m2 key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-m2 - - name: Build - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: mvn -B -Pjacoco verify - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: 17 - - name: Sonar + - name: Build and analyze env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: mvn -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=ontimize_ontimize-jee -Dsonar.coverage.jacoco.xmlReportPaths="$GITHUB_WORKSPACE/ontimize-jee-jacoco/target/site/jacoco-aggregate/jacoco.xml" \ No newline at end of file + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN_EE }} + run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=ontimize_ontimize-jee \ No newline at end of file diff --git a/pom.xml b/pom.xml index 4c509296..4e51d201 100644 --- a/pom.xml +++ b/pom.xml @@ -206,7 +206,6 @@ ontimize - https://sonarcloud.io From f5206372ea0d551fbbb60fff6949b46fc8a32076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Tue, 16 Sep 2025 14:10:34 +0200 Subject: [PATCH 13/65] Fix variable names for clarity in Log4j2LoggerHelper --- .../server/services/management/Log4j2LoggerHelper.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java b/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java index 934809e7..74657b30 100644 --- a/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java +++ b/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java @@ -69,10 +69,10 @@ public InputStream getLogFileContent(String fileName) throws Exception { if (!Files.exists(file)) { throw new OntimizeJEEException("File not found"); } - final PipedInputStream pis = new PipedInputStream(); - final PipedOutputStream pos = new PipedOutputStream(pis); + final PipedInputStream inputStream = new PipedInputStream(); + final PipedOutputStream outputStream = new PipedOutputStream(inputStream); new Thread(() -> { - try (ZipOutputStream zos = new ZipOutputStream(pos)) { + try (ZipOutputStream zos = new ZipOutputStream(outputStream)) { zos.putNextEntry(new ZipEntry(file.getFileName().toString())); StreamUtils.copy(Files.newInputStream(file), zos); zos.closeEntry(); @@ -81,7 +81,7 @@ public InputStream getLogFileContent(String fileName) throws Exception { } }, "LoggerHelper copy stream").start(); - return pis; + return inputStream; } private Path getLogFolder() { From 91278415545ea833162720067f8e595613095db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Tue, 16 Sep 2025 14:37:38 +0200 Subject: [PATCH 14/65] Refactor Log4j2LoggerHelper for clarity and consistency --- .../management/Log4j2LoggerHelper.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java b/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java index 74657b30..1ad7d4d3 100644 --- a/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java +++ b/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java @@ -35,11 +35,12 @@ public class Log4j2LoggerHelper implements ILoggerHelper { private static final Logger logger = LoggerFactory.getLogger(Log4j2LoggerHelper.class); public Log4j2LoggerHelper() { + // Empty constructor required for framework instantiation and reflection. + // No initialization logic needed here. } @Override public InputStream openLogStream() throws IOException { - // TODO Auto-generated method stub return null; } @@ -50,7 +51,7 @@ public EntityResult getLogFiles() throws Exception { return new EntityResultMapImpl(EntityResult.OPERATION_SUCCESSFUL_SHOW_MESSAGE, EntityResult.NODATA_RESULT, "No hay ficheros que mostrar"); } - final EntityResult res = new EntityResultMapImpl(Arrays.asList(new String[] { "FILE_NAME", "FILE_SIZE" })); + final EntityResult res = new EntityResultMapImpl(Arrays.asList("FILE_NAME", "FILE_SIZE")); Files.walkFileTree(folder, new java.nio.file.SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { @@ -62,6 +63,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO return res; } + @Override public InputStream getLogFileContent(String fileName) throws Exception { Path folder = this.getLogFolder(); @@ -85,20 +87,18 @@ public InputStream getLogFileContent(String fileName) throws Exception { } private Path getLogFolder() { - for (Logger logger : LogManagerFactory.getLogManager().getLoggerList()) { + for (Logger log : LogManagerFactory.getLogManager().getLoggerList()) { ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); - Map loggersToUse = this.getValidLoggersToUse(loggerFactory); - org.apache.logging.log4j.core.Logger innerLogger = this.getInnerLogger(loggersToUse.get(logger.getName())); + Map loggersToUse = this.getValidLoggersToUse(loggerFactory); + org.apache.logging.log4j.core.Logger innerLogger = this.getInnerLogger(loggersToUse.get(log.getName())); for (Appender appender : innerLogger.getAppenders().values()) { if (appender instanceof FileAppender) { Path file = Paths.get(((FileAppender) appender).getFileName()); - Path folder = file.getParent(); - return folder; + return file.getParent(); } else if (appender instanceof RollingFileAppender) { Path file = Paths.get(((RollingFileAppender) appender).getFileName()); - Path folder = file.getParent(); - return folder; + return file.getParent(); } } } @@ -111,13 +111,12 @@ private org.apache.logging.log4j.core.Logger getInnerLogger(Object logger2) { return (org.apache.logging.log4j.core.Logger) Log4jManager.getReflectionFieldValue(logger2, "logger"); } - // For some extrange reason, when a lloger is requested to logerFactory it gets from a "Default" + // For some strange reason, when a logger is requested to loggerFactory it gets from a "Default" // context, and not from our own context. - private Map getValidLoggersToUse(ILoggerFactory loggerFactory) { + private Map getValidLoggersToUse(ILoggerFactory loggerFactory) { Map registry = (Map) Log4jManager.getReflectionFieldValue(loggerFactory, "registry"); - Map loggersToUse = registry.get(org.apache.logging.log4j.core.LoggerContext.getContext(false)); - return loggersToUse; + return registry.get(org.apache.logging.log4j.core.LoggerContext.getContext(false)); } } From f522d03b25a72f7201eaf1cd27dcf560455290d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Tue, 16 Sep 2025 14:47:19 +0200 Subject: [PATCH 15/65] Enhance Log4j2LoggerHelper with improved Javadoc and type safety --- .../server/services/management/Log4j2LoggerHelper.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java b/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java index 1ad7d4d3..76bbe610 100644 --- a/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java +++ b/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java @@ -64,6 +64,12 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO } + /** + * @param fileName The name of the log file to retrieve. + * @return InputStream zipped with the content of the log file. This stream must be closed by the caller. + * @throws Exception + */ + @SuppressWarnings("java:S2095") @Override public InputStream getLogFileContent(String fileName) throws Exception { Path folder = this.getLogFolder(); @@ -89,7 +95,7 @@ public InputStream getLogFileContent(String fileName) throws Exception { private Path getLogFolder() { for (Logger log : LogManagerFactory.getLogManager().getLoggerList()) { ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); - Map loggersToUse = this.getValidLoggersToUse(loggerFactory); + Map loggersToUse = this.getValidLoggersToUse(loggerFactory); org.apache.logging.log4j.core.Logger innerLogger = this.getInnerLogger(loggersToUse.get(log.getName())); for (Appender appender : innerLogger.getAppenders().values()) { @@ -113,7 +119,7 @@ private org.apache.logging.log4j.core.Logger getInnerLogger(Object logger2) { // For some strange reason, when a logger is requested to loggerFactory it gets from a "Default" // context, and not from our own context. - private Map getValidLoggersToUse(ILoggerFactory loggerFactory) { + private Map getValidLoggersToUse(ILoggerFactory loggerFactory) { Map registry = (Map) Log4jManager.getReflectionFieldValue(loggerFactory, "registry"); return registry.get(org.apache.logging.log4j.core.LoggerContext.getContext(false)); From cfffad558a8aa9cf874acbdc29fe98d11da113a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Wed, 17 Sep 2025 09:20:38 +0200 Subject: [PATCH 16/65] Enhance Log4j2LoggerHelper with null checks and type safety improvements --- .../server/services/management/Log4j2LoggerHelper.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java b/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java index 76bbe610..c6555a9d 100644 --- a/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java +++ b/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java @@ -73,6 +73,9 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO @Override public InputStream getLogFileContent(String fileName) throws Exception { Path folder = this.getLogFolder(); + if (folder == null) { + throw new OntimizeJEEException("Folder not found"); + } final Path file = folder.resolve(fileName); if (!Files.exists(file)) { throw new OntimizeJEEException("File not found"); @@ -95,7 +98,7 @@ public InputStream getLogFileContent(String fileName) throws Exception { private Path getLogFolder() { for (Logger log : LogManagerFactory.getLogManager().getLoggerList()) { ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); - Map loggersToUse = this.getValidLoggersToUse(loggerFactory); + Map loggersToUse = this.getValidLoggersToUse(loggerFactory); org.apache.logging.log4j.core.Logger innerLogger = this.getInnerLogger(loggersToUse.get(log.getName())); for (Appender appender : innerLogger.getAppenders().values()) { @@ -119,10 +122,9 @@ private org.apache.logging.log4j.core.Logger getInnerLogger(Object logger2) { // For some strange reason, when a logger is requested to loggerFactory it gets from a "Default" // context, and not from our own context. - private Map getValidLoggersToUse(ILoggerFactory loggerFactory) { - Map registry = (Map) Log4jManager.getReflectionFieldValue(loggerFactory, + private Map getValidLoggersToUse(ILoggerFactory loggerFactory) { + Map> registry = (Map>) Log4jManager.getReflectionFieldValue(loggerFactory, "registry"); return registry.get(org.apache.logging.log4j.core.LoggerContext.getContext(false)); } - } From 1b4bd83942e953cca5ca037188c8a6f6f15feee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Wed, 17 Sep 2025 09:43:11 +0200 Subject: [PATCH 17/65] Add info to CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3176cba..03994878 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ## [Unreleased] +### Changed +- **Log4j2LoggerHelper**: Clarified variable names, added null checks, enhanced type safety, and improved Javadoc documentation. ## [5.13.0] - 2025-09-11 ### Added ✔️ * **OntimizeJdbcDaoSupport**: Created executeSQLStatement() to use DDL statements. #175 From 1708fe1ba8995b684541230ce7b35db2bf2580d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Wed, 17 Sep 2025 10:02:01 +0200 Subject: [PATCH 18/65] Add spaces to follow conventions --- .../jee/server/services/management/Log4j2LoggerHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java b/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java index c6555a9d..e7e0ba87 100644 --- a/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java +++ b/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java @@ -123,7 +123,7 @@ private org.apache.logging.log4j.core.Logger getInnerLogger(Object logger2) { // For some strange reason, when a logger is requested to loggerFactory it gets from a "Default" // context, and not from our own context. private Map getValidLoggersToUse(ILoggerFactory loggerFactory) { - Map> registry = (Map>) Log4jManager.getReflectionFieldValue(loggerFactory, + Map> registry = (Map>) Log4jManager.getReflectionFieldValue(loggerFactory, "registry"); return registry.get(org.apache.logging.log4j.core.LoggerContext.getContext(false)); } From 3d6f07f687c678737587a90462bfa322e284649d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Wed, 17 Sep 2025 10:03:48 +0200 Subject: [PATCH 19/65] Add info about suppress warning --- .../jee/server/services/management/Log4j2LoggerHelper.java | 1 + 1 file changed, 1 insertion(+) diff --git a/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java b/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java index e7e0ba87..6ac9ecb2 100644 --- a/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java +++ b/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/Log4j2LoggerHelper.java @@ -69,6 +69,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO * @return InputStream zipped with the content of the log file. This stream must be closed by the caller. * @throws Exception */ + // Suppress resource leak warning because the PipedInputStream is returned to the caller. @SuppressWarnings("java:S2095") @Override public InputStream getLogFileContent(String fileName) throws Exception { From dc4a743111c1e19272cd2039d2510097d550f5cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Wed, 17 Sep 2025 10:35:05 +0200 Subject: [PATCH 20/65] Add suppression warning for false positive in getLogFileContent method --- .../jee/server/services/management/LogbackLoggerHelper.java | 1 + 1 file changed, 1 insertion(+) diff --git a/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/LogbackLoggerHelper.java b/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/LogbackLoggerHelper.java index 2c7c6484..654492ab 100644 --- a/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/LogbackLoggerHelper.java +++ b/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/LogbackLoggerHelper.java @@ -230,6 +230,7 @@ private Path getLogFolder() { return null; } + @SuppressWarnings("java:S2095") // False positive, the stream must be closed by the caller public InputStream getLogFileContent(String fileName) throws Exception { Path folder = this.getLogFolder(); final Path file = folder.resolve(fileName); From 76c0d5f2a3ea1ed090ee317085abd508839574bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Wed, 17 Sep 2025 10:38:01 +0200 Subject: [PATCH 21/65] Enhance getLogFileContent method with detailed Javadoc and suppress resource leak warning --- .../server/services/management/LogbackLoggerHelper.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/LogbackLoggerHelper.java b/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/LogbackLoggerHelper.java index 654492ab..1ff69398 100644 --- a/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/LogbackLoggerHelper.java +++ b/ontimize-jee-server/src/main/java/com/ontimize/jee/server/services/management/LogbackLoggerHelper.java @@ -230,7 +230,13 @@ private Path getLogFolder() { return null; } - @SuppressWarnings("java:S2095") // False positive, the stream must be closed by the caller + /** + * @param fileName The name of the log file to retrieve. + * @return InputStream zipped with the content of the log file. This stream must be closed by the caller. + * @throws Exception + */ + // Suppress resource leak warning because the PipedInputStream is returned to the caller. + @SuppressWarnings("java:S2095") public InputStream getLogFileContent(String fileName) throws Exception { Path folder = this.getLogFolder(); final Path file = folder.resolve(fileName); From 44645e5c24085f0c81c8b51075eb45bf6148f54b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Wed, 17 Sep 2025 10:42:07 +0200 Subject: [PATCH 22/65] Enhance getLogFileContent method with detailed Javadoc and suppress resource leak warning --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03994878..a47217a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ## [Unreleased] ### Changed - **Log4j2LoggerHelper**: Clarified variable names, added null checks, enhanced type safety, and improved Javadoc documentation. +- **LogbackLoggerHelper**: Improved Javadoc documentation and suppress false positives on Sonar. ## [5.13.0] - 2025-09-11 ### Added ✔️ * **OntimizeJdbcDaoSupport**: Created executeSQLStatement() to use DDL statements. #175 From affad1e044c9ec1fc1292653e98a2ff312cc1089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Wed, 17 Sep 2025 10:52:52 +0200 Subject: [PATCH 23/65] Refactor string concatenation in XMLClientUtilities for improved readability --- .../com/ontimize/jee/common/security/XMLClientUtilities.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ontimize-jee-common/src/main/java/com/ontimize/jee/common/security/XMLClientUtilities.java b/ontimize-jee-common/src/main/java/com/ontimize/jee/common/security/XMLClientUtilities.java index 4647ee14..e64ed76c 100644 --- a/ontimize-jee-common/src/main/java/com/ontimize/jee/common/security/XMLClientUtilities.java +++ b/ontimize-jee-common/src/main/java/com/ontimize/jee/common/security/XMLClientUtilities.java @@ -696,7 +696,7 @@ private static StringBuffer dom2StringInternal(Node node) { if (namedNodeAttributes != null) { for (int i = 0; i < namedNodeAttributes.getLength(); i++) { Node n = namedNodeAttributes.item(i); - sbAttributes.append(n.getNodeName() + "=\"" + n.getNodeValue() + "\" "); + sbAttributes.append(n.getNodeName()).append("=\"").append(n.getNodeValue()).append("\" "); } } if (node.getNodeType() == Node.ELEMENT_NODE) { From 20c80bc45f2fd930132d2ebef4c3762a24c00258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Wed, 17 Sep 2025 10:58:43 +0200 Subject: [PATCH 24/65] Replace the type specification in this constructor call with the diamond operator --- .../ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index 67e2afec..33602d97 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -331,7 +331,7 @@ protected int getQueryRecordNumber(Map keysValues, final String queryId) { } cond = cond.trim(); - List vValuesTemp = new ArrayList(); + List vValuesTemp = new ArrayList<>(); vValuesTemp.addAll(vValues); Pair replaceAll = StringTools.replaceAll(sqlTemplate, From 0580ea03ab12a23e3714f535e9265900df8e4e4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Wed, 17 Sep 2025 10:59:49 +0200 Subject: [PATCH 25/65] Replace the type specification in this constructor call with the diamond operator --- .../ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index 33602d97..445e45a5 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -458,7 +458,7 @@ public SQLStatement composeQuerySql(final String queryId, final List attribut } cond = cond.trim(); - List vValuesTemp = new ArrayList(); + List vValuesTemp = new ArrayList<>(); vValuesTemp.addAll(vValues); Pair replaceAll = StringTools.replaceAll(sqlTemplate, From 485483ea13df67e2a220926fbdf5f3e3de525993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Wed, 17 Sep 2025 11:11:44 +0200 Subject: [PATCH 26/65] Refactor query method calls to improve parameter order and enhance readability --- .../jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index 445e45a5..a064e0f1 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -361,8 +361,9 @@ protected int getQueryRecordNumber(Map keysValues, final String queryId) { String sqlQuery = stSQL.getSQLStatement(); List vValues = stSQL.getValues(); EntityResult erResult = this.getJdbcTemplate() - .query(sqlQuery, vValues.toArray(), - new EntityResultResultSetExtractor(this.getStatementHandler(), queryTemplateInformation)); + .query(sqlQuery, + new EntityResultResultSetExtractor(this.getStatementHandler(), queryTemplateInformation), + vValues.toArray()); if ((erResult == null) || (erResult.getCode() == EntityResult.OPERATION_WRONG)) { OntimizeJdbcDaoSupport.logger.error("Error executed record count query:{} : {}", erResult.getMessage(), @@ -527,7 +528,7 @@ public List query(final Map keysValues, final List sort, final S sort, null, queryAdapter); final String sqlQuery = stSQL.getSQLStatement(); final List vValues = stSQL.getValues(); - return this.getJdbcTemplate().query(sqlQuery, vValues.toArray(), rowMapper); + return this.getJdbcTemplate().query(sqlQuery, rowMapper, vValues.toArray()); } /** @@ -1610,7 +1611,7 @@ protected void checkDaoConfig() { public void addQueryTemplateInformation(final String id, final String value, final List ambiguousColumns, final List functionColumns, List orderColumns) { - this.addQueryTemplateInformation(id, value, ambiguousColumns, functionColumns, new ArrayList(), + this.addQueryTemplateInformation(id, value, ambiguousColumns, functionColumns, new ArrayList<>(), orderColumns); } From 84b73595e3706956347c68b06129623465e5c6a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Wed, 17 Sep 2025 13:05:23 +0200 Subject: [PATCH 27/65] Refactor SQL placeholder replacement logic into a dedicated method for improved readability and maintainability --- .../dao/jdbc/OntimizeJdbcDaoSupport.java | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index a064e0f1..3ded45d0 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -334,20 +334,8 @@ protected int getQueryRecordNumber(Map keysValues, final String queryId) { List vValuesTemp = new ArrayList<>(); vValuesTemp.addAll(vValues); - Pair replaceAll = StringTools.replaceAll(sqlTemplate, - OntimizeJdbcDaoSupport.PLACEHOLDER_WHERE_CONCAT, - cond.length() == 0 ? "" : SQLStatementBuilder.AND + " " + cond); - sqlTemplate = replaceAll.getFirst(); - for (int i = 1; i < replaceAll.getSecond(); i++) { - vValues.addAll(vValuesTemp); - } + sqlTemplate = this.applyWherePlaceholders(sqlTemplate, cond, vValues, vValuesTemp); - replaceAll = StringTools.replaceAll(sqlTemplate, OntimizeJdbcDaoSupport.PLACEHOLDER_WHERE, - cond.length() == 0 ? "" : SQLStatementBuilder.WHERE + " " + cond); - sqlTemplate = replaceAll.getFirst(); - for (int i = 1; i < replaceAll.getSecond(); i++) { - vValues.addAll(vValuesTemp); - } sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_SCHEMA, this.getSchemaName()); sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_ORDER_CONCAT, ""); sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_ORDER, ""); @@ -462,20 +450,7 @@ public SQLStatement composeQuerySql(final String queryId, final List attribut List vValuesTemp = new ArrayList<>(); vValuesTemp.addAll(vValues); - Pair replaceAll = StringTools.replaceAll(sqlTemplate, - OntimizeJdbcDaoSupport.PLACEHOLDER_WHERE_CONCAT, - cond.length() == 0 ? "" : SQLStatementBuilder.AND + " " + cond); - sqlTemplate = replaceAll.getFirst(); - for (int i = 1; i < replaceAll.getSecond(); i++) { - vValues.addAll(vValuesTemp); - } - - replaceAll = StringTools.replaceAll(sqlTemplate, OntimizeJdbcDaoSupport.PLACEHOLDER_WHERE, - cond.length() == 0 ? "" : SQLStatementBuilder.WHERE + " " + cond); - sqlTemplate = replaceAll.getFirst(); - for (int i = 1; i < replaceAll.getSecond(); i++) { - vValues.addAll(vValuesTemp); - } + sqlTemplate = this.applyWherePlaceholders(sqlTemplate, cond, vValues, vValuesTemp); // Order by List orderColumns = queryTemplateInformation.getOrderColumns(); @@ -504,6 +479,33 @@ public SQLStatement composeQuerySql(final String queryId, final List attribut return stSQL; } + /** + * Replaces the WHERE placeholders in the SQL template and updates the values. + * + * @param sqlTemplate The original SQL template. + * @param cond The generated condition. + * @param vValues The list of values to be updated. + * @param vValuesTemp Temporary list of condition values. + * @return The modified sqlTemplate with the placeholders replaced. + */ + public String applyWherePlaceholders(String sqlTemplate, String cond, List vValues, List vValuesTemp) { + Pair replaceAll = StringTools.replaceAll(sqlTemplate, + OntimizeJdbcDaoSupport.PLACEHOLDER_WHERE_CONCAT, + cond.length() == 0 ? "" : SQLStatementBuilder.AND + " " + cond); + sqlTemplate = replaceAll.getFirst(); + for (int i = 1; i < replaceAll.getSecond(); i++) { + vValues.addAll(vValuesTemp); + } + + replaceAll = StringTools.replaceAll(sqlTemplate, OntimizeJdbcDaoSupport.PLACEHOLDER_WHERE, + cond.length() == 0 ? "" : SQLStatementBuilder.WHERE + " " + cond); + sqlTemplate = replaceAll.getFirst(); + for (int i = 1; i < replaceAll.getSecond(); i++) { + vValues.addAll(vValuesTemp); + } + return sqlTemplate; + } + @Override public List query(final Map keysValues, final List sort, final String queryId, final Class clazz) { From a54858c7ecde50f11507a8c7fbd374891d27b559 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Wed, 17 Sep 2025 13:10:18 +0200 Subject: [PATCH 28/65] Refactor parameter order in JdbcTemplate query mock for avoid potential stubbing problem --- .../jee/server/dao/jdbc/OntimizeJdbcDaoSupportTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ontimize-jee-server-jdbc/src/test/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupportTest.java b/ontimize-jee-server-jdbc/src/test/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupportTest.java index d5e16013..6d523ee2 100644 --- a/ontimize-jee-server-jdbc/src/test/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupportTest.java +++ b/ontimize-jee-server-jdbc/src/test/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupportTest.java @@ -122,7 +122,7 @@ void when_receive_keysValues_and_attributes_and_sort_and_queryId_and_queryAdapte JdbcTemplate jdbcTemplate = Mockito.mock(JdbcTemplate.class); Mockito.doReturn(new AdvancedEntityResultMapImpl(EntityResult.OPERATION_SUCCESSFUL, EntityResult.DATA_RESULT)).when(jdbcTemplate).query(Mockito.any(PreparedStatementCreator.class), Mockito.any(), Mockito.any()); - Mockito.doReturn(new EntityResultMapImpl()).when(jdbcTemplate).query(Mockito.any(String.class), (Object[]) Mockito.any(), Mockito.any(ResultSetExtractor.class)); + Mockito.doReturn(new EntityResultMapImpl()).when(jdbcTemplate).query(Mockito.any(String.class), Mockito.any(ResultSetExtractor.class), (Object[]) Mockito.any()); ontimizeJdbcDaoSupport.setJdbcTemplate(jdbcTemplate); ontimizeJdbcDaoSupport.setStatementHandler(new DefaultSQLStatementHandler()); ReflectionTestUtils.setField(ontimizeJdbcDaoSupport, "compiled", true); From e26c09e4c6b8ed4dcd58430f41a0a87380f66fb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Wed, 17 Sep 2025 13:19:35 +0200 Subject: [PATCH 29/65] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a47217a2..c106bcf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ ### Changed - **Log4j2LoggerHelper**: Clarified variable names, added null checks, enhanced type safety, and improved Javadoc documentation. - **LogbackLoggerHelper**: Improved Javadoc documentation and suppress false positives on Sonar. +- **XMLClientUtilities**: Refactored string concatenation to use StringBuilder for improved readability and performance. +- **OntimizeJdbcDaoSupport**: Refactored SQL placeholder handling, introduced a helper method, and improved type safety to resolve SonarQube issues. +- **OntimizeJdbcDaoSupportTest**: Updated mocks to match the modified method signatures. ## [5.13.0] - 2025-09-11 ### Added ✔️ * **OntimizeJdbcDaoSupport**: Created executeSQLStatement() to use DDL statements. #175 From 0602bdcbd99fd4fe49d257f8ad8ffa5dadce6505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Wed, 17 Sep 2025 15:07:14 +0200 Subject: [PATCH 30/65] Refactor property loading to use a stream for improved readability --- .../jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index 3ded45d0..439196ac 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -23,6 +23,8 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Properties; +import java.util.function.Function; +import java.util.stream.Collectors; import javax.sql.DataSource; import javax.xml.bind.JAXB; @@ -1485,8 +1487,15 @@ protected void loadConfigurationFile(final String path, final String pathToPlace if (isPlaceHolder != null) { prop.load(isPlaceHolder); } + + Map mapProperties = prop.stringPropertyNames() + .stream() + .collect(Collectors.toMap( + Function.identity(), + prop::getProperty + )); reader = new ReplaceTokensFilterReader(new InputStreamReader(is), - new HashMap((Map) prop)); + mapProperties); } } else { reader = new InputStreamReader(is); From c24be6ec9adb729edd364427399a2203abf8a724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Wed, 17 Sep 2025 15:58:31 +0200 Subject: [PATCH 31/65] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c106bcf9..016cdbca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ - **Log4j2LoggerHelper**: Clarified variable names, added null checks, enhanced type safety, and improved Javadoc documentation. - **LogbackLoggerHelper**: Improved Javadoc documentation and suppress false positives on Sonar. - **XMLClientUtilities**: Refactored string concatenation to use StringBuilder for improved readability and performance. -- **OntimizeJdbcDaoSupport**: Refactored SQL placeholder handling, introduced a helper method, and improved type safety to resolve SonarQube issues. +- **OntimizeJdbcDaoSupport**: Refactored SQL placeholder and property handling, introduced a helper method, and improved type safety using Java streams to resolve SonarQube issues. - **OntimizeJdbcDaoSupportTest**: Updated mocks to match the modified method signatures. ## [5.13.0] - 2025-09-11 ### Added ✔️ From 1073434044ebcd53fbc9df5d6f3c4ee5c10b7092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Wed, 17 Sep 2025 16:11:34 +0200 Subject: [PATCH 32/65] Refactor property loading to use a stream for improved readability and maintainability, change deprecated methods for new versions --- .../jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java | 6 +++--- .../jdbc/extension/DefaultDaoExtensionHelper.java | 13 ++++++++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index 439196ac..35361432 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -1831,14 +1831,14 @@ protected KeyHolder executeInsertAndReturnKeyHolderInternal(final InsertMetaInfo // Postgres uses a RETURNING // clause while HSQL uses a second query that has to be executed // with the same connection. - final String keyQuery = this.tableMetaDataContext.getSimulationQueryForGetGeneratedKey( + final String keyQuery = this.tableMetaDataContext.getSimpleQueryForGetGeneratedKey( this.tableMetaDataContext.getTableName(), this.getGeneratedKeyNames()[0]); Assert.notNull(keyQuery, "Query for simulating get generated keys can't be null"); if (keyQuery.toUpperCase().startsWith("RETURNING")) { final Long key = this.getJdbcTemplate() .queryForObject(holder.getInsertString() + " " + keyQuery, - holder.getValues().toArray(new Object[holder.getValues().size()]), - Long.class); + Long.class, + holder.getValues().toArray(new Object[holder.getValues().size()])); final Map keys = new HashMap<>(1); keys.put(this.getGeneratedKeyNames()[0], key); keyHolder.getKeyList().add(keys); diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/extension/DefaultDaoExtensionHelper.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/extension/DefaultDaoExtensionHelper.java index fb4c5aeb..f6db69cc 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/extension/DefaultDaoExtensionHelper.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/extension/DefaultDaoExtensionHelper.java @@ -12,6 +12,8 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.function.Function; +import java.util.stream.Collectors; import javax.xml.bind.JAXB; @@ -139,8 +141,17 @@ protected Reader readWithPlaceHolders(InputStream is, final String pathToPlaceHo if (isPlaceHolder != null) { prop.load(isPlaceHolder); } + + Map mapProperties = prop.stringPropertyNames() + .stream() + .collect(Collectors.toMap( + Function.identity(), + prop::getProperty + )); + return new ReplaceTokensFilterReader(new InputStreamReader(is), - new HashMap((Map) prop)); + mapProperties); + } } else { return new InputStreamReader(is); From e47ef6ba5f98a3f24bca8be9340651298d2a71cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Wed, 17 Sep 2025 16:17:00 +0200 Subject: [PATCH 33/65] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 016cdbca..e729a65a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,9 @@ - **Log4j2LoggerHelper**: Clarified variable names, added null checks, enhanced type safety, and improved Javadoc documentation. - **LogbackLoggerHelper**: Improved Javadoc documentation and suppress false positives on Sonar. - **XMLClientUtilities**: Refactored string concatenation to use StringBuilder for improved readability and performance. -- **OntimizeJdbcDaoSupport**: Refactored SQL placeholder and property handling, introduced a helper method, and improved type safety using Java streams to resolve SonarQube issues. +- **OntimizeJdbcDaoSupport**: Refactored SQL placeholder and property handling, introduced a helper method, corrected key retrieval logic, and improved type safety using Java streams to resolve SonarQube issues. - **OntimizeJdbcDaoSupportTest**: Updated mocks to match the modified method signatures. +- **DefaultDaoExtensionHelper**: Improved property placeholder handling with Java streams for type safety and clarity, addressing SonarQube findings. ## [5.13.0] - 2025-09-11 ### Added ✔️ * **OntimizeJdbcDaoSupport**: Created executeSQLStatement() to use DDL statements. #175 From 88bb4660bc5e93f149fc043a909bdbb608c7b8c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Thu, 18 Sep 2025 07:49:42 +0200 Subject: [PATCH 34/65] Add validation for table name in getGeneratedKeys simulation. Remove unused import --- .../ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java | 3 +++ .../server/dao/jdbc/extension/DefaultDaoExtensionHelper.java | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index 35361432..2b78c83c 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -1825,6 +1825,9 @@ protected KeyHolder executeInsertAndReturnKeyHolderInternal(final InsertMetaInfo + this.getGeneratedKeyNames().length + " columns specified: " + Arrays .asList(this.getGeneratedKeyNames())); } + if (this.tableMetaDataContext.getTableName() == null) { + throw new InvalidDataAccessApiUsageException("Table name is required to simulate getGeneratedKeys"); + } // This is a hack to be able to get the generated key from a // database that doesn't support // get generated keys feature. HSQL is one, PostgreSQL is another. diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/extension/DefaultDaoExtensionHelper.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/extension/DefaultDaoExtensionHelper.java index f6db69cc..fb1fb88c 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/extension/DefaultDaoExtensionHelper.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/extension/DefaultDaoExtensionHelper.java @@ -8,7 +8,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; From d99f6715e9a530561b98fed75a6cf14e0155308a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Thu, 18 Sep 2025 08:14:23 +0200 Subject: [PATCH 35/65] Refactor key generation validation to improve error handling and ensure table name is not null --- .../dao/jdbc/OntimizeJdbcDaoSupport.java | 47 ++++++++++++------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index 2b78c83c..77bad753 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -1810,30 +1810,25 @@ protected KeyHolder executeInsertAndReturnKeyHolderInternal(final InsertMetaInfo holder.getInsertString(), holder.getValues()); final KeyHolder keyHolder = new GeneratedKeyHolder(); if (!this.tableMetaDataContext.isGetGeneratedKeysSupported()) { - if (!this.tableMetaDataContext.isGetGeneratedKeysSimulated()) { - throw new InvalidDataAccessResourceUsageException( - "The getGeneratedKeys feature is not supported by this database"); - } - if (this.getGeneratedKeyNames().length < 1) { - throw new InvalidDataAccessApiUsageException( - "Generated Key Name(s) not specificed. " - + "Using the generated keys features requires specifying the name(s) of the generated column(s)"); - } - if (this.getGeneratedKeyNames().length > 1) { - throw new InvalidDataAccessApiUsageException( - "Current database only supports retreiving the key for a single column. There are " - + this.getGeneratedKeyNames().length + " columns specified: " + Arrays - .asList(this.getGeneratedKeyNames())); - } - if (this.tableMetaDataContext.getTableName() == null) { - throw new InvalidDataAccessApiUsageException("Table name is required to simulate getGeneratedKeys"); + try { + this.validateKeyGenerationSupport(); + } catch (InvalidDataAccessApiUsageException ex) { + OntimizeJdbcDaoSupport.logger.error("Error in key generation validation: {}", ex.getMessage()); + throw ex; } + // This is a hack to be able to get the generated key from a // database that doesn't support // get generated keys feature. HSQL is one, PostgreSQL is another. // Postgres uses a RETURNING // clause while HSQL uses a second query that has to be executed // with the same connection. + final String tableName = this.tableMetaDataContext.getTableName(); + + if (tableName == null) { + throw new IllegalArgumentException("Table name must not be null"); + } + final String keyQuery = this.tableMetaDataContext.getSimpleQueryForGetGeneratedKey( this.tableMetaDataContext.getTableName(), this.getGeneratedKeyNames()[0]); Assert.notNull(keyQuery, "Query for simulating get generated keys can't be null"); @@ -1894,6 +1889,24 @@ public PreparedStatement createPreparedStatement(final Connection con) throws SQ return keyHolder; } + protected void validateKeyGenerationSupport() { + if (!this.tableMetaDataContext.isGetGeneratedKeysSimulated()) { + throw new InvalidDataAccessResourceUsageException( + "The getGeneratedKeys feature is not supported by this database"); + } + if (this.getGeneratedKeyNames().length < 1) { + throw new InvalidDataAccessApiUsageException( + "Generated Key Name(s) not specificed. " + + "Using the generated keys features requires specifying the name(s) of the generated column(s)"); + } + if (this.getGeneratedKeyNames().length > 1) { + throw new InvalidDataAccessApiUsageException( + "Current database only supports retreiving the key for a single column. There are " + + this.getGeneratedKeyNames().length + " columns specified: " + Arrays + .asList(this.getGeneratedKeyNames())); + } + } + /** * Create the PreparedStatement to be used for insert that have generated keys. * @param con the connection used From b5a763f4a4039fd80bf5fc42410b4fe3378ce066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Thu, 18 Sep 2025 08:20:06 +0200 Subject: [PATCH 36/65] Improve error handling in key generation validation by throwing a more descriptive exception --- .../ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index 77bad753..483f11d9 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -1813,8 +1813,7 @@ protected KeyHolder executeInsertAndReturnKeyHolderInternal(final InsertMetaInfo try { this.validateKeyGenerationSupport(); } catch (InvalidDataAccessApiUsageException ex) { - OntimizeJdbcDaoSupport.logger.error("Error in key generation validation: {}", ex.getMessage()); - throw ex; + throw new InvalidDataAccessApiUsageException("Error in key generation validation: {}" + ex.getMessage()); } // This is a hack to be able to get the generated key from a From 8e9f951f7d6fda3ce333b82c09bfa233ff7306e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Thu, 18 Sep 2025 08:27:31 +0200 Subject: [PATCH 37/65] Refactor key generation logic to ensure table name is validated before execution --- .../dao/jdbc/OntimizeJdbcDaoSupport.java | 101 +++++++++--------- 1 file changed, 49 insertions(+), 52 deletions(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index 483f11d9..4890392f 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -1822,59 +1822,56 @@ protected KeyHolder executeInsertAndReturnKeyHolderInternal(final InsertMetaInfo // Postgres uses a RETURNING // clause while HSQL uses a second query that has to be executed // with the same connection. - final String tableName = this.tableMetaDataContext.getTableName(); - if (tableName == null) { - throw new IllegalArgumentException("Table name must not be null"); - } - - final String keyQuery = this.tableMetaDataContext.getSimpleQueryForGetGeneratedKey( - this.tableMetaDataContext.getTableName(), this.getGeneratedKeyNames()[0]); - Assert.notNull(keyQuery, "Query for simulating get generated keys can't be null"); - if (keyQuery.toUpperCase().startsWith("RETURNING")) { - final Long key = this.getJdbcTemplate() - .queryForObject(holder.getInsertString() + " " + keyQuery, - Long.class, - holder.getValues().toArray(new Object[holder.getValues().size()])); - final Map keys = new HashMap<>(1); - keys.put(this.getGeneratedKeyNames()[0], key); - keyHolder.getKeyList().add(keys); - } else { - this.getJdbcTemplate().execute(new ConnectionCallback() { - - @Override - public Object doInConnection(final Connection con) throws SQLException, DataAccessException { - // Do the insert - PreparedStatement ps = null; - try { - ps = con.prepareStatement(holder.getInsertString()); - OntimizeJdbcDaoSupport.this.setParameterValues(ps, holder.getValues(), - holder.getInsertTypes()); - ps.executeUpdate(); - } finally { - JdbcUtils.closeStatement(ps); - } - // Get the key - ResultSet rs = null; - final Map keys = new HashMap<>(1); - final Statement keyStmt = con.createStatement(); - try { - rs = keyStmt.executeQuery(keyQuery); - if (rs.next()) { - final long key = rs.getLong(1); - keys.put(OntimizeJdbcDaoSupport.this.getGeneratedKeyNames()[0], key); - keyHolder.getKeyList().add(keys); - } - } finally { - JdbcUtils.closeResultSet(rs); - JdbcUtils.closeStatement(keyStmt); - } - return null; - } - }); - } - return keyHolder; - } + if (this.tableMetaDataContext.getTableName() != null) { + final String keyQuery = this.tableMetaDataContext.getSimpleQueryForGetGeneratedKey( + this.tableMetaDataContext.getTableName(), this.getGeneratedKeyNames()[0]); + Assert.notNull(keyQuery, "Query for simulating get generated keys can't be null"); + if (keyQuery.toUpperCase().startsWith("RETURNING")) { + final Long key = this.getJdbcTemplate() + .queryForObject(holder.getInsertString() + " " + keyQuery, + Long.class, + holder.getValues().toArray(new Object[holder.getValues().size()])); + final Map keys = new HashMap<>(1); + keys.put(this.getGeneratedKeyNames()[0], key); + keyHolder.getKeyList().add(keys); + } else { + this.getJdbcTemplate().execute(new ConnectionCallback() { + + @Override + public Object doInConnection(final Connection con) throws SQLException, DataAccessException { + // Do the insert + PreparedStatement ps = null; + try { + ps = con.prepareStatement(holder.getInsertString()); + OntimizeJdbcDaoSupport.this.setParameterValues(ps, holder.getValues(), + holder.getInsertTypes()); + ps.executeUpdate(); + } finally { + JdbcUtils.closeStatement(ps); + } + // Get the key + ResultSet rs = null; + final Map keys = new HashMap<>(1); + final Statement keyStmt = con.createStatement(); + try { + rs = keyStmt.executeQuery(keyQuery); + if (rs.next()) { + final long key = rs.getLong(1); + keys.put(OntimizeJdbcDaoSupport.this.getGeneratedKeyNames()[0], key); + keyHolder.getKeyList().add(keys); + } + } finally { + JdbcUtils.closeResultSet(rs); + JdbcUtils.closeStatement(keyStmt); + } + return null; + } + }); + } + return keyHolder; + } + } this.getJdbcTemplate().update(new PreparedStatementCreator() { @Override From bed47f31ac1ec1201e9aaf66eb168a1510e042f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Thu, 18 Sep 2025 08:39:45 +0200 Subject: [PATCH 38/65] Refactor table name retrieval in key generation logic to improve readability and ensure null safety --- .../jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index 4890392f..3d3a18d8 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -1823,9 +1823,11 @@ protected KeyHolder executeInsertAndReturnKeyHolderInternal(final InsertMetaInfo // clause while HSQL uses a second query that has to be executed // with the same connection. - if (this.tableMetaDataContext.getTableName() != null) { + String tableName = (this.tableMetaDataContext.getTableName() != null) ? this.tableMetaDataContext.getTableName() : null; + + if (tableName != null) { final String keyQuery = this.tableMetaDataContext.getSimpleQueryForGetGeneratedKey( - this.tableMetaDataContext.getTableName(), this.getGeneratedKeyNames()[0]); + tableName, this.getGeneratedKeyNames()[0]); Assert.notNull(keyQuery, "Query for simulating get generated keys can't be null"); if (keyQuery.toUpperCase().startsWith("RETURNING")) { final Long key = this.getJdbcTemplate() From 3e1a5ad438d988e95bba51b22d39db61bbf85830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Thu, 18 Sep 2025 09:08:46 +0200 Subject: [PATCH 39/65] Refactor method parameters for improved readability and consistency --- .../dao/jdbc/OntimizeJdbcDaoSupport.java | 212 +++++++++--------- 1 file changed, 102 insertions(+), 110 deletions(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index 3d3a18d8..4dbe0dfc 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -1,6 +1,3 @@ -/** - * - */ package com.ontimize.jee.server.dao.jdbc; import java.io.IOException; @@ -203,7 +200,7 @@ public EntityResult query(Map keysValues, List attributes, List sort */ @Override public EntityResult query(final Map keysValues, final List attributes, final List sort, - final String queryId, ISQLQueryAdapter queryAdapter) { + final String queryId, ISQLQueryAdapter queryAdapter) { this.checkCompiled(); final QueryTemplateInformation queryTemplateInformation = this.getQueryTemplateInformation(queryId); @@ -237,7 +234,7 @@ public EntityResult query(final Map keysValues, final List attributes, @Override public AdvancedEntityResult paginationQuery(Map keysValues, List attributes, int recordNumber, - int startIndex, List orderBy, String queryId) { + int startIndex, List orderBy, String queryId) { return this.paginationQuery(keysValues, attributes, recordNumber, startIndex, orderBy, queryId, null); } @@ -253,8 +250,8 @@ public AdvancedEntityResult paginationQuery(Map keysValues, List attrib */ @Override public AdvancedEntityResult paginationQuery(Map keysValues, List attributes, int recordNumber, - int startIndex, List orderBy, String queryId, - ISQLQueryAdapter queryAdapter) { + int startIndex, List orderBy, String queryId, + ISQLQueryAdapter queryAdapter) { this.checkCompiled(); final QueryTemplateInformation queryTemplateInformation = this.getQueryTemplateInformation(queryId); final SQLStatement stSQL = this.composeQuerySql(queryId, attributes, keysValues, orderBy, @@ -364,7 +361,7 @@ protected int getQueryRecordNumber(Map keysValues, final String queryId) { List v = (List) erResult.get(SQLStatementBuilder.COUNT_COLUMN_NAME); if ((v == null) || v.isEmpty()) { OntimizeJdbcDaoSupport.logger - .error("Error executed record cound query. The result not contain record number."); + .error("Error executed record cound query. The result not contain record number."); return 0; } return ((Number) v.get(0)).intValue(); @@ -387,8 +384,8 @@ protected String performPlaceHolderPagination(String sqlTemplate, PageableInfo p * @return the SQL statement */ public SQLStatement composeQuerySql(final String queryId, final List attributes, final Map keysValues, - final List sort, PageableInfo pageableInfo, - ISQLQueryAdapter queryAdapter) { + final List sort, PageableInfo pageableInfo, + ISQLQueryAdapter queryAdapter) { final QueryTemplateInformation queryTemplateInformation = this.getQueryTemplateInformation(queryId); final Map kvWithoutReferenceAttributes = this.processReferenceDataFieldAttributes(keysValues); @@ -510,7 +507,7 @@ public String applyWherePlaceholders(String sqlTemplate, String cond, List List query(final Map keysValues, final List sort, final String queryId, - final Class clazz) { + final Class clazz) { return this.query(keysValues, sort, queryId, clazz, null); } @@ -525,7 +522,7 @@ public List query(final Map keysValues, final List sort, final S */ @Override public List query(final Map keysValues, final List sort, final String queryId, final Class clazz, - ISQLQueryAdapter queryAdapter) { + ISQLQueryAdapter queryAdapter) { this.checkCompiled(); BeanPropertyRowMapper rowMapper = this.createRowMapper(clazz); final SQLStatement stSQL = this.composeQuerySql(queryId, rowMapper.convertBeanPropertiesToDB(clazz), keysValues, @@ -568,7 +565,7 @@ public boolean executeComposeSQLStatement(String sqlStatement, List vValues) } finally { logger.trace("Time consumed in statement= {} ms", chrono.stopMs()); } - } + } /** * Creates the row mapper. @@ -587,7 +584,7 @@ protected BeanPropertyRowMapper createRowMapper(final Class clazz) { * @return the list */ protected List applyTransformations(final QueryTemplateInformation templateInformation, - final List vValidAttributes) { + final List vValidAttributes) { final List ambiguousColumns = templateInformation.getAmbiguousColumns(); final List functionColumns = templateInformation.getFunctionColumns(); @@ -640,7 +637,7 @@ protected List applyTransformations(final QueryTemplateInformation templateIn * @return the Map */ protected Map applyTransformations(final QueryTemplateInformation templateInformation, - final Map kvValidKeysValues) { + final Map kvValidKeysValues) { final List ambiguousColumns = templateInformation.getAmbiguousColumns(); final List functionColumns = templateInformation.getFunctionColumns(); @@ -737,8 +734,8 @@ protected String resolveAmbiguousColumn(String key, List am * @return the object */ protected BasicExpression applyTransformationsToBasicExpression(final BasicExpression original, - List ambiguousColumns, - List functionColumns) { + List ambiguousColumns, + List functionColumns) { Object originalLeftOperand = original.getLeftOperand(); Operator originalOperator = original.getOperator(); Object originalRightOperand = original.getRightOperand(); @@ -776,7 +773,7 @@ protected BasicExpression applyTransformationsToBasicExpression(final BasicExpre * @return the basic field */ protected BasicField applyTransformationsToBasicField(BasicField originalField, - List ambiguousColumns, List functionColumns) { + List ambiguousColumns, List functionColumns) { String columnName = originalField.toString(); Integer columnType = originalField.getSqlType(); if (columnType == null) columnType = this.getColumnSQLType(columnName); @@ -893,7 +890,7 @@ public EntityResult update(final Map attributesValues, final Map key * @return the entity result */ protected EntityResult innerUpdate(final Map attributesValues, final Map keysValues, - final boolean safe) { + final boolean safe) { this.checkCompiled(); final EntityResult erResult = new EntityResultMapImpl(); @@ -994,7 +991,7 @@ public EntityResult innerDelete(final Map keysValues, final boolean safe) if (keysValuesChecked.isEmpty()) { OntimizeJdbcDaoSupport.logger - .debug("Delete: Keys does not contain any pair key-value valid:" + keysValues); + .debug("Delete: Keys does not contain any pair key-value valid:" + keysValues); throw new SQLWarningException(I18NNaming.M_IT_HAS_NOT_CHANGED_ANY_RECORD, null); } @@ -1060,7 +1057,7 @@ protected void checkUpdateKeys(final Map keysValues) { } } OntimizeJdbcDaoSupport.logger - .debug(" Update valid keys values: Input: " + keysValues + " -> Result: " + hValidKeysValues); + .debug(" Update valid keys values: Input: " + keysValues + " -> Result: " + hValidKeysValues); return hValidKeysValues; } @@ -1225,7 +1222,7 @@ protected boolean isColumnNameValid(String ob) { * @return */ protected Map getValidQueryingKeysValues(Map inputKeysValues, - List validColumns) { + List validColumns) { if ((validColumns == null) || validColumns.isEmpty()) { return inputKeysValues; } @@ -1237,7 +1234,7 @@ protected Map getValidQueryingKeysValues(Map inp } } OntimizeJdbcDaoSupport.logger - .debug(" Query valid keys values: Input: " + inputKeysValues + " -> Result: " + hValidKeysValues); + .debug(" Query valid keys values: Input: " + inputKeysValues + " -> Result: " + hValidKeysValues); return hValidKeysValues; } @@ -1517,10 +1514,10 @@ protected void loadConfigurationFile(final String path, final String pathToPlace this.addQueryTemplateInformation(query.getId(), query.getSentence().getValue(), // query.getAmbiguousColumns() == null ? null : query.getAmbiguousColumns().getAmbiguousColumn(), // - query.getFunctionColumns() == null ? null : query.getFunctionColumns().getFunctionColumn(), // - query.getValidColumns() != null ? query.getValidColumns().getColumn() - : new ArrayList(), // - query.getOrderColumns() == null ? null : query.getOrderColumns().getOrderColumn()); + query.getFunctionColumns() == null ? null : query.getFunctionColumns().getFunctionColumn(), // + query.getValidColumns() != null ? query.getValidColumns().getColumn() + : new ArrayList(), // + query.getOrderColumns() == null ? null : query.getOrderColumns().getOrderColumn()); } } this.setGeneratedKeyName(setupConfig.getGeneratedKey()); @@ -1540,7 +1537,7 @@ protected void loadConfigurationFile(final String path, final String pathToPlace } protected JdbcEntitySetupType checkDaoExtensions(JdbcEntitySetupType baseSetup, final String path, - final String pathToPlaceHolder) { + final String pathToPlaceHolder) { if (this.daoExtensionHelper == null) { return baseSetup; } @@ -1620,8 +1617,8 @@ protected void checkDaoConfig() { * @param functionColumns the function columns */ public void addQueryTemplateInformation(final String id, final String value, - final List ambiguousColumns, final List functionColumns, - List orderColumns) { + final List ambiguousColumns, final List functionColumns, + List orderColumns) { this.addQueryTemplateInformation(id, value, ambiguousColumns, functionColumns, new ArrayList<>(), orderColumns); } @@ -1635,8 +1632,8 @@ public void addQueryTemplateInformation(final String id, final String value, * @param validColumns */ public void addQueryTemplateInformation(final String id, final String value, - final List ambiguousColumns, final List functionColumns, - List validColumns, List orderColumns) { + final List ambiguousColumns, final List functionColumns, + List validColumns, List orderColumns) { this.sqlQueries.put(id, new QueryTemplateInformation(value, ambiguousColumns, functionColumns, validColumns, orderColumns)); } @@ -1810,10 +1807,20 @@ protected KeyHolder executeInsertAndReturnKeyHolderInternal(final InsertMetaInfo holder.getInsertString(), holder.getValues()); final KeyHolder keyHolder = new GeneratedKeyHolder(); if (!this.tableMetaDataContext.isGetGeneratedKeysSupported()) { - try { - this.validateKeyGenerationSupport(); - } catch (InvalidDataAccessApiUsageException ex) { - throw new InvalidDataAccessApiUsageException("Error in key generation validation: {}" + ex.getMessage()); + if (!this.tableMetaDataContext.isGetGeneratedKeysSimulated()) { + throw new InvalidDataAccessResourceUsageException( + "The getGeneratedKeys feature is not supported by this database"); + } + if (this.getGeneratedKeyNames().length < 1) { + throw new InvalidDataAccessApiUsageException( + "Generated Key Name(s) not specificed. " + + "Using the generated keys features requires specifying the name(s) of the generated column(s)"); + } + if (this.getGeneratedKeyNames().length > 1) { + throw new InvalidDataAccessApiUsageException( + "Current database only supports retreiving the key for a single column. There are " + + this.getGeneratedKeyNames().length + " columns specified: " + Arrays + .asList(this.getGeneratedKeyNames())); } // This is a hack to be able to get the generated key from a @@ -1823,57 +1830,60 @@ protected KeyHolder executeInsertAndReturnKeyHolderInternal(final InsertMetaInfo // clause while HSQL uses a second query that has to be executed // with the same connection. - String tableName = (this.tableMetaDataContext.getTableName() != null) ? this.tableMetaDataContext.getTableName() : null; - - if (tableName != null) { - final String keyQuery = this.tableMetaDataContext.getSimpleQueryForGetGeneratedKey( - tableName, this.getGeneratedKeyNames()[0]); - Assert.notNull(keyQuery, "Query for simulating get generated keys can't be null"); - if (keyQuery.toUpperCase().startsWith("RETURNING")) { - final Long key = this.getJdbcTemplate() - .queryForObject(holder.getInsertString() + " " + keyQuery, - Long.class, - holder.getValues().toArray(new Object[holder.getValues().size()])); - final Map keys = new HashMap<>(1); - keys.put(this.getGeneratedKeyNames()[0], key); - keyHolder.getKeyList().add(keys); - } else { - this.getJdbcTemplate().execute(new ConnectionCallback() { - - @Override - public Object doInConnection(final Connection con) throws SQLException, DataAccessException { - // Do the insert - PreparedStatement ps = null; - try { - ps = con.prepareStatement(holder.getInsertString()); - OntimizeJdbcDaoSupport.this.setParameterValues(ps, holder.getValues(), - holder.getInsertTypes()); - ps.executeUpdate(); - } finally { - JdbcUtils.closeStatement(ps); - } - // Get the key - ResultSet rs = null; - final Map keys = new HashMap<>(1); - final Statement keyStmt = con.createStatement(); - try { - rs = keyStmt.executeQuery(keyQuery); - if (rs.next()) { - final long key = rs.getLong(1); - keys.put(OntimizeJdbcDaoSupport.this.getGeneratedKeyNames()[0], key); - keyHolder.getKeyList().add(keys); - } - } finally { - JdbcUtils.closeResultSet(rs); - JdbcUtils.closeStatement(keyStmt); - } - return null; - } - }); - } - return keyHolder; - } - } + String tableName = this.tableMetaDataContext.getTableName() != null ? this.tableMetaDataContext.getTableName() : null; + String keyQuery = null; + + if (tableName != null){ + keyQuery = this.tableMetaDataContext.getSimpleQueryForGetGeneratedKey( + tableName, this.getGeneratedKeyNames()[0]); + } + + Assert.notNull(keyQuery, "Query for simulating get generated keys can't be null"); + + if (keyQuery.toUpperCase().startsWith("RETURNING")) { + final Long key = this.getJdbcTemplate() + .queryForObject(holder.getInsertString() + " " + keyQuery, + Long.class, + holder.getValues().toArray(new Object[holder.getValues().size()])); + final Map keys = new HashMap<>(1); + keys.put(this.getGeneratedKeyNames()[0], key); + keyHolder.getKeyList().add(keys); + } else { + this.getJdbcTemplate().execute(new ConnectionCallback() { + + @Override + public Object doInConnection(final Connection con) throws SQLException, DataAccessException { + // Do the insert + PreparedStatement ps = null; + try { + ps = con.prepareStatement(holder.getInsertString()); + OntimizeJdbcDaoSupport.this.setParameterValues(ps, holder.getValues(), + holder.getInsertTypes()); + ps.executeUpdate(); + } finally { + JdbcUtils.closeStatement(ps); + } + // Get the key + ResultSet rs = null; + final Map keys = new HashMap<>(1); + final Statement keyStmt = con.createStatement(); + try { + rs = keyStmt.executeQuery(keyQuery); + if (rs.next()) { + final long key = rs.getLong(1); + keys.put(OntimizeJdbcDaoSupport.this.getGeneratedKeyNames()[0], key); + keyHolder.getKeyList().add(keys); + } + } finally { + JdbcUtils.closeResultSet(rs); + JdbcUtils.closeStatement(keyStmt); + } + return null; + } + }); + } + return keyHolder; + } this.getJdbcTemplate().update(new PreparedStatementCreator() { @Override @@ -1887,24 +1897,6 @@ public PreparedStatement createPreparedStatement(final Connection con) throws SQ return keyHolder; } - protected void validateKeyGenerationSupport() { - if (!this.tableMetaDataContext.isGetGeneratedKeysSimulated()) { - throw new InvalidDataAccessResourceUsageException( - "The getGeneratedKeys feature is not supported by this database"); - } - if (this.getGeneratedKeyNames().length < 1) { - throw new InvalidDataAccessApiUsageException( - "Generated Key Name(s) not specificed. " - + "Using the generated keys features requires specifying the name(s) of the generated column(s)"); - } - if (this.getGeneratedKeyNames().length > 1) { - throw new InvalidDataAccessApiUsageException( - "Current database only supports retreiving the key for a single column. There are " - + this.getGeneratedKeyNames().length + " columns specified: " + Arrays - .asList(this.getGeneratedKeyNames())); - } - } - /** * Create the PreparedStatement to be used for insert that have generated keys. * @param con the connection used @@ -1955,7 +1947,7 @@ public int[] insertBatch(final Map[] batch) { * @return array of number of rows affected */ protected int[] doExecuteInsertBatch(final SqlParameterSource[] batch, final String insertString, - final int[] insertTypes) { + final int[] insertTypes) { this.checkCompiled(); final List[] batchValues = new ArrayList[batch.length]; int i = 0; @@ -1972,7 +1964,7 @@ protected int[] doExecuteInsertBatch(final SqlParameterSource[] batch, final Str * @return the int[] */ protected int[] executeInsertBatchInternal(final List[] batchValues, final String insertString, - final int[] insertTypes) { + final int[] insertTypes) { OntimizeJdbcDaoSupport.logger.debug("Executing statement {} with batch of size: {}", insertString, batchValues.length); return this.getJdbcTemplate().batchUpdate(insertString, new BatchPreparedStatementSetter() { @@ -1998,7 +1990,7 @@ public int getBatchSize() { * @throws SQLException the sQL exception */ protected void setParameterValues(final PreparedStatement preparedStatement, final List values, - final int[] columnTypes) throws SQLException { + final int[] columnTypes) throws SQLException { int colIndex = 0; for (Object value : values) { @@ -2203,7 +2195,7 @@ protected JdbcTemplate createJdbcTemplate(DataSource dataSource) { JdbcTemplate template = super.createJdbcTemplate(dataSource); template.setFetchSize(1000); OntimizeJdbcDaoSupport.logger - .trace("Creating new JdbcTemplate has finally fetchSize=" + template.getFetchSize()); + .trace("Creating new JdbcTemplate has finally fetchSize=" + template.getFetchSize()); return template; } @@ -2218,4 +2210,4 @@ private Integer getColumnSQLType(final String column) { } return null; } -} +} \ No newline at end of file From 7c9613be340dc9399bf2bb25c2b47aacb4381d2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Thu, 18 Sep 2025 09:18:05 +0200 Subject: [PATCH 40/65] Refactor key query handling in OntimizeJdbcDaoSupport for improved readability and consistency --- .../dao/jdbc/OntimizeJdbcDaoSupport.java | 101 +++++++++--------- 1 file changed, 50 insertions(+), 51 deletions(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index 4dbe0dfc..bfdc1fd6 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -1831,58 +1831,57 @@ protected KeyHolder executeInsertAndReturnKeyHolderInternal(final InsertMetaInfo // with the same connection. String tableName = this.tableMetaDataContext.getTableName() != null ? this.tableMetaDataContext.getTableName() : null; - String keyQuery = null; - if (tableName != null){ - keyQuery = this.tableMetaDataContext.getSimpleQueryForGetGeneratedKey( - tableName, this.getGeneratedKeyNames()[0]); - } - - Assert.notNull(keyQuery, "Query for simulating get generated keys can't be null"); - - if (keyQuery.toUpperCase().startsWith("RETURNING")) { - final Long key = this.getJdbcTemplate() - .queryForObject(holder.getInsertString() + " " + keyQuery, - Long.class, - holder.getValues().toArray(new Object[holder.getValues().size()])); - final Map keys = new HashMap<>(1); - keys.put(this.getGeneratedKeyNames()[0], key); - keyHolder.getKeyList().add(keys); - } else { - this.getJdbcTemplate().execute(new ConnectionCallback() { - - @Override - public Object doInConnection(final Connection con) throws SQLException, DataAccessException { - // Do the insert - PreparedStatement ps = null; - try { - ps = con.prepareStatement(holder.getInsertString()); - OntimizeJdbcDaoSupport.this.setParameterValues(ps, holder.getValues(), - holder.getInsertTypes()); - ps.executeUpdate(); - } finally { - JdbcUtils.closeStatement(ps); - } - // Get the key - ResultSet rs = null; - final Map keys = new HashMap<>(1); - final Statement keyStmt = con.createStatement(); - try { - rs = keyStmt.executeQuery(keyQuery); - if (rs.next()) { - final long key = rs.getLong(1); - keys.put(OntimizeJdbcDaoSupport.this.getGeneratedKeyNames()[0], key); - keyHolder.getKeyList().add(keys); - } - } finally { - JdbcUtils.closeResultSet(rs); - JdbcUtils.closeStatement(keyStmt); - } - return null; - } - }); - } - return keyHolder; + if (tableName != null) { + final String keyQuery = this.tableMetaDataContext.getSimpleQueryForGetGeneratedKey( + tableName, this.getGeneratedKeyNames()[0]); + + Assert.notNull(keyQuery, "Query for simulating get generated keys can't be null"); + + if (keyQuery.toUpperCase().startsWith("RETURNING")) { + final Long key = this.getJdbcTemplate() + .queryForObject(holder.getInsertString() + " " + keyQuery, + Long.class, + holder.getValues().toArray(new Object[holder.getValues().size()])); + final Map keys = new HashMap<>(1); + keys.put(this.getGeneratedKeyNames()[0], key); + keyHolder.getKeyList().add(keys); + } else { + this.getJdbcTemplate().execute(new ConnectionCallback() { + + @Override + public Object doInConnection(final Connection con) throws SQLException, DataAccessException { + // Do the insert + PreparedStatement ps = null; + try { + ps = con.prepareStatement(holder.getInsertString()); + OntimizeJdbcDaoSupport.this.setParameterValues(ps, holder.getValues(), + holder.getInsertTypes()); + ps.executeUpdate(); + } finally { + JdbcUtils.closeStatement(ps); + } + // Get the key + ResultSet rs = null; + final Map keys = new HashMap<>(1); + final Statement keyStmt = con.createStatement(); + try { + rs = keyStmt.executeQuery(keyQuery); + if (rs.next()) { + final long key = rs.getLong(1); + keys.put(OntimizeJdbcDaoSupport.this.getGeneratedKeyNames()[0], key); + keyHolder.getKeyList().add(keys); + } + } finally { + JdbcUtils.closeResultSet(rs); + JdbcUtils.closeStatement(keyStmt); + } + return null; + } + }); + } + } + return keyHolder; } this.getJdbcTemplate().update(new PreparedStatementCreator() { From 417c484f9d3498f497b941636c5e9d76d2efea40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Thu, 18 Sep 2025 09:30:14 +0200 Subject: [PATCH 41/65] Refactor key retrieval logic in OntimizeJdbcDaoSupport for separation of concerns --- .../dao/jdbc/OntimizeJdbcDaoSupport.java | 92 ++++++++++--------- 1 file changed, 50 insertions(+), 42 deletions(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index bfdc1fd6..f237b3d7 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -1839,48 +1839,11 @@ protected KeyHolder executeInsertAndReturnKeyHolderInternal(final InsertMetaInfo Assert.notNull(keyQuery, "Query for simulating get generated keys can't be null"); if (keyQuery.toUpperCase().startsWith("RETURNING")) { - final Long key = this.getJdbcTemplate() - .queryForObject(holder.getInsertString() + " " + keyQuery, - Long.class, - holder.getValues().toArray(new Object[holder.getValues().size()])); - final Map keys = new HashMap<>(1); - keys.put(this.getGeneratedKeyNames()[0], key); - keyHolder.getKeyList().add(keys); - } else { - this.getJdbcTemplate().execute(new ConnectionCallback() { - - @Override - public Object doInConnection(final Connection con) throws SQLException, DataAccessException { - // Do the insert - PreparedStatement ps = null; - try { - ps = con.prepareStatement(holder.getInsertString()); - OntimizeJdbcDaoSupport.this.setParameterValues(ps, holder.getValues(), - holder.getInsertTypes()); - ps.executeUpdate(); - } finally { - JdbcUtils.closeStatement(ps); - } - // Get the key - ResultSet rs = null; - final Map keys = new HashMap<>(1); - final Statement keyStmt = con.createStatement(); - try { - rs = keyStmt.executeQuery(keyQuery); - if (rs.next()) { - final long key = rs.getLong(1); - keys.put(OntimizeJdbcDaoSupport.this.getGeneratedKeyNames()[0], key); - keyHolder.getKeyList().add(keys); - } - } finally { - JdbcUtils.closeResultSet(rs); - JdbcUtils.closeStatement(keyStmt); - } - return null; - } - }); - } - } + this.simulatingWithReturning(holder, keyQuery, keyHolder); + }else{ + this.simulatingWithoutReturning(holder, keyQuery, keyHolder); + } + } return keyHolder; } this.getJdbcTemplate().update(new PreparedStatementCreator() { @@ -1896,6 +1859,51 @@ public PreparedStatement createPreparedStatement(final Connection con) throws SQ return keyHolder; } + private void simulatingWithoutReturning(InsertMetaInfoHolder holder, String keyQuery, KeyHolder keyHolder) { + this.getJdbcTemplate().execute(new ConnectionCallback() { + + @Override + public Object doInConnection(final Connection con) throws SQLException, DataAccessException { + // Do the insert + PreparedStatement ps = null; + try { + ps = con.prepareStatement(holder.getInsertString()); + OntimizeJdbcDaoSupport.this.setParameterValues(ps, holder.getValues(), + holder.getInsertTypes()); + ps.executeUpdate(); + } finally { + JdbcUtils.closeStatement(ps); + } + // Get the key + ResultSet rs = null; + final Map keys = new HashMap<>(1); + final Statement keyStmt = con.createStatement(); + try { + rs = keyStmt.executeQuery(keyQuery); + if (rs.next()) { + final long key = rs.getLong(1); + keys.put(OntimizeJdbcDaoSupport.this.getGeneratedKeyNames()[0], key); + keyHolder.getKeyList().add(keys); + } + } finally { + JdbcUtils.closeResultSet(rs); + JdbcUtils.closeStatement(keyStmt); + } + return null; + } + }); + } + + private void simulatingWithReturning(InsertMetaInfoHolder holder, String keyQuery, KeyHolder keyHolder) { + final Long key = this.getJdbcTemplate() + .queryForObject(holder.getInsertString() + " " + keyQuery, + Long.class, + holder.getValues().toArray(new Object[holder.getValues().size()])); + final Map keys = new HashMap<>(1); + keys.put(this.getGeneratedKeyNames()[0], key); + keyHolder.getKeyList().add(keys); + } + /** * Create the PreparedStatement to be used for insert that have generated keys. * @param con the connection used From 071dcdd5f1978611d174d7511e73a6000df86297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Thu, 18 Sep 2025 10:17:35 +0200 Subject: [PATCH 42/65] Change visibility of methods --- .../ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index f237b3d7..74efb1b7 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -1859,7 +1859,7 @@ public PreparedStatement createPreparedStatement(final Connection con) throws SQ return keyHolder; } - private void simulatingWithoutReturning(InsertMetaInfoHolder holder, String keyQuery, KeyHolder keyHolder) { + protected void simulatingWithoutReturning(InsertMetaInfoHolder holder, String keyQuery, KeyHolder keyHolder) { this.getJdbcTemplate().execute(new ConnectionCallback() { @Override @@ -1894,7 +1894,7 @@ public Object doInConnection(final Connection con) throws SQLException, DataAcce }); } - private void simulatingWithReturning(InsertMetaInfoHolder holder, String keyQuery, KeyHolder keyHolder) { + protected void simulatingWithReturning(InsertMetaInfoHolder holder, String keyQuery, KeyHolder keyHolder) { final Long key = this.getJdbcTemplate() .queryForObject(holder.getInsertString() + " " + keyQuery, Long.class, From 8bb4b512a66d41356bb27840f7dfccdb203247f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Thu, 18 Sep 2025 10:29:57 +0200 Subject: [PATCH 43/65] Reformat code --- .../dao/jdbc/OntimizeJdbcDaoSupport.java | 4307 ++++++++--------- 1 file changed, 2123 insertions(+), 2184 deletions(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index 74efb1b7..b906f553 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -1,70 +1,9 @@ package com.ontimize.jee.server.dao.jdbc; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.SQLWarning; -import java.sql.Statement; -import java.sql.Types; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Properties; -import java.util.function.Function; -import java.util.stream.Collectors; - -import javax.sql.DataSource; -import javax.xml.bind.JAXB; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.dao.DataAccessException; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.dao.DataRetrievalFailureException; -import org.springframework.dao.InvalidDataAccessApiUsageException; -import org.springframework.dao.InvalidDataAccessResourceUsageException; -import org.springframework.jdbc.SQLWarningException; -import org.springframework.jdbc.core.ArgumentPreparedStatementSetter; -import org.springframework.jdbc.core.BatchPreparedStatementSetter; -import org.springframework.jdbc.core.ConnectionCallback; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.PreparedStatementCreator; -import org.springframework.jdbc.core.SqlParameterValue; -import org.springframework.jdbc.core.SqlProvider; -import org.springframework.jdbc.core.SqlTypeValue; -import org.springframework.jdbc.core.StatementCreatorUtils; -import org.springframework.jdbc.core.metadata.TableParameterMetaData; -import org.springframework.jdbc.core.namedparam.SqlParameterSource; -import org.springframework.jdbc.core.support.JdbcDaoSupport; -import org.springframework.jdbc.support.GeneratedKeyHolder; -import org.springframework.jdbc.support.JdbcUtils; -import org.springframework.jdbc.support.KeyHolder; -import org.springframework.util.Assert; - import com.ontimize.jee.common.db.AdvancedEntityResult; import com.ontimize.jee.common.db.AdvancedEntityResultMapImpl; import com.ontimize.jee.common.db.NullValue; import com.ontimize.jee.common.db.SQLStatementBuilder; -import com.ontimize.jee.common.db.SQLStatementBuilder.BasicExpression; -import com.ontimize.jee.common.db.SQLStatementBuilder.BasicField; -import com.ontimize.jee.common.db.SQLStatementBuilder.ExtendedSQLConditionValuesProcessor; -import com.ontimize.jee.common.db.SQLStatementBuilder.Operator; -import com.ontimize.jee.common.db.SQLStatementBuilder.SQLOrder; -import com.ontimize.jee.common.db.SQLStatementBuilder.SQLStatement; import com.ontimize.jee.common.db.handler.SQLStatementHandler; import com.ontimize.jee.common.db.util.DBFunctionName; import com.ontimize.jee.common.dto.EntityResult; @@ -73,11 +12,7 @@ import com.ontimize.jee.common.gui.field.MultipleTableAttribute; import com.ontimize.jee.common.gui.field.ReferenceFieldAttribute; import com.ontimize.jee.common.naming.I18NNaming; -import com.ontimize.jee.common.tools.CheckingTools; -import com.ontimize.jee.common.tools.Chronometer; -import com.ontimize.jee.common.tools.ObjectTools; -import com.ontimize.jee.common.tools.Pair; -import com.ontimize.jee.common.tools.StringTools; +import com.ontimize.jee.common.tools.*; import com.ontimize.jee.common.tools.streamfilter.ReplaceTokensFilterReader; import com.ontimize.jee.server.dao.DaoProperty; import com.ontimize.jee.server.dao.IOntimizeDaoSupport; @@ -86,2135 +21,2139 @@ import com.ontimize.jee.server.dao.common.INameConvention; import com.ontimize.jee.server.dao.common.INameConverter; import com.ontimize.jee.server.dao.jdbc.extension.IDaoExtensionHelper; -import com.ontimize.jee.server.dao.jdbc.setup.AmbiguousColumnType; -import com.ontimize.jee.server.dao.jdbc.setup.FunctionColumnType; -import com.ontimize.jee.server.dao.jdbc.setup.JdbcEntitySetupType; -import com.ontimize.jee.server.dao.jdbc.setup.OrderColumnType; -import com.ontimize.jee.server.dao.jdbc.setup.QueryType; +import com.ontimize.jee.server.dao.jdbc.setup.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.dao.*; +import org.springframework.jdbc.SQLWarningException; +import org.springframework.jdbc.core.*; +import org.springframework.jdbc.core.metadata.TableParameterMetaData; +import org.springframework.jdbc.core.namedparam.SqlParameterSource; +import org.springframework.jdbc.core.support.JdbcDaoSupport; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.JdbcUtils; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.util.Assert; + +import javax.sql.DataSource; +import javax.xml.bind.JAXB; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.sql.*; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; /** * The Class OntimizeJdbcDaoSupport. */ public class OntimizeJdbcDaoSupport extends JdbcDaoSupport implements ApplicationContextAware, IOntimizeDaoSupport { - /** The logger. */ - protected static final Logger logger = LoggerFactory.getLogger(OntimizeJdbcDaoSupport.class); - - /** The Constant PLACEHOLDER_ORDER. */ - protected static final String PLACEHOLDER_ORDER = "#ORDER#"; - - /** The Constant PLACEHOLDER_ORDER_CONCAT. */ - protected static final String PLACEHOLDER_ORDER_CONCAT = "#ORDER_CONCAT#"; - - /** The Constant PLACEHOLDER_WHERE. */ - protected static final String PLACEHOLDER_WHERE = "#WHERE#"; - - /** The Constant PLACEHOLDER_WHERE_CONCAT. */ - protected static final String PLACEHOLDER_WHERE_CONCAT = "#WHERE_CONCAT#"; - - /** The Constant PLACEHOLDER_COLUMNS. */ - protected static final String PLACEHOLDER_COLUMNS = "#COLUMNS#"; - - /** The Constant PLACEHOLDER_SCHEMA. */ - protected static final String PLACEHOLDER_SCHEMA = "#SCHEMA#"; - - /** Context used to retrieve and manage database metadata. */ - protected final OntimizeTableMetaDataContext tableMetaDataContext; - - /** List of columns objects to be used in insert statement. */ - protected final List declaredColumns = new ArrayList<>(); - - /** - * Has this operation been compiled? Compilation means at least checking that a DataSource or - * JdbcTemplate has been provided, but subclasses may also implement their own custom validation. - */ - private boolean compiled = false; - - private String[] generatedKeyNames = {}; - - /** The statement builder. */ - private SQLStatementHandler statementHandler; - - /** The bean property converter. */ - private INameConverter nameConverter; - - /** Mandatory delete keys. */ - private List deleteKeys; - - /** Mandatory update keys. */ - private List updateKeys; - - /** Queries. */ - protected final Map sqlQueries = new HashMap<>(); - - /** The application context. */ - private ApplicationContext applicationContext; - - /** - * Configuration file - */ - private String configurationFile = null; - - /** - * Configuration file placeholder - */ - private String configurationFilePlaceholder = null; - - /** - * Name convention - * - */ - @Autowired - private INameConvention nameConvention; - - /** Dao Extension helper. */ - @Autowired(required = false) - private IDaoExtensionHelper daoExtensionHelper; - - /** - * Instantiates a new ontimize jdbc dao support. - */ - public OntimizeJdbcDaoSupport() { - super(); - this.tableMetaDataContext = this.createTableMetadataContext(); - } - - public OntimizeJdbcDaoSupport(final String configurationFile, final String configurationFilePlaceholder) { - this(); - this.configurationFile = configurationFile; - this.configurationFilePlaceholder = configurationFilePlaceholder; - } - - @Override - public EntityResult query(Map keysValues, List attributes, List sort, String queryId) { - return this.query(keysValues, attributes, sort, queryId, null); - } - - /** - * Query. - * @param keysValues the keys values - * @param attributes the attributes - * @param sort the sort - * @param queryId the query id - * @return the entity result - */ - @Override - public EntityResult query(final Map keysValues, final List attributes, final List sort, - final String queryId, ISQLQueryAdapter queryAdapter) { - this.checkCompiled(); - final QueryTemplateInformation queryTemplateInformation = this.getQueryTemplateInformation(queryId); - - final SQLStatement stSQL = this.composeQuerySql(queryId, attributes, keysValues, sort, null, queryAdapter); - - final String sqlQuery = stSQL.getSQLStatement(); - final List vValues = stSQL.getValues(); - // TODO los atributos que se pasan al entityresultsetextractor tienen que ir "desambiguados" porque - // cuando el DefaultSQLStatementHandler busca - // las columnas toUpperCase y toLowerCase no tiene en cuenta el '.' - Chronometer chrono = new Chronometer().start(); - try { - - JdbcTemplate jdbcTemplate = this.getJdbcTemplate(); - - if (jdbcTemplate != null) { - - ArgumentPreparedStatementSetter pss = new ArgumentPreparedStatementSetter(vValues.toArray()); - - return jdbcTemplate.query(sqlQuery, pss, - new EntityResultResultSetExtractor(this.getStatementHandler(), queryTemplateInformation, - attributes)); - } - - return new EntityResultMapImpl(EntityResult.OPERATION_WRONG, EntityResult.NODATA_RESULT); - - } finally { - OntimizeJdbcDaoSupport.logger.trace("Time consumed in query+result= {} ms", chrono.stopMs()); - } - } - - @Override - public AdvancedEntityResult paginationQuery(Map keysValues, List attributes, int recordNumber, - int startIndex, List orderBy, String queryId) { - return this.paginationQuery(keysValues, attributes, recordNumber, startIndex, orderBy, queryId, null); - } - - /** - * Pageable query. - * @param keysValues the keys values - * @param attributes the attributes - * @param recordNumber number of records to query - * @param startIndex number of first row - * @param orderBy list of columns to establish the order - * @param queryId the query id - * @return the entity result - */ - @Override - public AdvancedEntityResult paginationQuery(Map keysValues, List attributes, int recordNumber, - int startIndex, List orderBy, String queryId, - ISQLQueryAdapter queryAdapter) { - this.checkCompiled(); - final QueryTemplateInformation queryTemplateInformation = this.getQueryTemplateInformation(queryId); - final SQLStatement stSQL = this.composeQuerySql(queryId, attributes, keysValues, orderBy, - new PageableInfo(recordNumber, startIndex), - queryAdapter); - final String sqlQuery = stSQL.getSQLStatement(); - final List vValues = stSQL.getValues(); - - ArgumentPreparedStatementSetter pss = new ArgumentPreparedStatementSetter(vValues.toArray()); - - JdbcTemplate jdbcTemplate = this.getJdbcTemplate(); - - if (jdbcTemplate != null) { - AdvancedEntityResult advancedER = jdbcTemplate.query( - new SimpleScrollablePreparedStatementCreator(sqlQuery), pss, new AdvancedEntityResultResultSetExtractor( - this.getStatementHandler(), queryTemplateInformation, attributes, recordNumber, startIndex)); - - advancedER.setTotalRecordCount(this.getQueryRecordNumber(keysValues, queryId)); - return advancedER; - - } - - return new AdvancedEntityResultMapImpl(EntityResult.OPERATION_WRONG, EntityResult.NODATA_RESULT); - - } - - protected static class SimpleScrollablePreparedStatementCreator implements PreparedStatementCreator, SqlProvider { - - private final String sql; - - public SimpleScrollablePreparedStatementCreator(String sql) { - Assert.notNull(sql, "SQL must not be null"); - this.sql = sql; - } - - @Override - public PreparedStatement createPreparedStatement(Connection con) throws SQLException { - return con.prepareStatement(this.sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); - } - - @Override - public String getSql() { - return this.sql; - } - } - - protected int getQueryRecordNumber(Map keysValues, final String queryId) { - final QueryTemplateInformation queryTemplateInformation = this.getQueryTemplateInformation(queryId); - final Map kvWithoutReferenceAttributes = this.processReferenceDataFieldAttributes(keysValues); - Map kvValidKeysValues = new HashMap<>(); - final Map processMultipleValueAttributes = this - .processMultipleValueAttributes(kvWithoutReferenceAttributes); - if (processMultipleValueAttributes != null) { - kvValidKeysValues.putAll(processMultipleValueAttributes); - } - - SQLStatement stSQL = null; - - if (queryTemplateInformation != null) { - List validColumns = queryTemplateInformation.getValidColumns(); - kvValidKeysValues = this.getValidQueryingKeysValues(kvValidKeysValues, validColumns); - - kvValidKeysValues = this.applyTransformations(queryTemplateInformation, kvValidKeysValues); - - final StringBuilder sbColumns = new StringBuilder(); - sbColumns.append(SQLStatementBuilder.COUNT); - - String sqlTemplate = queryTemplateInformation.getSqlTemplate() - .replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_COLUMNS, sbColumns.toString()); - // Where - final List vValues = new ArrayList<>(); - String cond = this.getStatementHandler() - .createQueryConditionsWithoutWhere(kvValidKeysValues, new ArrayList<>(), vValues); - if (cond == null) { - cond = ""; - } - cond = cond.trim(); - - List vValuesTemp = new ArrayList<>(); - vValuesTemp.addAll(vValues); - - sqlTemplate = this.applyWherePlaceholders(sqlTemplate, cond, vValues, vValuesTemp); - - sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_SCHEMA, this.getSchemaName()); - sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_ORDER_CONCAT, ""); - sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_ORDER, ""); - stSQL = new SQLStatement(sqlTemplate, vValues); - } else { - stSQL = this.getStatementHandler() - .createCountQuery(this.getSchemaTable(), new HashMap<>(kvValidKeysValues), new ArrayList<>(), - new ArrayList<>()); - } - - String sqlQuery = stSQL.getSQLStatement(); - List vValues = stSQL.getValues(); - EntityResult erResult = this.getJdbcTemplate() - .query(sqlQuery, - new EntityResultResultSetExtractor(this.getStatementHandler(), queryTemplateInformation), - vValues.toArray()); - - if ((erResult == null) || (erResult.getCode() == EntityResult.OPERATION_WRONG)) { - OntimizeJdbcDaoSupport.logger.error("Error executed record count query:{} : {}", erResult.getMessage(), - sqlQuery); - return 0; - } - - List v = (List) erResult.get(SQLStatementBuilder.COUNT_COLUMN_NAME); - if ((v == null) || v.isEmpty()) { - OntimizeJdbcDaoSupport.logger - .error("Error executed record cound query. The result not contain record number."); - return 0; - } - return ((Number) v.get(0)).intValue(); - } - - protected String performPlaceHolderPagination(String sqlTemplate, PageableInfo pageableInfo) { - if (pageableInfo == null) { - return sqlTemplate; - } - return this.getStatementHandler() - .convertPaginationStatement(sqlTemplate, pageableInfo.getStartIndex(), pageableInfo.getRecordNumber()); - } - - /** - * Compose sql. - * @param queryId the query id - * @param attributes the attributes - * @param keysValues the keys values - * @param sort the sort - * @return the SQL statement - */ - public SQLStatement composeQuerySql(final String queryId, final List attributes, final Map keysValues, - final List sort, PageableInfo pageableInfo, - ISQLQueryAdapter queryAdapter) { - final QueryTemplateInformation queryTemplateInformation = this.getQueryTemplateInformation(queryId); - - final Map kvWithoutReferenceAttributes = this.processReferenceDataFieldAttributes(keysValues); - Map kvValidKeysValues = new HashMap<>(); - final Map processMultipleValueAttributes = this - .processMultipleValueAttributes(kvWithoutReferenceAttributes); - if (processMultipleValueAttributes != null) { - kvValidKeysValues.putAll(processMultipleValueAttributes); - } - - List vValidAttributes = this.processReferenceDataFieldAttributes(attributes); - - SQLStatement stSQL = null; - if (queryTemplateInformation == null) { - vValidAttributes = this.getValidAttributes(vValidAttributes, null); - CheckingTools.failIf(vValidAttributes.isEmpty(), "NO_ATTRIBUTES_TO_QUERY"); - // use table - if (pageableInfo != null) { - stSQL = this.getStatementHandler() - .createSelectQuery(this.getSchemaTable(), new ArrayList<>(vValidAttributes), - new HashMap<>(kvValidKeysValues), new ArrayList<>(), - new ArrayList<>(sort == null ? Collections.emptyList() : sort), - pageableInfo.getRecordNumber(), - pageableInfo.getStartIndex()); - } else { - stSQL = this.getStatementHandler() - .createSelectQuery(this.getSchemaTable(), new ArrayList<>(vValidAttributes), - new HashMap<>(kvValidKeysValues), new ArrayList<>(), - new ArrayList<>(sort == null ? Collections.emptyList() : sort)); - } - - } else { - List validColumns = queryTemplateInformation.getValidColumns(); - kvValidKeysValues = this.getValidQueryingKeysValues(kvValidKeysValues, validColumns); - vValidAttributes = this.getValidAttributes(vValidAttributes, validColumns); - - kvValidKeysValues = this.applyTransformations(queryTemplateInformation, kvValidKeysValues); - vValidAttributes = this.applyTransformations(queryTemplateInformation, vValidAttributes); - CheckingTools.failIf(vValidAttributes.isEmpty(), "NO_ATTRIBUTES_TO_QUERY"); - final StringBuilder sbColumns = new StringBuilder(); - // columns - for (final Object ob : vValidAttributes) { - sbColumns.append(ob.toString()); - sbColumns.append(SQLStatementBuilder.COMMA); - } - for (int i = 0; i < SQLStatementBuilder.COMMA.length(); i++) { - sbColumns.deleteCharAt(sbColumns.length() - 1); - } - String sqlTemplate = queryTemplateInformation.getSqlTemplate() - .replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_COLUMNS, sbColumns.toString()); - - // Where - final List vValues = new ArrayList<>(); - String cond = this.getStatementHandler() - .createQueryConditionsWithoutWhere(kvValidKeysValues, new ArrayList<>(), vValues); - if (cond == null) { - cond = ""; - } - cond = cond.trim(); - - List vValuesTemp = new ArrayList<>(); - vValuesTemp.addAll(vValues); - - sqlTemplate = this.applyWherePlaceholders(sqlTemplate, cond, vValues, vValuesTemp); - - // Order by - List orderColumns = queryTemplateInformation.getOrderColumns(); - List sortColumns = this.applyOrderColumns(sort, orderColumns); - String order = this.getStatementHandler().createSortStatement(sortColumns, false); - if (order.length() > 0) { - order = order.substring(SQLStatementBuilder.ORDER_BY.length()); - } - order = order.trim(); - - sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_ORDER_CONCAT, - order.length() == 0 ? "" : SQLStatementBuilder.COMMA + " " + order); - sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_ORDER, - order.length() == 0 ? "" : SQLStatementBuilder.ORDER_BY + " " + order); - if (pageableInfo != null) { - sqlTemplate = this.performPlaceHolderPagination(sqlTemplate, pageableInfo); - } - sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_SCHEMA, this.getSchemaName()); - stSQL = new SQLStatement(sqlTemplate, vValues); - } - if (queryAdapter != null) { - stSQL = queryAdapter.adaptQuery(stSQL, this, keysValues, kvValidKeysValues, attributes, vValidAttributes, - sort, queryId); - } - OntimizeJdbcDaoSupport.logger.trace(stSQL.getSQLStatement()); - return stSQL; - } - - /** - * Replaces the WHERE placeholders in the SQL template and updates the values. - * - * @param sqlTemplate The original SQL template. - * @param cond The generated condition. - * @param vValues The list of values to be updated. - * @param vValuesTemp Temporary list of condition values. - * @return The modified sqlTemplate with the placeholders replaced. - */ - public String applyWherePlaceholders(String sqlTemplate, String cond, List vValues, List vValuesTemp) { - Pair replaceAll = StringTools.replaceAll(sqlTemplate, - OntimizeJdbcDaoSupport.PLACEHOLDER_WHERE_CONCAT, - cond.length() == 0 ? "" : SQLStatementBuilder.AND + " " + cond); - sqlTemplate = replaceAll.getFirst(); - for (int i = 1; i < replaceAll.getSecond(); i++) { - vValues.addAll(vValuesTemp); - } - - replaceAll = StringTools.replaceAll(sqlTemplate, OntimizeJdbcDaoSupport.PLACEHOLDER_WHERE, - cond.length() == 0 ? "" : SQLStatementBuilder.WHERE + " " + cond); - sqlTemplate = replaceAll.getFirst(); - for (int i = 1; i < replaceAll.getSecond(); i++) { - vValues.addAll(vValuesTemp); - } - return sqlTemplate; - } - - @Override - public List query(final Map keysValues, final List sort, final String queryId, - final Class clazz) { - return this.query(keysValues, sort, queryId, clazz, null); - } - - /** - * Query. - * @param the generic type - * @param keysValues the keys values - * @param sort the sort - * @param queryId the query id - * @param clazz the clazz - * @return the list - */ - @Override - public List query(final Map keysValues, final List sort, final String queryId, final Class clazz, - ISQLQueryAdapter queryAdapter) { - this.checkCompiled(); - BeanPropertyRowMapper rowMapper = this.createRowMapper(clazz); - final SQLStatement stSQL = this.composeQuerySql(queryId, rowMapper.convertBeanPropertiesToDB(clazz), keysValues, - sort, null, queryAdapter); - final String sqlQuery = stSQL.getSQLStatement(); - final List vValues = stSQL.getValues(); - return this.getJdbcTemplate().query(sqlQuery, rowMapper, vValues.toArray()); - } - - /** - * Executes a single SQL statement using a prepared statement with the given parameters. - * - *

This method is intended for executing a single SQL command (e.g., CREATE, DROP, UPDATE), and supports parameter - * substitution using placeholders.

- * - *

Restrictions: - *

    - *
  • Only one SQL statement can be executed per call. Do not pass multiple statements separated by semicolons.
  • - *
  • Dynamic WHERE clauses not working with DLL statements.
  • - *
  • This method does not return results.
  • - *
- *

- * - * @param sqlStatement The SQL statement to execute. - * @param vValues The list of values for the prepared statement. - */ - public boolean executeComposeSQLStatement(String sqlStatement, List vValues) { - Chronometer chrono = new Chronometer().start(); - try { - JdbcTemplate jdbcTemplate = this.getJdbcTemplate(); - if (jdbcTemplate != null) { - jdbcTemplate.execute((ConnectionCallback) con -> { - PreparedStatement ps = con.prepareStatement(sqlStatement); - ArgumentPreparedStatementSetter pss = new ArgumentPreparedStatementSetter(vValues.toArray()); - pss.setValues(ps); - return ps.execute(); - }); - } - return false; - } finally { - logger.trace("Time consumed in statement= {} ms", chrono.stopMs()); - } - } - - /** - * Creates the row mapper. - * @param the generic type - * @param clazz the clazz - * @return the bean property row mapper - */ - protected BeanPropertyRowMapper createRowMapper(final Class clazz) { - return new BeanPropertyRowMapper<>(this.getNameConverter(), this.getDataSource(), clazz); - } - - /** - * Apply template prefix. - * @param templateInformation the template information - * @param vValidAttributes the v valid attributes - * @return the list - */ - protected List applyTransformations(final QueryTemplateInformation templateInformation, - final List vValidAttributes) { - final List ambiguousColumns = templateInformation.getAmbiguousColumns(); - final List functionColumns = templateInformation.getFunctionColumns(); - - final List res = new ArrayList<>(vValidAttributes.size()); - for (final Object ob : vValidAttributes) { - boolean transformed = false; - if (ambiguousColumns != null) { - for (final AmbiguousColumnType ambiguosColumn : ambiguousColumns) { - if (ob.toString().toUpperCase().equals(ambiguosColumn.getName().toUpperCase())) { - final String dbName = ambiguosColumn.getDatabaseName() == null ? ambiguosColumn.getName() - : ambiguosColumn.getDatabaseName(); - final StringBuilder sb = new StringBuilder(); - sb.append(ambiguosColumn.getPrefix()); - sb.append("."); - sb.append(dbName); - sb.append(SQLStatementBuilder.AS); - sb.append(ambiguosColumn.getName()); - res.add(sb.toString()); - transformed = true; - break; - } - } - } - if (!transformed && (functionColumns != null)) { - for (final FunctionColumnType functionColumn : functionColumns) { - if (ob.toString().toUpperCase().equals(functionColumn.getName().toUpperCase())) { - final StringBuilder sb = new StringBuilder(); - sb.append(SQLStatementBuilder.OPEN_PARENTHESIS); - sb.append(functionColumn.getValue()); - sb.append(SQLStatementBuilder.CLOSE_PARENTHESIS); - sb.append(SQLStatementBuilder.AS); - sb.append(functionColumn.getName()); - res.add(sb.toString()); - transformed = true; - break; - } - } - } - if (!transformed) { - res.add(ob); - } - } - return res; - } - - /** - * Apply template prefix. - * @param templateInformation the template information - * @param kvValidKeysValues the kv valid keys values - * @return the Map - */ - protected Map applyTransformations(final QueryTemplateInformation templateInformation, - final Map kvValidKeysValues) { - final List ambiguousColumns = templateInformation.getAmbiguousColumns(); - final List functionColumns = templateInformation.getFunctionColumns(); - - final Map res = new HashMap<>(); - for (final Entry kvEntry : kvValidKeysValues.entrySet()) { - if (kvEntry.getKey() instanceof String) { - String key = (String) kvEntry.getKey(); - boolean transformed = false; - if ( (ExtendedSQLConditionValuesProcessor.EXPRESSION_KEY.equals(key) || ExtendedSQLConditionValuesProcessor.FILTER_KEY.equals(key)) - && (kvEntry.getValue() instanceof BasicExpression)) { - res.put(key, this.applyTransformationsToBasicExpression((BasicExpression) kvEntry.getValue(), - ambiguousColumns, functionColumns)); - transformed = true; - } else { - String resolvedAmbiguousColumn = this.resolveAmbiguousColumn(key, ambiguousColumns); - if (resolvedAmbiguousColumn != null) { - res.put(resolvedAmbiguousColumn, kvEntry.getValue()); - transformed = true; - } else { - String resolvedFunctionColumn = this.resolveFunctionColumn(key, functionColumns); - if (resolvedFunctionColumn != null) { - res.put(resolvedFunctionColumn, kvEntry.getValue()); - transformed = true; - } - } - } - if (!transformed) { - res.put(key, kvEntry.getValue()); - } - } else { - res.put(kvEntry.getKey(), kvEntry.getValue()); - } - } - return res; - } - - protected List applyOrderColumns(final List sort, final List orderColumns) { - List vResult = new ArrayList<>(); - if ((sort != null) && (sort.size() > 0)) { - vResult.addAll(sort); - } - - if ((orderColumns != null) && (orderColumns.size() > 0)) { - for (OrderColumnType orderColumnType : orderColumns) { - SQLOrder sqlOrder = new SQLOrder(orderColumnType.getName(), "ASC".equals(orderColumnType.getType())); - vResult.add(sqlOrder); - } - } - - return vResult; - } - - /** - * Resolve function column. - * @param key the key - * @param functionColumns the function columns - * @return the string - */ - protected String resolveFunctionColumn(String key, List functionColumns) { - if (functionColumns != null) { - for (final FunctionColumnType functionColumn : functionColumns) { - if (key.toString().toUpperCase().equals(functionColumn.getName().toUpperCase())) { - return functionColumn.getValue(); - } - } - } - return null; - } - - /** - * Resolve ambiguous column. - * @param key the key - * @param ambiguousColumns the ambiguous columns - * @return the string - */ - protected String resolveAmbiguousColumn(String key, List ambiguousColumns) { - if (ambiguousColumns != null) { - for (final AmbiguousColumnType ambiguosColumn : ambiguousColumns) { - if (key.toUpperCase().equals(ambiguosColumn.getName().toUpperCase())) { - final String dbName = ambiguosColumn.getDatabaseName() == null ? key - : ambiguosColumn.getDatabaseName(); - return ambiguosColumn.getPrefix() + "." + dbName; - } - } - } - return null; - } - - /** - * Apply transformations to basic expression. - * @param functionColumns - * @param ambiguousColumns - * @param value the value - * @return the object - */ - protected BasicExpression applyTransformationsToBasicExpression(final BasicExpression original, - List ambiguousColumns, - List functionColumns) { - Object originalLeftOperand = original.getLeftOperand(); - Operator originalOperator = original.getOperator(); - Object originalRightOperand = original.getRightOperand(); - Object transformedLeftOperand = null; - Operator transformedOperator = originalOperator; - Object transformedRightOperand = null; - if (originalLeftOperand instanceof BasicField) { - transformedLeftOperand = this.applyTransformationsToBasicField((BasicField) originalLeftOperand, - ambiguousColumns, functionColumns); - } else if (originalLeftOperand instanceof BasicExpression) { - transformedLeftOperand = this.applyTransformationsToBasicExpression((BasicExpression) originalLeftOperand, - ambiguousColumns, functionColumns); - } else { - transformedLeftOperand = originalLeftOperand; - } - - if (originalRightOperand instanceof BasicField) { - transformedRightOperand = this.applyTransformationsToBasicField((BasicField) originalRightOperand, - ambiguousColumns, functionColumns); - } else if (originalRightOperand instanceof BasicExpression) { - transformedRightOperand = this.applyTransformationsToBasicExpression((BasicExpression) originalRightOperand, - ambiguousColumns, functionColumns); - } else { - transformedRightOperand = originalRightOperand; - } - - return new BasicExpression(transformedLeftOperand, transformedOperator, transformedRightOperand); - } - - /** - * Apply transformations to basic field. - * @param originalField the original field - * @param ambiguousColumns the ambiguous columns - * @param functionColumns the function columns - * @return the basic field - */ - protected BasicField applyTransformationsToBasicField(BasicField originalField, - List ambiguousColumns, List functionColumns) { - String columnName = originalField.toString(); - Integer columnType = originalField.getSqlType(); - if (columnType == null) columnType = this.getColumnSQLType(columnName); - String resolvedAmbiguousColumn = this.resolveAmbiguousColumn(columnName, ambiguousColumns); - if (resolvedAmbiguousColumn != null) { - return new BasicField(resolvedAmbiguousColumn, columnType); - } - String resolvedFunctionColumn = this.resolveFunctionColumn(columnName, functionColumns); - if (resolvedFunctionColumn != null) { - return new BasicField(resolvedFunctionColumn, columnType); - } - return new BasicField(columnName, columnType); - } - - /* - * (non-Javadoc) - * - * @see com.ontimize.jee.server.entity.IOntimizeDaoSupport#insert(java.util.Map) - */ - /** - * Insert. - * @param attributesValues the attributes values - * @return the entity result - */ - @Override - public EntityResult insert(final Map attributesValues) { - this.checkCompiled(); - final EntityResult erResult = new EntityResultMapImpl(); - - final Map avWithoutMultipleTableAttributes = this.processMultipleTableAttribute(attributesValues); - final Map avWithoutReferenceAttributes = this - .processReferenceDataFieldAttributes(avWithoutMultipleTableAttributes); - final Map avWithoutMultipleValueAttributes = this - .processMultipleValueAttributes(avWithoutReferenceAttributes); - final Map avValidPre = this - .getValidAttributes(this.processStringKeys(avWithoutMultipleValueAttributes)); - final Map avValid = this.removeNullValues(avValidPre); - if (avValid.isEmpty()) { - // TODO se deber�a lanzar excepci�n, pero puede tener colaterales con la one-2-one - OntimizeJdbcDaoSupport.logger.warn("Insert: Attributes does not contain any pair key-value valid"); - return erResult; - } - - if (this.getGeneratedKeyNames().length < 1) { - final int res = this.doExecuteInsert(avValid); - if (res != 1) { - throw new SQLWarningException(I18NNaming.M_IT_HAS_NOT_CHANGED_ANY_RECORD, null); - } - } else if (this.getGeneratedKeyNames().length == 1) { - final Object res = this.doExecuteInsertAndReturnKey(avValid); - if (res == null) { - throw new DataRetrievalFailureException(I18NNaming.M_IT_HAS_NOT_CHANGED_ANY_RECORD); - } - erResult.put(this.nameConvention.convertName(this.getGeneratedKeyNames()[0]), res); - } - return erResult; - } - - /** - * Removes the null values. - * @param inputAttributesValues the input attributes values - * @return the map - */ - protected Map removeNullValues(Map inputAttributesValues) { - final Map hValidKeysValues = new HashMap<>(); - for (final Entry entry : inputAttributesValues.entrySet()) { - final String oKey = entry.getKey(); - final Object oValue = entry.getValue(); - if ((oValue != null) && !(oValue instanceof NullValue)) { - hValidKeysValues.put(oKey, oValue); - } - } - return hValidKeysValues; - } - - /* - * (non-Javadoc) - * - * @see com.ontimize.jee.server.entity.IOntimizeDaoSupport#unsafeUpdate(java.util .Map, - * java.util.Map) - */ - /** - * Unsafe update. - * @param attributesValues the attributes values - * @param keysValues the keys values - * @return the entity result - */ - @Override - public EntityResult unsafeUpdate(final Map attributesValues, final Map keysValues) { - return this.innerUpdate(attributesValues, keysValues, false); - } - - /* - * (non-Javadoc) - * - * @see com.ontimize.jee.server.entity.IOntimizeDaoSupport#update(java.util.Map, java.util.Map) - */ - /** - * Update. - * @param attributesValues the attributes values - * @param keysValues the keys values - * @return the entity result - */ - @Override - public EntityResult update(final Map attributesValues, final Map keysValues) { - return this.innerUpdate(attributesValues, keysValues, true); - } - - /** - * Inner update. - * @param attributesValues the attributes values - * @param keysValues the keys values - * @param safe the safe - * @return the entity result - */ - protected EntityResult innerUpdate(final Map attributesValues, final Map keysValues, - final boolean safe) { - this.checkCompiled(); - final EntityResult erResult = new EntityResultMapImpl(); - - // Check the primary keys - final Map avWithoutMultipleTableAttributes = this.processMultipleTableAttribute(attributesValues); - final Map avWithoutReferenceAttributes = this - .processReferenceDataFieldAttributes(avWithoutMultipleTableAttributes); - final Map avWithoutMultipleValue = this.processMultipleValueAttributes(avWithoutReferenceAttributes); - - final Map kvWithoutMulpleTableAttributes = this.processMultipleTableAttribute(keysValues); - final Map kvWithoutReferenceAttributessRef = this - .processReferenceDataFieldAttributes(kvWithoutMulpleTableAttributes); - - final Map hValidAttributesValues = this.getValidAttributes(avWithoutMultipleValue); - Map hValidKeysValues = null; - if (safe) { - hValidKeysValues = this.getValidUpdatingKeysValues(kvWithoutReferenceAttributessRef); - this.checkUpdateKeys(hValidKeysValues); - } else { - hValidKeysValues = kvWithoutReferenceAttributessRef; - } - - if (hValidAttributesValues.isEmpty() || hValidKeysValues.isEmpty()) { - OntimizeJdbcDaoSupport.logger.debug("Update: Attributes or Keys do not contain any pair key-value valid"); - throw new SQLWarningException(I18NNaming.M_IT_HAS_NOT_CHANGED_ANY_RECORD, null); - } - final SQLStatement stSQL = this.getStatementHandler() - .createUpdateQuery(this.getSchemaTable(), new HashMap<>(hValidAttributesValues), - new HashMap<>(hValidKeysValues)); - final String sqlQuery = stSQL.getSQLStatement(); - final List vValues = this.processNullValues(stSQL.getValues()); - final int update = this.getJdbcTemplate().update(sqlQuery, vValues.toArray()); - if (update == 0) { - erResult.setCode(EntityResult.OPERATION_SUCCESSFUL_SHOW_MESSAGE); - erResult.setMessage(I18NNaming.M_IT_HAS_NOT_CHANGED_ANY_RECORD); - } - return erResult; - } - - /** - * Process null values. - * @param values the values - * @return the list - */ - protected List processNullValues(final List values) { - for (int i = 0; i < values.size(); i++) { - final Object ob = values.get(i); - if (ob instanceof NullValue) { - ((List) values).set(i, new SqlParameterValue(((NullValue) ob).getSQLDataType(), null)); - } - } - return values; - } - - /* - * (non-Javadoc) - * - * @see com.ontimize.jee.server.entity.IOntimizeDaoSupport#unsafeDelete(java.util .Map) - */ - /** - * Unsafe delete. - * @param keysValues the keys values - * @return the entity result - */ - @Override - public EntityResult unsafeDelete(final Map keysValues) { - return this.innerDelete(keysValues, false); - } - - /* - * (non-Javadoc) - * - * @see com.ontimize.jee.server.entity.IOntimizeDaoSupport#delete(java.util.Map) - */ - /** - * Delete. - * @param keysValues the keys values - * @return the entity result - */ - @Override - public EntityResult delete(final Map keysValues) { - return this.innerDelete(keysValues, true); - } - - /** - * Inner delete. - * @param keysValues the keys values - * @param safe the safe - * @return the entity result - */ - public EntityResult innerDelete(final Map keysValues, final boolean safe) { - this.checkCompiled(); - final EntityResult erResult = new EntityResultMapImpl(); - Map keysValuesChecked = keysValues; - if (safe) { - keysValuesChecked = this.checkDeleteKeys(keysValues); - } - - if (keysValuesChecked.isEmpty()) { - OntimizeJdbcDaoSupport.logger - .debug("Delete: Keys does not contain any pair key-value valid:" + keysValues); - throw new SQLWarningException(I18NNaming.M_IT_HAS_NOT_CHANGED_ANY_RECORD, null); - } - - final SQLStatement stSQL = this.getStatementHandler() - .createDeleteQuery(this.getSchemaTable(), new HashMap<>(keysValuesChecked)); - final String sqlQuery = stSQL.getSQLStatement(); - final List vValues = stSQL.getValues(); - this.getJdbcTemplate().update(sqlQuery, vValues.toArray()); - - return erResult; - } - - /** - * Checks if keysValues contains a value for all columns defined in 'delete_keys' - * parameter. - *

- * @param keysValues the keys values - */ - protected Map checkDeleteKeys(final Map keysValues) { - Map res = new HashMap<>(); - for (String element : this.deleteKeys) { - String mapKey = element; - if (!keysValues.containsKey(mapKey)) { - throw new SQLWarningException("M_NECESSARY_" + mapKey.toUpperCase(), null); - } - res.put(mapKey, keysValues.get(mapKey)); - } - OntimizeJdbcDaoSupport.logger.debug(" Delete valid keys values: Input: {} -> Result: {}", keysValues, res); - return res; - } - - /** - * Checks if keysValues contains a value for all columns defined in 'update_keys' - * parameter. - *

- * @param keysValues the keys values - */ - protected void checkUpdateKeys(final Map keysValues) { - for (String element : this.updateKeys) { - if (!keysValues.containsKey(element)) { - throw new SQLWarningException("M_NECESSARY_" + element.toUpperCase(), new SQLWarning()); - } - } - } - - /** - * Returns a Map containing a list of valid key-value pairs from those contained in the - * keysValues argument. - *

- * A key-value pair is valid if the key is valid. - *

- * Only keys matching (case-sensitive) any of the columns defined by the 'update_keys' parameter are - * considered valid. - *

- * @param keysValues the keys values - * @return the valid updating keys values - */ - public Map getValidUpdatingKeysValues(final Map keysValues) { - final Map hValidKeysValues = new HashMap<>(); - for (String element : this.updateKeys) { - if (keysValues.containsKey(element)) { - hValidKeysValues.put(element, keysValues.get(element)); - } - } - OntimizeJdbcDaoSupport.logger - .debug(" Update valid keys values: Input: " + keysValues + " -> Result: " + hValidKeysValues); - return hValidKeysValues; - } - - /** - * Returns a Map containing a list of valid key-value pairs from those contained in the - * attributesValues argument. - *

- * A key-value pair is valid if the key is in the table column list. - *

- * @param inputAttributesValues the attributes values - * @return the valid attributes - */ - public Map getValidAttributes(final Map inputAttributesValues) { - final Map hValidKeysValues = new HashMap<>(); - List nameConventionTableColumns = this.tableMetaDataContext.getNameConventionTableColumns(); - for (final Entry entry : inputAttributesValues.entrySet()) { - final Object oKey = entry.getKey(); - final Object oValue = entry.getValue(); - if (nameConventionTableColumns.contains(oKey)) { - hValidKeysValues.put((String) oKey, oValue); - } - } - OntimizeJdbcDaoSupport.logger.debug( - " Update valid attributes values: Input: " + inputAttributesValues + " -> Result: " + hValidKeysValues); - return hValidKeysValues; - } - - /** - * Processes all the MultipleTableAttribute contained as keys ih the Map av. All other - * objects are added to the resulting List with no changes. The MultipleTableAttribute objects are - * replaced by their attribute. - * @param av the av - * @return a new HashMap with the processed objects. - */ - protected Map processMultipleTableAttribute(final Map av) { - final Map res = new HashMap<>(); - for (final Entry entry : av.entrySet()) { - final Object oKey = entry.getKey(); - final Object oValue = entry.getValue(); - if (oKey instanceof MultipleTableAttribute) { - res.put(((MultipleTableAttribute) oKey).getAttribute(), oValue); - } else { - res.put(oKey, oValue); - } - } - return res; - } - - /** - * Processes the ReferenceFieldAttribute objects contained in keysValues. - *

- * Returns a Map containing all the objects contained in the argument keysValues except - * in the case of keys that are ReferenceFieldAttribute objects, which are replaced by - * ((ReferenceFieldAttribute)object).getAttr() - *

- * @param keysValues the keysValues to process - * @return a Map containing the processed objects - */ - public Map processReferenceDataFieldAttributes(final Map keysValues) { - if (keysValues == null) { - return null; - } - final Map res = new HashMap<>(); - for (final Entry entry : keysValues.entrySet()) { - final Object oKey = entry.getKey(); - final Object oValue = entry.getValue(); - if (oKey instanceof ReferenceFieldAttribute) { - final String attr = ((ReferenceFieldAttribute) oKey).getAttr(); - res.put(attr, oValue); - } else { - res.put(oKey, oValue); - } - } - return res; - } - - /** - * Processes the ReferenceFieldAttribute objects contained in list. - *

- * Returns a List containing all the objects in the argument list except in the case of - * keys that are ReferenceFieldAttribute objects, which are maintained but also - * ((ReferenceFieldAttribute)object).getAttr() is added - *

- * @param list the list to process - * @return a List containing the processed objects - */ - public List processReferenceDataFieldAttributes(final List list) { - if (list == null) { - return null; - } - final List res = new ArrayList<>(); - for (final Object ob : list) { - // Add the attribute - if (!res.contains(ob)) { - res.add(ob); - } - // If the attribute is ReferenceFieldAttribute add the string to - if ((ob instanceof ReferenceFieldAttribute) && !res.contains(((ReferenceFieldAttribute) ob).getAttr())) { - res.add(((ReferenceFieldAttribute) ob).getAttr()); - } - } - return res; - } - - /** - * Returns a list containing the valid attributes of those included in the List - * attributes - *

- * If valid column names have been specified for this entity, only attributes matching - * (case-sensitive) any of this column names are considered valid. - *

- * If no columns have been defined, all attributes will be considered valid. - * @param attributes the attributes - * @param validColumns the valid columns - * @return a List with the valid attributes - */ - public List getValidAttributes(final List attributes, List validColumns) { - List inputValidColumns = validColumns == null ? (List) Collections.EMPTY_LIST : validColumns; - final List validAttributes = new ArrayList<>(); - for (final Object ob : attributes) { - if ((ob instanceof String) || (ob instanceof DBFunctionName)) { - boolean isValid = true; - if (ob instanceof String) { - if (ExtendedSQLConditionValuesProcessor.EXPRESSION_KEY.equals(ob)) { - isValid = true; - } else if (!inputValidColumns.isEmpty() && !inputValidColumns.contains(ob)) { - isValid = false; - } else { - isValid = this.isColumnNameValid((String) ob); - } - } - if (isValid) { - validAttributes.add(ob); - } - } - } - return validAttributes; - } - - /** - * Checks if is column name valid. - * @param ob the ob - * @return true, if is column name valid - */ - protected boolean isColumnNameValid(String ob) { - boolean notValid = ob.contains(" ") || ob.contains("*"); - return !notValid; - } - - /** - * Returns a cleaned map containing the valid pairs of those included in the map - * inputKeysValues - *

- * If valid column names have been specified for this entity/query, only attributes matching - * (case-sensitive) any of this column names are considered valid. - *

- * If no columns have been defined, all attributes will be considered valid. - * - * Returns cleaned keys values to do query according valid columns (if defined). - * @param inputKeysValues - * @param validColumns - * @return - */ - protected Map getValidQueryingKeysValues(Map inputKeysValues, - List validColumns) { - if ((validColumns == null) || validColumns.isEmpty()) { - return inputKeysValues; - } - final Map hValidKeysValues = new HashMap<>(); - for (Entry entry : inputKeysValues.entrySet()) { - if (ExtendedSQLConditionValuesProcessor.EXPRESSION_KEY.equals(entry.getKey()) - || validColumns.contains(entry.getKey())) { - hValidKeysValues.put(entry.getKey(), entry.getValue()); - } - } - OntimizeJdbcDaoSupport.logger - .debug(" Query valid keys values: Input: " + inputKeysValues + " -> Result: " + hValidKeysValues); - return hValidKeysValues; - } - - /** - * Processes the MultipleValue objects contained in keysValues. Returns a new HashMap - * with the same data as keysValues except that MultipleValue objects are deleted and - * the key-value pairs of these objects are added to the new HashMap. - * @param keysValues the keys values - * @return a new HashMap with MultipleValue objects replaced by their key-value pairs - */ - public Map processMultipleValueAttributes(final Map keysValues) { - if (keysValues == null) { - return null; - } - final Map res = new HashMap<>(); - for (final Entry entry : keysValues.entrySet()) { - final Object oKey = entry.getKey(); - final Object oValue = entry.getValue(); - if (oValue instanceof MultipleValue) { - final Enumeration mvKeys = ((MultipleValue) oValue).keys(); - while (mvKeys.hasMoreElements()) { - final Object iMvKeyM = mvKeys.nextElement(); - final Object oMvValue = ((MultipleValue) oValue).get(iMvKeyM); - res.put(iMvKeyM, oMvValue); - } - } else { - res.put(oKey, oValue); - } - } - return res; - } - - /** - * Processes the keys in order to get String as column name. - * @param keysValues the keys values - * @return a new HashMap with MultipleValue objects replaced by their key-value pairs - */ - public Map processStringKeys(final Map keysValues) { - if (keysValues == null) { - return null; - } - final Map res = new HashMap<>(); - for (final Entry entry : keysValues.entrySet()) { - final Object oKey = entry.getKey(); - final Object oValue = entry.getValue(); - res.put(oKey.toString(), oValue); - } - return res; - } - - // ------------------------------------------------------------------------- - // Methods dealing with configuration properties - // ------------------------------------------------------------------------- - - /** - * Set the name of the table for this insert. - * @param tableName the new table name - */ - public void setTableName(final String tableName) { - this.checkIfConfigurationModificationIsAllowed(); - this.tableMetaDataContext.setTableName(tableName); - } - - /** - * Get the name of the table for this insert. - * @return the table name - */ - public String getTableName() { - this.checkCompiled(); - return this.tableMetaDataContext.getTableName(); - } - - /** - * Set the name of the schema for this insert. - * @param schemaName the new schema name - */ - public void setSchemaName(final String schemaName) { - this.checkIfConfigurationModificationIsAllowed(); - this.tableMetaDataContext.setSchemaName(StringTools.isEmpty(schemaName) ? null : schemaName); - } - - /** - * Get the name of the schema for this insert. - * @return the schema name - */ - public String getSchemaName() { - this.checkCompiled(); - return this.tableMetaDataContext.getSchemaName(); - } - - /** - * Set the name of the catalog for this insert. - * @param catalogName the new catalog name - */ - public void setCatalogName(final String catalogName) { - this.checkIfConfigurationModificationIsAllowed(); - this.tableMetaDataContext.setCatalogName(StringTools.isEmpty(catalogName) ? null : catalogName); - } - - /** - * Get the name of the catalog for this insert. - * @return the catalog name - */ - public String getCatalogName() { - this.checkCompiled(); - return this.tableMetaDataContext.getCatalogName(); - } - - /** - * Set the names of the columns to be used. - * @param columnNames the new column names - */ - public void setColumnNames(final List columnNames) { - this.checkIfConfigurationModificationIsAllowed(); - this.declaredColumns.clear(); - this.declaredColumns.addAll(columnNames); - } - - /** - * Get the names of the columns used. - * @return the column names - */ - public List getColumnNames() { - this.checkCompiled(); - return Collections.unmodifiableList(this.declaredColumns); - } - - /** - * Get the names of any generated keys. - * @return the generated key names - */ - public String[] getGeneratedKeyNames() { - this.checkCompiled(); - return this.generatedKeyNames; - } - - /** - * Set the names of any generated keys. - * @param generatedKeyNames the new generated key names - */ - public void setGeneratedKeyNames(final String[] generatedKeyNames) { - this.checkIfConfigurationModificationIsAllowed(); - this.generatedKeyNames = generatedKeyNames; - } - - /** - * Specify the name of a single generated key column. - * @param generatedKeyName the new generated key name - */ - public void setGeneratedKeyName(final String generatedKeyName) { - this.checkIfConfigurationModificationIsAllowed(); - if (generatedKeyName == null) { - this.generatedKeyNames = new String[] {}; - } else { - this.generatedKeyNames = new String[] { generatedKeyName }; - } - } - - /** - * Specify whether the parameter metadata for the call should be used. The default is true. - * @param accessTableColumnMetaData the new access table column meta data - */ - public void setAccessTableColumnMetaData(final boolean accessTableColumnMetaData) { - this.tableMetaDataContext.setAccessTableColumnMetaData(accessTableColumnMetaData); - } - - /** - * Specify whether the default for including synonyms should be changed. The default is false. - * @param override the new override include synonyms default - */ - public void setOverrideIncludeSynonymsDefault(final boolean override) { - this.tableMetaDataContext.setOverrideIncludeSynonymsDefault(override); - } - - - // ------------------------------------------------------------------------- - // Methods handling compilation issues - // ------------------------------------------------------------------------- - /** - * Compile this JdbcInsert using provided parameters and meta data plus other settings. This - * finalizes the configuration for this object and subsequent attempts to compile are ignored. This - * will be implicitly called the first time an un-compiled insert is executed. - * @throws InvalidDataAccessApiUsageException if the object hasn't been correctly initialized, for - * example if no DataSource has been provided - */ - public synchronized final void compile() throws InvalidDataAccessApiUsageException { - if (!this.isCompiled()) { - final ConfigurationFile annotation = this.getClass().getAnnotation(ConfigurationFile.class); - if (annotation != null) { - this.configurationFile = annotation.configurationFile(); - this.configurationFilePlaceholder = annotation.configurationFilePlaceholder(); - } - this.loadConfigurationFile(this.configurationFile, this.configurationFilePlaceholder); - - if (this.getJdbcTemplate() == null) { - throw new IllegalArgumentException("'dataSource' or 'jdbcTemplate' is required"); - } - - if (this.tableMetaDataContext.getTableName() == null) { - throw new InvalidDataAccessApiUsageException("Table name is required"); - } - try { - this.getJdbcTemplate().afterPropertiesSet(); - } catch (final IllegalArgumentException ex) { - throw new InvalidDataAccessApiUsageException(ex.getMessage(), ex); - } - this.compileInternal(); - this.compiled = true; - if (OntimizeJdbcDaoSupport.logger.isDebugEnabled()) { - OntimizeJdbcDaoSupport.logger.debug("JdbcInsert for table [{}] compiled", this.getTableName()); - } - } - } - - @Override - public void reload() { - OntimizeJdbcDaoSupport.logger.debug("dao {} - {} marked to recompile", this.getClass().getName(), - this.getTableName()); - this.compiled = false; - this.setTableName(null); - this.setSchemaName(null); - this.setCatalogName(null); - this.setDeleteKeys(null); - this.setUpdateKeys(null); - this.sqlQueries.clear(); - this.setGeneratedKeyName(null); - this.setStatementHandler(null); - this.setNameConverter(null); - } - - /** - * Load the configuration file. - * @param path the path - * @param pathToPlaceHolder the path to place holder - * @throws InvalidDataAccessApiUsageException the invalid data access api usage exception - */ - protected void loadConfigurationFile(final String path, final String pathToPlaceHolder) - throws InvalidDataAccessApiUsageException { - - try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);) { - Reader reader = null; - if (pathToPlaceHolder != null) { - try (InputStream isPlaceHolder = Thread.currentThread() - .getContextClassLoader() - .getResourceAsStream(pathToPlaceHolder);) { - final Properties prop = new Properties(); - if (isPlaceHolder != null) { - prop.load(isPlaceHolder); - } - - Map mapProperties = prop.stringPropertyNames() - .stream() - .collect(Collectors.toMap( - Function.identity(), - prop::getProperty - )); - reader = new ReplaceTokensFilterReader(new InputStreamReader(is), - mapProperties); - } - } else { - reader = new InputStreamReader(is); - } - - JdbcEntitySetupType baseSetup = JAXB.unmarshal(reader, JdbcEntitySetupType.class); - - // Support to Dao extensions - JdbcEntitySetupType setupConfig = this.checkDaoExtensions(baseSetup, path, pathToPlaceHolder); - - // Process setup information to configure dao - this.setTableName(setupConfig.getTable()); - this.setSchemaName(setupConfig.getSchema()); - this.setCatalogName(setupConfig.getCatalog()); - this.setDeleteKeys(setupConfig.getDeleteKeys().getColumn()); - this.setUpdateKeys(setupConfig.getUpdateKeys().getColumn()); - if (setupConfig.getQueries() != null) { - for (final QueryType query : setupConfig.getQueries().getQuery()) {// - this.addQueryTemplateInformation(query.getId(), query.getSentence().getValue(), // - query.getAmbiguousColumns() == null ? null - : query.getAmbiguousColumns().getAmbiguousColumn(), // - query.getFunctionColumns() == null ? null : query.getFunctionColumns().getFunctionColumn(), // - query.getValidColumns() != null ? query.getValidColumns().getColumn() - : new ArrayList(), // - query.getOrderColumns() == null ? null : query.getOrderColumns().getOrderColumn()); - } - } - this.setGeneratedKeyName(setupConfig.getGeneratedKey()); - this.setDataSource((DataSource) this.applicationContext.getBean(setupConfig.getDatasource())); - this.setStatementHandler( - (SQLStatementHandler) this.applicationContext.getBean(setupConfig.getSqlhandler())); - - final String nameConverter = setupConfig.getNameconverter(); - if (!CheckingTools.isStringEmpty(nameConverter)) { - this.setNameConverter((INameConverter) this.applicationContext.getBean(nameConverter)); - } - this.tableMetaDataContext.setNameConvention(this.nameConvention); - } catch (final IOException e) { - throw new InvalidDataAccessApiUsageException(I18NNaming.M_ERROR_LOADING_CONFIGURATION_FILE, e); - } - - } - - protected JdbcEntitySetupType checkDaoExtensions(JdbcEntitySetupType baseSetup, final String path, - final String pathToPlaceHolder) { - if (this.daoExtensionHelper == null) { - return baseSetup; - } - return this.daoExtensionHelper.checkDaoExtensions(baseSetup, path, pathToPlaceHolder); - } - - /** - * Sets the bean property converter. - * @param converter the new bean property converter - */ - protected void setNameConverter(final INameConverter converter) { - this.nameConverter = converter; - } - - /** - * Gets the bean property converter. - * @return the bean property converter - */ - public INameConverter getNameConverter() { - this.checkCompiled(); - return this.nameConverter; - } - - /** - * Sets the configuration file. - * @param configurationFile the new configuration file - */ - public synchronized void setConfigurationFile(final String configurationFile) { - this.configurationFile = configurationFile; - } - - /** - * Gets the configuration file. - * @return the configuration file - */ - public String getConfigurationFile() { - return this.configurationFile; - } - - /** - * Sets the configuration file placeholder. - * @param configurationFilePlaceholder the new configuration file placeholder - */ - public synchronized void setConfigurationFilePlaceholder(final String configurationFilePlaceholder) { - this.configurationFilePlaceholder = configurationFilePlaceholder; - } - - /** - * Gets the configuration file placeholder. - * @return the configuration file placeholder - */ - public String getConfigurationFilePlaceholder() { - return this.configurationFilePlaceholder; - } - - public INameConvention getNameConvention() { - return nameConvention; - } - - public void setNameConvention(INameConvention nameConvention) { - this.nameConvention = nameConvention; - } - - /** - * Check dao config. - */ - @Override - protected void checkDaoConfig() { - // no need of jdbctemplate at this point - } - - /** - * Adds a query. - * @param id the id - * @param value the value - * @param ambiguousColumns the ambiguous columns - * @param functionColumns the function columns - */ - public void addQueryTemplateInformation(final String id, final String value, - final List ambiguousColumns, final List functionColumns, - List orderColumns) { - this.addQueryTemplateInformation(id, value, ambiguousColumns, functionColumns, new ArrayList<>(), - orderColumns); - } - - /** - * Adds a query, allowing determine valid columns to query to DB. - * @param id - * @param value - * @param ambiguousColumns - * @param functionColumns - * @param validColumns - */ - public void addQueryTemplateInformation(final String id, final String value, - final List ambiguousColumns, final List functionColumns, - List validColumns, List orderColumns) { - this.sqlQueries.put(id, - new QueryTemplateInformation(value, ambiguousColumns, functionColumns, validColumns, orderColumns)); - } - - /** - * Gets the template query. - * @param id the id - * @return the template query - */ - public QueryTemplateInformation getQueryTemplateInformation(final String id) { - this.checkCompiled(); - return this.sqlQueries.get(id); - } - - /** - * Method to perform the actual compilation. Subclasses can override this template method to perform - * their own compilation. Invoked after this base class's compilation is complete. - */ - protected void compileInternal() { - this.tableMetaDataContext.processMetaData(this.getJdbcTemplate().getDataSource(), this.declaredColumns, - this.generatedKeyNames); - this.onCompileInternal(); - } - - /** - * Hook method that subclasses may override to react to compilation. This implementation does - * nothing. - */ - protected void onCompileInternal() { - // This implementation does nothing. - } - - /** - * Is this operation "compiled"?. - * @return whether this operation is compiled, and ready to use. - */ - public boolean isCompiled() { - return this.compiled; - } - - /** - * Check whether this operation has been compiled already; lazily compile it if not already - * compiled. - *

- * Automatically called by {@code validateParameters}. - */ - public void checkCompiled() { - if (!this.isCompiled()) { - OntimizeJdbcDaoSupport.logger.debug("JdbcInsert not compiled before execution - invoking compile"); - this.compile(); - } - } - - /** - * Method to check whether we are allowd to make any configuration changes at this time. If the - * class has been compiled, then no further changes to the configuration are allowed. - */ - protected void checkIfConfigurationModificationIsAllowed() { - if (this.isCompiled()) { - throw new InvalidDataAccessApiUsageException( - "Configuration can't be altered once the class has been compiled or used"); - } - } - - // ------------------------------------------------------------------------- - // Methods handling execution - // ------------------------------------------------------------------------- - - /** - * Method that provides execution of the insert using the passed in Map of parameters. - * @param args Map with parameter names and values to be used in insert - * @return number of rows affected - */ - protected int doExecuteInsert(final Map args) { - this.checkCompiled(); - final InsertMetaInfoHolder holder = this.matchInParameterValuesWithInsertColumns(args); - return this.executeInsertInternal(holder); - } - - /** - * Method that provides execution of the insert using the passed in. - * @param parameterSource parameter names and values to be used in insert - * @return number of rows affected {@link SqlParameterSource} - */ - protected int doExecuteInsert(final SqlParameterSource parameterSource) { - this.checkCompiled(); - final InsertMetaInfoHolder holder = this.matchInParameterValuesWithInsertColumns(parameterSource); - return this.executeInsertInternal(holder); - } - - /** - * Method to execute the insert. - * @param values the values - * @return the int - */ - protected int executeInsertInternal(InsertMetaInfoHolder holder) { - OntimizeJdbcDaoSupport.logger.debug("The following parameters are used for insert {} with: {}", - holder.getInsertString(), holder.getValues()); - return this.getJdbcTemplate() - .update(holder.getInsertString(), holder.getValues().toArray(), holder.getInsertTypes()); - } - - /** - * Method that provides execution of the insert using the passed in Map of parameters and returning - * a generated key. - * @param args Map with parameter names and values to be used in insert - * @return the key generated by the insert - */ - protected Object doExecuteInsertAndReturnKey(final Map args) { - this.checkCompiled(); - final InsertMetaInfoHolder holder = this.matchInParameterValuesWithInsertColumns(args); - return this.executeInsertAndReturnKeyInternal(holder); - } - - /** - * Method that provides execution of the insert using the passed in. - * @param parameterSource parameter names and values to be used in insert - * @return the key generated by the insert {@link SqlParameterSource} and returning a generated key - */ - protected Object doExecuteInsertAndReturnKey(final SqlParameterSource parameterSource) { - this.checkCompiled(); - final InsertMetaInfoHolder holder = this.matchInParameterValuesWithInsertColumns(parameterSource); - return this.executeInsertAndReturnKeyInternal(holder); - } - - /** - * Method that provides execution of the insert using the passed in Map of parameters and returning - * all generated keys. - * @param args Map with parameter names and values to be used in insert - * @return the KeyHolder containing keys generated by the insert - */ - protected KeyHolder doExecuteInsertAndReturnKeyHolder(final Map args) { - this.checkCompiled(); - final InsertMetaInfoHolder holder = this.matchInParameterValuesWithInsertColumns(args); - return this.executeInsertAndReturnKeyHolderInternal(holder); - } - - /** - * Method that provides execution of the insert using the passed in. - * @param parameterSource parameter names and values to be used in insert - * @return the KeyHolder containing keys generated by the insert {@link SqlParameterSource} and - * returning all generated keys - */ - protected KeyHolder doExecuteInsertAndReturnKeyHolder(final SqlParameterSource parameterSource) { - this.checkCompiled(); - final InsertMetaInfoHolder holder = this.matchInParameterValuesWithInsertColumns(parameterSource); - return this.executeInsertAndReturnKeyHolderInternal(holder); - } - - /** - * Method to execute the insert generating single key. - * @param values the values - * @return the number - */ - protected Object executeInsertAndReturnKeyInternal(final InsertMetaInfoHolder holder) { - final KeyHolder kh = this.executeInsertAndReturnKeyHolderInternal(holder); - if ((kh != null) && (kh.getKeyAs(Object.class) != null)) { - return kh.getKeyAs(Object.class); - } - throw new DataIntegrityViolationException( - "Unable to retrieve the generated key for the insert: " + holder.getInsertString()); - } - - /** - * Method to execute the insert generating any number of keys. - * @param values the values - * @return the key holder - */ - protected KeyHolder executeInsertAndReturnKeyHolderInternal(final InsertMetaInfoHolder holder) { - OntimizeJdbcDaoSupport.logger.debug("The following parameters are used for call {} with: {}", - holder.getInsertString(), holder.getValues()); - final KeyHolder keyHolder = new GeneratedKeyHolder(); - if (!this.tableMetaDataContext.isGetGeneratedKeysSupported()) { - if (!this.tableMetaDataContext.isGetGeneratedKeysSimulated()) { - throw new InvalidDataAccessResourceUsageException( - "The getGeneratedKeys feature is not supported by this database"); - } - if (this.getGeneratedKeyNames().length < 1) { - throw new InvalidDataAccessApiUsageException( - "Generated Key Name(s) not specificed. " - + "Using the generated keys features requires specifying the name(s) of the generated column(s)"); - } - if (this.getGeneratedKeyNames().length > 1) { - throw new InvalidDataAccessApiUsageException( - "Current database only supports retreiving the key for a single column. There are " - + this.getGeneratedKeyNames().length + " columns specified: " + Arrays - .asList(this.getGeneratedKeyNames())); - } - - // This is a hack to be able to get the generated key from a - // database that doesn't support - // get generated keys feature. HSQL is one, PostgreSQL is another. - // Postgres uses a RETURNING - // clause while HSQL uses a second query that has to be executed - // with the same connection. - - String tableName = this.tableMetaDataContext.getTableName() != null ? this.tableMetaDataContext.getTableName() : null; + /** + * The logger. + */ + protected static final Logger logger = LoggerFactory.getLogger(OntimizeJdbcDaoSupport.class); + + /** + * The Constant PLACEHOLDER_ORDER. + */ + protected static final String PLACEHOLDER_ORDER = "#ORDER#"; + + /** + * The Constant PLACEHOLDER_ORDER_CONCAT. + */ + protected static final String PLACEHOLDER_ORDER_CONCAT = "#ORDER_CONCAT#"; + + /** + * The Constant PLACEHOLDER_WHERE. + */ + protected static final String PLACEHOLDER_WHERE = "#WHERE#"; + + /** + * The Constant PLACEHOLDER_WHERE_CONCAT. + */ + protected static final String PLACEHOLDER_WHERE_CONCAT = "#WHERE_CONCAT#"; + + /** + * The Constant PLACEHOLDER_COLUMNS. + */ + protected static final String PLACEHOLDER_COLUMNS = "#COLUMNS#"; + + /** + * The Constant PLACEHOLDER_SCHEMA. + */ + protected static final String PLACEHOLDER_SCHEMA = "#SCHEMA#"; + + /** + * Context used to retrieve and manage database metadata. + */ + protected final OntimizeTableMetaDataContext tableMetaDataContext; + + /** + * List of columns objects to be used in insert statement. + */ + protected final List declaredColumns = new ArrayList<>(); + /** + * Queries. + */ + protected final Map sqlQueries = new HashMap<>(); + /** + * Has this operation been compiled? Compilation means at least checking that a DataSource or + * JdbcTemplate has been provided, but subclasses may also implement their own custom validation. + */ + private boolean compiled = false; + private String[] generatedKeyNames = {}; + /** + * The statement builder. + */ + private SQLStatementHandler statementHandler; + /** + * The bean property converter. + */ + private INameConverter nameConverter; + /** + * Mandatory delete keys. + */ + private List deleteKeys; + /** + * Mandatory update keys. + */ + private List updateKeys; + /** + * The application context. + */ + private ApplicationContext applicationContext; + + /** + * Configuration file + */ + private String configurationFile = null; + + /** + * Configuration file placeholder + */ + private String configurationFilePlaceholder = null; + + /** + * Name convention + */ + @Autowired + private INameConvention nameConvention; + + /** + * Dao Extension helper. + */ + @Autowired(required = false) + private IDaoExtensionHelper daoExtensionHelper; + + /** + * Instantiates a new ontimize jdbc dao support. + */ + public OntimizeJdbcDaoSupport() { + super(); + this.tableMetaDataContext = this.createTableMetadataContext(); + } + + public OntimizeJdbcDaoSupport(final String configurationFile, final String configurationFilePlaceholder) { + this(); + this.configurationFile = configurationFile; + this.configurationFilePlaceholder = configurationFilePlaceholder; + } + + @Override + public EntityResult query(Map keysValues, List attributes, List sort, String queryId) { + return this.query(keysValues, attributes, sort, queryId, null); + } + + /** + * Query. + * + * @param keysValues the keys values + * @param attributes the attributes + * @param sort the sort + * @param queryId the query id + * @return the entity result + */ + @Override + public EntityResult query(final Map keysValues, final List attributes, final List sort, final String queryId, ISQLQueryAdapter queryAdapter) { + this.checkCompiled(); + final QueryTemplateInformation queryTemplateInformation = this.getQueryTemplateInformation(queryId); + + final SQLStatementBuilder.SQLStatement stSQL = this.composeQuerySql(queryId, attributes, keysValues, sort, null, queryAdapter); + + final String sqlQuery = stSQL.getSQLStatement(); + final List vValues = stSQL.getValues(); + // TODO los atributos que se pasan al entityresultsetextractor tienen que ir "desambiguados" porque + // cuando el DefaultSQLStatementHandler busca + // las columnas toUpperCase y toLowerCase no tiene en cuenta el '.' + Chronometer chrono = new Chronometer().start(); + try { + + JdbcTemplate jdbcTemplate = this.getJdbcTemplate(); + + if (jdbcTemplate != null) { + + ArgumentPreparedStatementSetter pss = new ArgumentPreparedStatementSetter(vValues.toArray()); + + return jdbcTemplate.query(sqlQuery, pss, new EntityResultResultSetExtractor(this.getStatementHandler(), queryTemplateInformation, attributes)); + } + + return new EntityResultMapImpl(EntityResult.OPERATION_WRONG, EntityResult.NODATA_RESULT); + + } finally { + OntimizeJdbcDaoSupport.logger.trace("Time consumed in query+result= {} ms", chrono.stopMs()); + } + } + + @Override + public AdvancedEntityResult paginationQuery(Map keysValues, List attributes, int recordNumber, int startIndex, List orderBy, String queryId) { + return this.paginationQuery(keysValues, attributes, recordNumber, startIndex, orderBy, queryId, null); + } + + /** + * Pageable query. + * + * @param keysValues the keys values + * @param attributes the attributes + * @param recordNumber number of records to query + * @param startIndex number of first row + * @param orderBy list of columns to establish the order + * @param queryId the query id + * @return the entity result + */ + @Override + public AdvancedEntityResult paginationQuery(Map keysValues, List attributes, int recordNumber, int startIndex, List orderBy, String queryId, ISQLQueryAdapter queryAdapter) { + this.checkCompiled(); + final QueryTemplateInformation queryTemplateInformation = this.getQueryTemplateInformation(queryId); + final SQLStatementBuilder.SQLStatement stSQL = this.composeQuerySql(queryId, attributes, keysValues, orderBy, new PageableInfo(recordNumber, startIndex), queryAdapter); + final String sqlQuery = stSQL.getSQLStatement(); + final List vValues = stSQL.getValues(); + + ArgumentPreparedStatementSetter pss = new ArgumentPreparedStatementSetter(vValues.toArray()); + + JdbcTemplate jdbcTemplate = this.getJdbcTemplate(); + + if (jdbcTemplate != null) { + AdvancedEntityResult advancedER = jdbcTemplate.query(new SimpleScrollablePreparedStatementCreator(sqlQuery), pss, new AdvancedEntityResultResultSetExtractor(this.getStatementHandler(), queryTemplateInformation, attributes, recordNumber, startIndex)); + + advancedER.setTotalRecordCount(this.getQueryRecordNumber(keysValues, queryId)); + return advancedER; + + } + + return new AdvancedEntityResultMapImpl(EntityResult.OPERATION_WRONG, EntityResult.NODATA_RESULT); + + } + + protected int getQueryRecordNumber(Map keysValues, final String queryId) { + final QueryTemplateInformation queryTemplateInformation = this.getQueryTemplateInformation(queryId); + final Map kvWithoutReferenceAttributes = this.processReferenceDataFieldAttributes(keysValues); + Map kvValidKeysValues = new HashMap<>(); + final Map processMultipleValueAttributes = this.processMultipleValueAttributes(kvWithoutReferenceAttributes); + if (processMultipleValueAttributes != null) { + kvValidKeysValues.putAll(processMultipleValueAttributes); + } + + SQLStatementBuilder.SQLStatement stSQL = null; + + if (queryTemplateInformation != null) { + List validColumns = queryTemplateInformation.getValidColumns(); + kvValidKeysValues = this.getValidQueryingKeysValues(kvValidKeysValues, validColumns); + + kvValidKeysValues = this.applyTransformations(queryTemplateInformation, kvValidKeysValues); + + String sqlTemplate = queryTemplateInformation.getSqlTemplate().replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_COLUMNS, SQLStatementBuilder.COUNT); + // Where + final List vValues = new ArrayList<>(); + String cond = this.getStatementHandler().createQueryConditionsWithoutWhere(kvValidKeysValues, new ArrayList<>(), vValues); + if (cond == null) { + cond = ""; + } + cond = cond.trim(); + + List vValuesTemp = new ArrayList<>(); + vValuesTemp.addAll(vValues); + + sqlTemplate = this.applyWherePlaceholders(sqlTemplate, cond, vValues, vValuesTemp); + + sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_SCHEMA, this.getSchemaName()); + sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_ORDER_CONCAT, ""); + sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_ORDER, ""); + stSQL = new SQLStatementBuilder.SQLStatement(sqlTemplate, vValues); + } else { + stSQL = this.getStatementHandler().createCountQuery(this.getSchemaTable(), new HashMap<>(kvValidKeysValues), new ArrayList<>(), new ArrayList<>()); + } + + String sqlQuery = stSQL.getSQLStatement(); + List vValues = stSQL.getValues(); + EntityResult erResult = this.getJdbcTemplate().query(sqlQuery, new EntityResultResultSetExtractor(this.getStatementHandler(), queryTemplateInformation), vValues.toArray()); + + if ((erResult == null) || (erResult.getCode() == EntityResult.OPERATION_WRONG)) { + OntimizeJdbcDaoSupport.logger.error("Error executed record count query:{} : {}", erResult.getMessage(), sqlQuery); + return 0; + } + + List v = (List) erResult.get(SQLStatementBuilder.COUNT_COLUMN_NAME); + if ((v == null) || v.isEmpty()) { + OntimizeJdbcDaoSupport.logger.error("Error executed record cound query. The result not contain record number."); + return 0; + } + return ((Number) v.get(0)).intValue(); + } + + protected String performPlaceHolderPagination(String sqlTemplate, PageableInfo pageableInfo) { + if (pageableInfo == null) { + return sqlTemplate; + } + return this.getStatementHandler().convertPaginationStatement(sqlTemplate, pageableInfo.getStartIndex(), pageableInfo.getRecordNumber()); + } + + /** + * Compose sql. + * + * @param queryId the query id + * @param attributes the attributes + * @param keysValues the keys values + * @param sort the sort + * @return the SQL statement + */ + public SQLStatementBuilder.SQLStatement composeQuerySql(final String queryId, final List attributes, final Map keysValues, final List sort, PageableInfo pageableInfo, ISQLQueryAdapter queryAdapter) { + final QueryTemplateInformation queryTemplateInformation = this.getQueryTemplateInformation(queryId); + + final Map kvWithoutReferenceAttributes = this.processReferenceDataFieldAttributes(keysValues); + Map kvValidKeysValues = new HashMap<>(); + final Map processMultipleValueAttributes = this.processMultipleValueAttributes(kvWithoutReferenceAttributes); + if (processMultipleValueAttributes != null) { + kvValidKeysValues.putAll(processMultipleValueAttributes); + } + + List vValidAttributes = this.processReferenceDataFieldAttributes(attributes); + + SQLStatementBuilder.SQLStatement stSQL = null; + if (queryTemplateInformation == null) { + vValidAttributes = this.getValidAttributes(vValidAttributes, null); + CheckingTools.failIf(vValidAttributes.isEmpty(), "NO_ATTRIBUTES_TO_QUERY"); + // use table + if (pageableInfo != null) { + stSQL = this.getStatementHandler().createSelectQuery(this.getSchemaTable(), new ArrayList<>(vValidAttributes), new HashMap<>(kvValidKeysValues), new ArrayList<>(), new ArrayList<>(sort == null ? Collections.emptyList() : sort), pageableInfo.getRecordNumber(), pageableInfo.getStartIndex()); + } else { + stSQL = this.getStatementHandler().createSelectQuery(this.getSchemaTable(), new ArrayList<>(vValidAttributes), new HashMap<>(kvValidKeysValues), new ArrayList<>(), new ArrayList<>(sort == null ? Collections.emptyList() : sort)); + } + + } else { + List validColumns = queryTemplateInformation.getValidColumns(); + kvValidKeysValues = this.getValidQueryingKeysValues(kvValidKeysValues, validColumns); + vValidAttributes = this.getValidAttributes(vValidAttributes, validColumns); + + kvValidKeysValues = this.applyTransformations(queryTemplateInformation, kvValidKeysValues); + vValidAttributes = this.applyTransformations(queryTemplateInformation, vValidAttributes); + CheckingTools.failIf(vValidAttributes.isEmpty(), "NO_ATTRIBUTES_TO_QUERY"); + final StringBuilder sbColumns = new StringBuilder(); + // columns + for (final Object ob : vValidAttributes) { + sbColumns.append(ob.toString()); + sbColumns.append(SQLStatementBuilder.COMMA); + } + for (int i = 0; i < SQLStatementBuilder.COMMA.length(); i++) { + sbColumns.deleteCharAt(sbColumns.length() - 1); + } + String sqlTemplate = queryTemplateInformation.getSqlTemplate().replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_COLUMNS, sbColumns.toString()); + + // Where + final List vValues = new ArrayList<>(); + String cond = this.getStatementHandler().createQueryConditionsWithoutWhere(kvValidKeysValues, new ArrayList<>(), vValues); + if (cond == null) { + cond = ""; + } + cond = cond.trim(); + + List vValuesTemp = new ArrayList<>(); + vValuesTemp.addAll(vValues); + + sqlTemplate = this.applyWherePlaceholders(sqlTemplate, cond, vValues, vValuesTemp); + + // Order by + List orderColumns = queryTemplateInformation.getOrderColumns(); + List sortColumns = this.applyOrderColumns(sort, orderColumns); + String order = this.getStatementHandler().createSortStatement(sortColumns, false); + if (order.length() > 0) { + order = order.substring(SQLStatementBuilder.ORDER_BY.length()); + } + order = order.trim(); + + sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_ORDER_CONCAT, order.length() == 0 ? "" : SQLStatementBuilder.COMMA + " " + order); + sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_ORDER, order.length() == 0 ? "" : SQLStatementBuilder.ORDER_BY + " " + order); + if (pageableInfo != null) { + sqlTemplate = this.performPlaceHolderPagination(sqlTemplate, pageableInfo); + } + sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_SCHEMA, this.getSchemaName()); + stSQL = new SQLStatementBuilder.SQLStatement(sqlTemplate, vValues); + } + if (queryAdapter != null) { + stSQL = queryAdapter.adaptQuery(stSQL, this, keysValues, kvValidKeysValues, attributes, vValidAttributes, sort, queryId); + } + OntimizeJdbcDaoSupport.logger.trace(stSQL.getSQLStatement()); + return stSQL; + } + + /** + * Replaces the WHERE placeholders in the SQL template and updates the values. + * + * @param sqlTemplate The original SQL template. + * @param cond The generated condition. + * @param vValues The list of values to be updated. + * @param vValuesTemp Temporary list of condition values. + * @return The modified sqlTemplate with the placeholders replaced. + */ + public String applyWherePlaceholders(String sqlTemplate, String cond, List vValues, List vValuesTemp) { + Pair replaceAll = StringTools.replaceAll(sqlTemplate, OntimizeJdbcDaoSupport.PLACEHOLDER_WHERE_CONCAT, cond.length() == 0 ? "" : SQLStatementBuilder.AND + " " + cond); + sqlTemplate = replaceAll.getFirst(); + for (int i = 1; i < replaceAll.getSecond(); i++) { + vValues.addAll(vValuesTemp); + } + + replaceAll = StringTools.replaceAll(sqlTemplate, OntimizeJdbcDaoSupport.PLACEHOLDER_WHERE, cond.length() == 0 ? "" : SQLStatementBuilder.WHERE + " " + cond); + sqlTemplate = replaceAll.getFirst(); + for (int i = 1; i < replaceAll.getSecond(); i++) { + vValues.addAll(vValuesTemp); + } + return sqlTemplate; + } + + @Override + public List query(final Map keysValues, final List sort, final String queryId, final Class clazz) { + return this.query(keysValues, sort, queryId, clazz, null); + } + + /** + * Query. + * + * @param the generic type + * @param keysValues the keys values + * @param sort the sort + * @param queryId the query id + * @param clazz the clazz + * @return the list + */ + @Override + public List query(final Map keysValues, final List sort, final String queryId, final Class clazz, ISQLQueryAdapter queryAdapter) { + this.checkCompiled(); + BeanPropertyRowMapper rowMapper = this.createRowMapper(clazz); + final SQLStatementBuilder.SQLStatement stSQL = this.composeQuerySql(queryId, rowMapper.convertBeanPropertiesToDB(clazz), keysValues, sort, null, queryAdapter); + final String sqlQuery = stSQL.getSQLStatement(); + final List vValues = stSQL.getValues(); + return this.getJdbcTemplate().query(sqlQuery, rowMapper, vValues.toArray()); + } + + /** + * Executes a single SQL statement using a prepared statement with the given parameters. + * + *

This method is intended for executing a single SQL command (e.g., CREATE, DROP, UPDATE), and supports parameter + * substitution using placeholders.

+ * + *

Restrictions: + *

    + *
  • Only one SQL statement can be executed per call. Do not pass multiple statements separated by semicolons.
  • + *
  • Dynamic WHERE clauses not working with DLL statements.
  • + *
  • This method does not return results.
  • + *
+ *

+ * + * @param sqlStatement The SQL statement to execute. + * @param vValues The list of values for the prepared statement. + */ + public boolean executeComposeSQLStatement(String sqlStatement, List vValues) { + Chronometer chrono = new Chronometer().start(); + try { + JdbcTemplate jdbcTemplate = this.getJdbcTemplate(); + if (jdbcTemplate != null) { + jdbcTemplate.execute((ConnectionCallback) con -> { + PreparedStatement ps = con.prepareStatement(sqlStatement); + ArgumentPreparedStatementSetter pss = new ArgumentPreparedStatementSetter(vValues.toArray()); + pss.setValues(ps); + return ps.execute(); + }); + } + return false; + } finally { + OntimizeJdbcDaoSupport.logger.trace("Time consumed in statement= {} ms", chrono.stopMs()); + } + } + + /** + * Creates the row mapper. + * + * @param the generic type + * @param clazz the clazz + * @return the bean property row mapper + */ + protected BeanPropertyRowMapper createRowMapper(final Class clazz) { + return new BeanPropertyRowMapper<>(this.getNameConverter(), this.getDataSource(), clazz); + } + + /** + * Apply template prefix. + * + * @param templateInformation the template information + * @param vValidAttributes the v valid attributes + * @return the list + */ + protected List applyTransformations(final QueryTemplateInformation templateInformation, final List vValidAttributes) { + final List ambiguousColumns = templateInformation.getAmbiguousColumns(); + final List functionColumns = templateInformation.getFunctionColumns(); + + final List res = new ArrayList<>(vValidAttributes.size()); + for (final Object ob : vValidAttributes) { + boolean transformed = false; + if (ambiguousColumns != null) { + for (final AmbiguousColumnType ambiguosColumn : ambiguousColumns) { + if (ob.toString().equalsIgnoreCase(ambiguosColumn.getName())) { + final String dbName = ambiguosColumn.getDatabaseName() == null ? ambiguosColumn.getName() : ambiguosColumn.getDatabaseName(); + String sb = ambiguosColumn.getPrefix() + "." + dbName + SQLStatementBuilder.AS + ambiguosColumn.getName(); + res.add(sb); + transformed = true; + break; + } + } + } + if (!transformed && (functionColumns != null)) { + for (final FunctionColumnType functionColumn : functionColumns) { + if (ob.toString().equalsIgnoreCase(functionColumn.getName())) { + String sb = SQLStatementBuilder.OPEN_PARENTHESIS + functionColumn.getValue() + SQLStatementBuilder.CLOSE_PARENTHESIS + SQLStatementBuilder.AS + functionColumn.getName(); + res.add(sb); + transformed = true; + break; + } + } + } + if (!transformed) { + res.add(ob); + } + } + return res; + } + + /** + * Apply template prefix. + * + * @param templateInformation the template information + * @param kvValidKeysValues the kv valid keys values + * @return the Map + */ + protected Map applyTransformations(final QueryTemplateInformation templateInformation, final Map kvValidKeysValues) { + final List ambiguousColumns = templateInformation.getAmbiguousColumns(); + final List functionColumns = templateInformation.getFunctionColumns(); + + final Map res = new HashMap<>(); + for (final Map.Entry kvEntry : kvValidKeysValues.entrySet()) { + if (kvEntry.getKey() instanceof String) { + String key = (String) kvEntry.getKey(); + boolean transformed = false; + if ((SQLStatementBuilder.ExtendedSQLConditionValuesProcessor.EXPRESSION_KEY.equals(key) || SQLStatementBuilder.ExtendedSQLConditionValuesProcessor.FILTER_KEY.equals(key)) && (kvEntry.getValue() instanceof SQLStatementBuilder.BasicExpression)) { + res.put(key, this.applyTransformationsToBasicExpression((SQLStatementBuilder.BasicExpression) kvEntry.getValue(), ambiguousColumns, functionColumns)); + transformed = true; + } else { + String resolvedAmbiguousColumn = this.resolveAmbiguousColumn(key, ambiguousColumns); + if (resolvedAmbiguousColumn != null) { + res.put(resolvedAmbiguousColumn, kvEntry.getValue()); + transformed = true; + } else { + String resolvedFunctionColumn = this.resolveFunctionColumn(key, functionColumns); + if (resolvedFunctionColumn != null) { + res.put(resolvedFunctionColumn, kvEntry.getValue()); + transformed = true; + } + } + } + if (!transformed) { + res.put(key, kvEntry.getValue()); + } + } else { + res.put(kvEntry.getKey(), kvEntry.getValue()); + } + } + return res; + } + + protected List applyOrderColumns(final List sort, final List orderColumns) { + List vResult = new ArrayList<>(); + if ((sort != null) && (sort.size() > 0)) { + vResult.addAll(sort); + } + + if ((orderColumns != null) && (orderColumns.size() > 0)) { + for (OrderColumnType orderColumnType : orderColumns) { + SQLStatementBuilder.SQLOrder sqlOrder = new SQLStatementBuilder.SQLOrder(orderColumnType.getName(), "ASC".equals(orderColumnType.getType())); + vResult.add(sqlOrder); + } + } + + return vResult; + } + + /** + * Resolve function column. + * + * @param key the key + * @param functionColumns the function columns + * @return the string + */ + protected String resolveFunctionColumn(String key, List functionColumns) { + if (functionColumns != null) { + for (final FunctionColumnType functionColumn : functionColumns) { + if (key.equalsIgnoreCase(functionColumn.getName())) { + return functionColumn.getValue(); + } + } + } + return null; + } + + /** + * Resolve ambiguous column. + * + * @param key the key + * @param ambiguousColumns the ambiguous columns + * @return the string + */ + protected String resolveAmbiguousColumn(String key, List ambiguousColumns) { + if (ambiguousColumns != null) { + for (final AmbiguousColumnType ambiguosColumn : ambiguousColumns) { + if (key.equalsIgnoreCase(ambiguosColumn.getName())) { + final String dbName = ambiguosColumn.getDatabaseName() == null ? key : ambiguosColumn.getDatabaseName(); + return ambiguosColumn.getPrefix() + "." + dbName; + } + } + } + return null; + } + + /** + * Apply transformations to basic expression. + * + * @param functionColumns + * @param ambiguousColumns + * @param value the value + * @return the object + */ + protected SQLStatementBuilder.BasicExpression applyTransformationsToBasicExpression(final SQLStatementBuilder.BasicExpression original, List ambiguousColumns, List functionColumns) { + Object originalLeftOperand = original.getLeftOperand(); + SQLStatementBuilder.Operator originalOperator = original.getOperator(); + Object originalRightOperand = original.getRightOperand(); + Object transformedLeftOperand = null; + SQLStatementBuilder.Operator transformedOperator = originalOperator; + Object transformedRightOperand = null; + if (originalLeftOperand instanceof SQLStatementBuilder.BasicField) { + transformedLeftOperand = this.applyTransformationsToBasicField((SQLStatementBuilder.BasicField) originalLeftOperand, ambiguousColumns, functionColumns); + } else if (originalLeftOperand instanceof SQLStatementBuilder.BasicExpression) { + transformedLeftOperand = this.applyTransformationsToBasicExpression((SQLStatementBuilder.BasicExpression) originalLeftOperand, ambiguousColumns, functionColumns); + } else { + transformedLeftOperand = originalLeftOperand; + } + + if (originalRightOperand instanceof SQLStatementBuilder.BasicField) { + transformedRightOperand = this.applyTransformationsToBasicField((SQLStatementBuilder.BasicField) originalRightOperand, ambiguousColumns, functionColumns); + } else if (originalRightOperand instanceof SQLStatementBuilder.BasicExpression) { + transformedRightOperand = this.applyTransformationsToBasicExpression((SQLStatementBuilder.BasicExpression) originalRightOperand, ambiguousColumns, functionColumns); + } else { + transformedRightOperand = originalRightOperand; + } + + return new SQLStatementBuilder.BasicExpression(transformedLeftOperand, transformedOperator, transformedRightOperand); + } + + /** + * Apply transformations to basic field. + * + * @param originalField the original field + * @param ambiguousColumns the ambiguous columns + * @param functionColumns the function columns + * @return the basic field + */ + protected SQLStatementBuilder.BasicField applyTransformationsToBasicField(SQLStatementBuilder.BasicField originalField, List ambiguousColumns, List functionColumns) { + String columnName = originalField.toString(); + Integer columnType = originalField.getSqlType(); + if (columnType == null) columnType = this.getColumnSQLType(columnName); + String resolvedAmbiguousColumn = this.resolveAmbiguousColumn(columnName, ambiguousColumns); + if (resolvedAmbiguousColumn != null) { + return new SQLStatementBuilder.BasicField(resolvedAmbiguousColumn, columnType); + } + String resolvedFunctionColumn = this.resolveFunctionColumn(columnName, functionColumns); + if (resolvedFunctionColumn != null) { + return new SQLStatementBuilder.BasicField(resolvedFunctionColumn, columnType); + } + return new SQLStatementBuilder.BasicField(columnName, columnType); + } + + /** + * Insert. + * + * @param attributesValues the attributes values + * @return the entity result + */ + @Override + public EntityResult insert(final Map attributesValues) { + this.checkCompiled(); + final EntityResult erResult = new EntityResultMapImpl(); + + final Map avWithoutMultipleTableAttributes = this.processMultipleTableAttribute(attributesValues); + final Map avWithoutReferenceAttributes = this.processReferenceDataFieldAttributes(avWithoutMultipleTableAttributes); + final Map avWithoutMultipleValueAttributes = this.processMultipleValueAttributes(avWithoutReferenceAttributes); + final Map avValidPre = this.getValidAttributes(this.processStringKeys(avWithoutMultipleValueAttributes)); + final Map avValid = this.removeNullValues(avValidPre); + if (avValid.isEmpty()) { + // TODO se deber�a lanzar excepci�n, pero puede tener colaterales con la one-2-one + OntimizeJdbcDaoSupport.logger.warn("Insert: Attributes does not contain any pair key-value valid"); + return erResult; + } + + if (this.getGeneratedKeyNames().length < 1) { + final int res = this.doExecuteInsert(avValid); + if (res != 1) { + throw new SQLWarningException(I18NNaming.M_IT_HAS_NOT_CHANGED_ANY_RECORD, null); + } + } else if (this.getGeneratedKeyNames().length == 1) { + final Object res = this.doExecuteInsertAndReturnKey(avValid); + if (res == null) { + throw new DataRetrievalFailureException(I18NNaming.M_IT_HAS_NOT_CHANGED_ANY_RECORD); + } + erResult.put(this.nameConvention.convertName(this.getGeneratedKeyNames()[0]), res); + } + return erResult; + } + + /* + * (non-Javadoc) + * + * @see com.ontimize.jee.server.entity.IOntimizeDaoSupport#insert(java.util.Map) + */ + + /** + * Removes the null values. + * + * @param inputAttributesValues the input attributes values + * @return the map + */ + protected Map removeNullValues(Map inputAttributesValues) { + final Map hValidKeysValues = new HashMap<>(); + for (final Map.Entry entry : inputAttributesValues.entrySet()) { + final String oKey = entry.getKey(); + final Object oValue = entry.getValue(); + if ((oValue != null) && !(oValue instanceof NullValue)) { + hValidKeysValues.put(oKey, oValue); + } + } + return hValidKeysValues; + } + + /** + * Unsafe update. + * + * @param attributesValues the attributes values + * @param keysValues the keys values + * @return the entity result + */ + @Override + public EntityResult unsafeUpdate(final Map attributesValues, final Map keysValues) { + return this.innerUpdate(attributesValues, keysValues, false); + } + + /* + * (non-Javadoc) + * + * @see com.ontimize.jee.server.entity.IOntimizeDaoSupport#unsafeUpdate(java.util .Map, + * java.util.Map) + */ + + /** + * Update. + * + * @param attributesValues the attributes values + * @param keysValues the keys values + * @return the entity result + */ + @Override + public EntityResult update(final Map attributesValues, final Map keysValues) { + return this.innerUpdate(attributesValues, keysValues, true); + } + + /* + * (non-Javadoc) + * + * @see com.ontimize.jee.server.entity.IOntimizeDaoSupport#update(java.util.Map, java.util.Map) + */ + + /** + * Inner update. + * + * @param attributesValues the attributes values + * @param keysValues the keys values + * @param safe the safe + * @return the entity result + */ + protected EntityResult innerUpdate(final Map attributesValues, final Map keysValues, final boolean safe) { + this.checkCompiled(); + final EntityResult erResult = new EntityResultMapImpl(); + + // Check the primary keys + final Map avWithoutMultipleTableAttributes = this.processMultipleTableAttribute(attributesValues); + final Map avWithoutReferenceAttributes = this.processReferenceDataFieldAttributes(avWithoutMultipleTableAttributes); + final Map avWithoutMultipleValue = this.processMultipleValueAttributes(avWithoutReferenceAttributes); + + final Map kvWithoutMulpleTableAttributes = this.processMultipleTableAttribute(keysValues); + final Map kvWithoutReferenceAttributessRef = this.processReferenceDataFieldAttributes(kvWithoutMulpleTableAttributes); + + final Map hValidAttributesValues = this.getValidAttributes(avWithoutMultipleValue); + Map hValidKeysValues = null; + if (safe) { + hValidKeysValues = this.getValidUpdatingKeysValues(kvWithoutReferenceAttributessRef); + this.checkUpdateKeys(hValidKeysValues); + } else { + hValidKeysValues = kvWithoutReferenceAttributessRef; + } + + if (hValidAttributesValues.isEmpty() || hValidKeysValues.isEmpty()) { + OntimizeJdbcDaoSupport.logger.debug("Update: Attributes or Keys do not contain any pair key-value valid"); + throw new SQLWarningException(I18NNaming.M_IT_HAS_NOT_CHANGED_ANY_RECORD, null); + } + final SQLStatementBuilder.SQLStatement stSQL = this.getStatementHandler().createUpdateQuery(this.getSchemaTable(), new HashMap<>(hValidAttributesValues), new HashMap<>(hValidKeysValues)); + final String sqlQuery = stSQL.getSQLStatement(); + final List vValues = this.processNullValues(stSQL.getValues()); + final int update = this.getJdbcTemplate().update(sqlQuery, vValues.toArray()); + if (update == 0) { + erResult.setCode(EntityResult.OPERATION_SUCCESSFUL_SHOW_MESSAGE); + erResult.setMessage(I18NNaming.M_IT_HAS_NOT_CHANGED_ANY_RECORD); + } + return erResult; + } + + /** + * Process null values. + * + * @param values the values + * @return the list + */ + protected List processNullValues(final List values) { + for (int i = 0; i < values.size(); i++) { + final Object ob = values.get(i); + if (ob instanceof NullValue) { + ((List) values).set(i, new SqlParameterValue(((NullValue) ob).getSQLDataType(), null)); + } + } + return values; + } + + /** + * Unsafe delete. + * + * @param keysValues the keys values + * @return the entity result + */ + @Override + public EntityResult unsafeDelete(final Map keysValues) { + return this.innerDelete(keysValues, false); + } + + /* + * (non-Javadoc) + * + * @see com.ontimize.jee.server.entity.IOntimizeDaoSupport#unsafeDelete(java.util .Map) + */ + + /** + * Delete. + * + * @param keysValues the keys values + * @return the entity result + */ + @Override + public EntityResult delete(final Map keysValues) { + return this.innerDelete(keysValues, true); + } + + /* + * (non-Javadoc) + * + * @see com.ontimize.jee.server.entity.IOntimizeDaoSupport#delete(java.util.Map) + */ + + /** + * Inner delete. + * + * @param keysValues the keys values + * @param safe the safe + * @return the entity result + */ + public EntityResult innerDelete(final Map keysValues, final boolean safe) { + this.checkCompiled(); + final EntityResult erResult = new EntityResultMapImpl(); + Map keysValuesChecked = keysValues; + if (safe) { + keysValuesChecked = this.checkDeleteKeys(keysValues); + } + + if (keysValuesChecked.isEmpty()) { + OntimizeJdbcDaoSupport.logger.debug("Delete: Keys does not contain any pair key-value valid:" + keysValues); + throw new SQLWarningException(I18NNaming.M_IT_HAS_NOT_CHANGED_ANY_RECORD, null); + } + + final SQLStatementBuilder.SQLStatement stSQL = this.getStatementHandler().createDeleteQuery(this.getSchemaTable(), new HashMap<>(keysValuesChecked)); + final String sqlQuery = stSQL.getSQLStatement(); + final List vValues = stSQL.getValues(); + this.getJdbcTemplate().update(sqlQuery, vValues.toArray()); + + return erResult; + } + + /** + * Checks if keysValues contains a value for all columns defined in 'delete_keys' + * parameter. + *

+ * + * @param keysValues the keys values + */ + protected Map checkDeleteKeys(final Map keysValues) { + Map res = new HashMap<>(); + for (String element : this.deleteKeys) { + String mapKey = element; + if (!keysValues.containsKey(mapKey)) { + throw new SQLWarningException("M_NECESSARY_" + mapKey.toUpperCase(), null); + } + res.put(mapKey, keysValues.get(mapKey)); + } + OntimizeJdbcDaoSupport.logger.debug(" Delete valid keys values: Input: {} -> Result: {}", keysValues, res); + return res; + } + + /** + * Checks if keysValues contains a value for all columns defined in 'update_keys' + * parameter. + *

+ * + * @param keysValues the keys values + */ + protected void checkUpdateKeys(final Map keysValues) { + for (String element : this.updateKeys) { + if (!keysValues.containsKey(element)) { + throw new SQLWarningException("M_NECESSARY_" + element.toUpperCase(), new SQLWarning()); + } + } + } + + /** + * Returns a Map containing a list of valid key-value pairs from those contained in the + * keysValues argument. + *

+ * A key-value pair is valid if the key is valid. + *

+ * Only keys matching (case-sensitive) any of the columns defined by the 'update_keys' parameter are + * considered valid. + *

+ * + * @param keysValues the keys values + * @return the valid updating keys values + */ + public Map getValidUpdatingKeysValues(final Map keysValues) { + final Map hValidKeysValues = new HashMap<>(); + for (String element : this.updateKeys) { + if (keysValues.containsKey(element)) { + hValidKeysValues.put(element, keysValues.get(element)); + } + } + OntimizeJdbcDaoSupport.logger.debug(" Update valid keys values: Input: " + keysValues + " -> Result: " + hValidKeysValues); + return hValidKeysValues; + } + + /** + * Returns a Map containing a list of valid key-value pairs from those contained in the + * attributesValues argument. + *

+ * A key-value pair is valid if the key is in the table column list. + *

+ * + * @param inputAttributesValues the attributes values + * @return the valid attributes + */ + public Map getValidAttributes(final Map inputAttributesValues) { + final Map hValidKeysValues = new HashMap<>(); + List nameConventionTableColumns = this.tableMetaDataContext.getNameConventionTableColumns(); + for (final Map.Entry entry : inputAttributesValues.entrySet()) { + final Object oKey = entry.getKey(); + final Object oValue = entry.getValue(); + if (nameConventionTableColumns.contains(oKey)) { + hValidKeysValues.put((String) oKey, oValue); + } + } + OntimizeJdbcDaoSupport.logger.debug(" Update valid attributes values: Input: " + inputAttributesValues + " -> Result: " + hValidKeysValues); + return hValidKeysValues; + } + + /** + * Processes all the MultipleTableAttribute contained as keys ih the Map av. All other + * objects are added to the resulting List with no changes. The MultipleTableAttribute objects are + * replaced by their attribute. + * + * @param av the av + * @return a new HashMap with the processed objects. + */ + protected Map processMultipleTableAttribute(final Map av) { + final Map res = new HashMap<>(); + for (final Map.Entry entry : av.entrySet()) { + final Object oKey = entry.getKey(); + final Object oValue = entry.getValue(); + if (oKey instanceof MultipleTableAttribute) { + res.put(((MultipleTableAttribute) oKey).getAttribute(), oValue); + } else { + res.put(oKey, oValue); + } + } + return res; + } + + /** + * Processes the ReferenceFieldAttribute objects contained in keysValues. + *

+ * Returns a Map containing all the objects contained in the argument keysValues except + * in the case of keys that are ReferenceFieldAttribute objects, which are replaced by + * ((ReferenceFieldAttribute)object).getAttr() + *

+ * + * @param keysValues the keysValues to process + * @return a Map containing the processed objects + */ + public Map processReferenceDataFieldAttributes(final Map keysValues) { + if (keysValues == null) { + return null; + } + final Map res = new HashMap<>(); + for (final Map.Entry entry : keysValues.entrySet()) { + final Object oKey = entry.getKey(); + final Object oValue = entry.getValue(); + if (oKey instanceof ReferenceFieldAttribute) { + final String attr = ((ReferenceFieldAttribute) oKey).getAttr(); + res.put(attr, oValue); + } else { + res.put(oKey, oValue); + } + } + return res; + } + + /** + * Processes the ReferenceFieldAttribute objects contained in list. + *

+ * Returns a List containing all the objects in the argument list except in the case of + * keys that are ReferenceFieldAttribute objects, which are maintained but also + * ((ReferenceFieldAttribute)object).getAttr() is added + *

+ * + * @param list the list to process + * @return a List containing the processed objects + */ + public List processReferenceDataFieldAttributes(final List list) { + if (list == null) { + return null; + } + final List res = new ArrayList<>(); + for (final Object ob : list) { + // Add the attribute + if (!res.contains(ob)) { + res.add(ob); + } + // If the attribute is ReferenceFieldAttribute add the string to + if ((ob instanceof ReferenceFieldAttribute) && !res.contains(((ReferenceFieldAttribute) ob).getAttr())) { + res.add(((ReferenceFieldAttribute) ob).getAttr()); + } + } + return res; + } + + /** + * Returns a list containing the valid attributes of those included in the List + * attributes + *

+ * If valid column names have been specified for this entity, only attributes matching + * (case-sensitive) any of this column names are considered valid. + *

+ * If no columns have been defined, all attributes will be considered valid. + * + * @param attributes the attributes + * @param validColumns the valid columns + * @return a List with the valid attributes + */ + public List getValidAttributes(final List attributes, List validColumns) { + List inputValidColumns = validColumns == null ? (List) Collections.EMPTY_LIST : validColumns; + final List validAttributes = new ArrayList<>(); + for (final Object ob : attributes) { + if ((ob instanceof String) || (ob instanceof DBFunctionName)) { + boolean isValid = true; + if (ob instanceof String) { + if (SQLStatementBuilder.ExtendedSQLConditionValuesProcessor.EXPRESSION_KEY.equals(ob)) { + isValid = true; + } else if (!inputValidColumns.isEmpty() && !inputValidColumns.contains(ob)) { + isValid = false; + } else { + isValid = this.isColumnNameValid((String) ob); + } + } + if (isValid) { + validAttributes.add(ob); + } + } + } + return validAttributes; + } + + /** + * Checks if is column name valid. + * + * @param ob the ob + * @return true, if is column name valid + */ + protected boolean isColumnNameValid(String ob) { + boolean notValid = ob.contains(" ") || ob.contains("*"); + return !notValid; + } + + /** + * Returns a cleaned map containing the valid pairs of those included in the map + * inputKeysValues + *

+ * If valid column names have been specified for this entity/query, only attributes matching + * (case-sensitive) any of this column names are considered valid. + *

+ * If no columns have been defined, all attributes will be considered valid. + *

+ * Returns cleaned keys values to do query according valid columns (if defined). + * + * @param inputKeysValues + * @param validColumns + * @return + */ + protected Map getValidQueryingKeysValues(Map inputKeysValues, List validColumns) { + if ((validColumns == null) || validColumns.isEmpty()) { + return inputKeysValues; + } + final Map hValidKeysValues = new HashMap<>(); + for (Map.Entry entry : inputKeysValues.entrySet()) { + if (SQLStatementBuilder.ExtendedSQLConditionValuesProcessor.EXPRESSION_KEY.equals(entry.getKey()) || validColumns.contains(entry.getKey())) { + hValidKeysValues.put(entry.getKey(), entry.getValue()); + } + } + OntimizeJdbcDaoSupport.logger.debug(" Query valid keys values: Input: " + inputKeysValues + " -> Result: " + hValidKeysValues); + return hValidKeysValues; + } + + /** + * Processes the MultipleValue objects contained in keysValues. Returns a new HashMap + * with the same data as keysValues except that MultipleValue objects are deleted and + * the key-value pairs of these objects are added to the new HashMap. + * + * @param keysValues the keys values + * @return a new HashMap with MultipleValue objects replaced by their key-value pairs + */ + public Map processMultipleValueAttributes(final Map keysValues) { + if (keysValues == null) { + return null; + } + final Map res = new HashMap<>(); + for (final Map.Entry entry : keysValues.entrySet()) { + final Object oKey = entry.getKey(); + final Object oValue = entry.getValue(); + if (oValue instanceof MultipleValue) { + final Enumeration mvKeys = ((MultipleValue) oValue).keys(); + while (mvKeys.hasMoreElements()) { + final Object iMvKeyM = mvKeys.nextElement(); + final Object oMvValue = ((MultipleValue) oValue).get(iMvKeyM); + res.put(iMvKeyM, oMvValue); + } + } else { + res.put(oKey, oValue); + } + } + return res; + } + + /** + * Processes the keys in order to get String as column name. + * + * @param keysValues the keys values + * @return a new HashMap with MultipleValue objects replaced by their key-value pairs + */ + public Map processStringKeys(final Map keysValues) { + if (keysValues == null) { + return null; + } + final Map res = new HashMap<>(); + for (final Map.Entry entry : keysValues.entrySet()) { + final Object oKey = entry.getKey(); + final Object oValue = entry.getValue(); + res.put(oKey.toString(), oValue); + } + return res; + } + + /** + * Get the name of the table for this insert. + * + * @return the table name + */ + public String getTableName() { + this.checkCompiled(); + return this.tableMetaDataContext.getTableName(); + } + + // ------------------------------------------------------------------------- + // Methods dealing with configuration properties + // ------------------------------------------------------------------------- + + /** + * Set the name of the table for this insert. + * + * @param tableName the new table name + */ + public void setTableName(final String tableName) { + this.checkIfConfigurationModificationIsAllowed(); + this.tableMetaDataContext.setTableName(tableName); + } + + /** + * Get the name of the schema for this insert. + * + * @return the schema name + */ + public String getSchemaName() { + this.checkCompiled(); + return this.tableMetaDataContext.getSchemaName(); + } + + /** + * Set the name of the schema for this insert. + * + * @param schemaName the new schema name + */ + public void setSchemaName(final String schemaName) { + this.checkIfConfigurationModificationIsAllowed(); + this.tableMetaDataContext.setSchemaName(StringTools.isEmpty(schemaName) ? null : schemaName); + } + + /** + * Get the name of the catalog for this insert. + * + * @return the catalog name + */ + public String getCatalogName() { + this.checkCompiled(); + return this.tableMetaDataContext.getCatalogName(); + } + + /** + * Set the name of the catalog for this insert. + * + * @param catalogName the new catalog name + */ + public void setCatalogName(final String catalogName) { + this.checkIfConfigurationModificationIsAllowed(); + this.tableMetaDataContext.setCatalogName(StringTools.isEmpty(catalogName) ? null : catalogName); + } + + /** + * Get the names of the columns used. + * + * @return the column names + */ + public List getColumnNames() { + this.checkCompiled(); + return Collections.unmodifiableList(this.declaredColumns); + } + + /** + * Set the names of the columns to be used. + * + * @param columnNames the new column names + */ + public void setColumnNames(final List columnNames) { + this.checkIfConfigurationModificationIsAllowed(); + this.declaredColumns.clear(); + this.declaredColumns.addAll(columnNames); + } + + /** + * Get the names of any generated keys. + * + * @return the generated key names + */ + public String[] getGeneratedKeyNames() { + this.checkCompiled(); + return this.generatedKeyNames; + } + + /** + * Set the names of any generated keys. + * + * @param generatedKeyNames the new generated key names + */ + public void setGeneratedKeyNames(final String[] generatedKeyNames) { + this.checkIfConfigurationModificationIsAllowed(); + this.generatedKeyNames = generatedKeyNames; + } + + /** + * Specify the name of a single generated key column. + * + * @param generatedKeyName the new generated key name + */ + public void setGeneratedKeyName(final String generatedKeyName) { + this.checkIfConfigurationModificationIsAllowed(); + if (generatedKeyName == null) { + this.generatedKeyNames = new String[]{}; + } else { + this.generatedKeyNames = new String[]{generatedKeyName}; + } + } + + /** + * Specify whether the parameter metadata for the call should be used. The default is true. + * + * @param accessTableColumnMetaData the new access table column meta data + */ + public void setAccessTableColumnMetaData(final boolean accessTableColumnMetaData) { + this.tableMetaDataContext.setAccessTableColumnMetaData(accessTableColumnMetaData); + } + + /** + * Specify whether the default for including synonyms should be changed. The default is false. + * + * @param override the new override include synonyms default + */ + public void setOverrideIncludeSynonymsDefault(final boolean override) { + this.tableMetaDataContext.setOverrideIncludeSynonymsDefault(override); + } + + /** + * Compile this JdbcInsert using provided parameters and meta data plus other settings. This + * finalizes the configuration for this object and subsequent attempts to compile are ignored. This + * will be implicitly called the first time an un-compiled insert is executed. + * + * @throws InvalidDataAccessApiUsageException if the object hasn't been correctly initialized, for + * example if no DataSource has been provided + */ + public synchronized final void compile() throws InvalidDataAccessApiUsageException { + if (!this.isCompiled()) { + final ConfigurationFile annotation = this.getClass().getAnnotation(ConfigurationFile.class); + if (annotation != null) { + this.configurationFile = annotation.configurationFile(); + this.configurationFilePlaceholder = annotation.configurationFilePlaceholder(); + } + this.loadConfigurationFile(this.configurationFile, this.configurationFilePlaceholder); + + if (this.getJdbcTemplate() == null) { + throw new IllegalArgumentException("'dataSource' or 'jdbcTemplate' is required"); + } + + if (this.tableMetaDataContext.getTableName() == null) { + throw new InvalidDataAccessApiUsageException("Table name is required"); + } + try { + this.getJdbcTemplate().afterPropertiesSet(); + } catch (final IllegalArgumentException ex) { + throw new InvalidDataAccessApiUsageException(ex.getMessage(), ex); + } + this.compileInternal(); + this.compiled = true; + if (OntimizeJdbcDaoSupport.logger.isDebugEnabled()) { + OntimizeJdbcDaoSupport.logger.debug("JdbcInsert for table [{}] compiled", this.getTableName()); + } + } + } + + + // ------------------------------------------------------------------------- + // Methods handling compilation issues + // ------------------------------------------------------------------------- + + @Override + public void reload() { + OntimizeJdbcDaoSupport.logger.debug("dao {} - {} marked to recompile", this.getClass().getName(), this.getTableName()); + this.compiled = false; + this.setTableName(null); + this.setSchemaName(null); + this.setCatalogName(null); + this.setDeleteKeys(null); + this.setUpdateKeys(null); + this.sqlQueries.clear(); + this.setGeneratedKeyName(null); + this.setStatementHandler(null); + this.setNameConverter(null); + } + + /** + * Load the configuration file. + * + * @param path the path + * @param pathToPlaceHolder the path to place holder + * @throws InvalidDataAccessApiUsageException the invalid data access api usage exception + */ + protected void loadConfigurationFile(final String path, final String pathToPlaceHolder) throws InvalidDataAccessApiUsageException { + + try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(path)) { + Reader reader = null; + if (pathToPlaceHolder != null) { + try (InputStream isPlaceHolder = Thread.currentThread().getContextClassLoader().getResourceAsStream(pathToPlaceHolder)) { + final Properties prop = new Properties(); + if (isPlaceHolder != null) { + prop.load(isPlaceHolder); + } + + Map mapProperties = prop.stringPropertyNames().stream().collect(Collectors.toMap(Function.identity(), prop::getProperty)); + reader = new ReplaceTokensFilterReader(new InputStreamReader(is), mapProperties); + } + } else { + reader = new InputStreamReader(is); + } + + JdbcEntitySetupType baseSetup = JAXB.unmarshal(reader, JdbcEntitySetupType.class); + + // Support to Dao extensions + JdbcEntitySetupType setupConfig = this.checkDaoExtensions(baseSetup, path, pathToPlaceHolder); + + // Process setup information to configure dao + this.setTableName(setupConfig.getTable()); + this.setSchemaName(setupConfig.getSchema()); + this.setCatalogName(setupConfig.getCatalog()); + this.setDeleteKeys(setupConfig.getDeleteKeys().getColumn()); + this.setUpdateKeys(setupConfig.getUpdateKeys().getColumn()); + if (setupConfig.getQueries() != null) { + for (final QueryType query : setupConfig.getQueries().getQuery()) {// + this.addQueryTemplateInformation(query.getId(), query.getSentence().getValue(), // + query.getAmbiguousColumns() == null ? null : query.getAmbiguousColumns().getAmbiguousColumn(), // + query.getFunctionColumns() == null ? null : query.getFunctionColumns().getFunctionColumn(), // + query.getValidColumns() != null ? query.getValidColumns().getColumn() : new ArrayList(), // + query.getOrderColumns() == null ? null : query.getOrderColumns().getOrderColumn()); + } + } + this.setGeneratedKeyName(setupConfig.getGeneratedKey()); + this.setDataSource((DataSource) this.applicationContext.getBean(setupConfig.getDatasource())); + this.setStatementHandler((SQLStatementHandler) this.applicationContext.getBean(setupConfig.getSqlhandler())); + + final String nameConverter = setupConfig.getNameconverter(); + if (!CheckingTools.isStringEmpty(nameConverter)) { + this.setNameConverter((INameConverter) this.applicationContext.getBean(nameConverter)); + } + this.tableMetaDataContext.setNameConvention(this.nameConvention); + } catch (final IOException e) { + throw new InvalidDataAccessApiUsageException(I18NNaming.M_ERROR_LOADING_CONFIGURATION_FILE, e); + } + + } + + protected JdbcEntitySetupType checkDaoExtensions(JdbcEntitySetupType baseSetup, final String path, final String pathToPlaceHolder) { + if (this.daoExtensionHelper == null) { + return baseSetup; + } + return this.daoExtensionHelper.checkDaoExtensions(baseSetup, path, pathToPlaceHolder); + } + + /** + * Gets the bean property converter. + * + * @return the bean property converter + */ + public INameConverter getNameConverter() { + this.checkCompiled(); + return this.nameConverter; + } + + /** + * Sets the bean property converter. + * + * @param converter the new bean property converter + */ + protected void setNameConverter(final INameConverter converter) { + this.nameConverter = converter; + } + + /** + * Gets the configuration file. + * + * @return the configuration file + */ + public String getConfigurationFile() { + return this.configurationFile; + } + + /** + * Sets the configuration file. + * + * @param configurationFile the new configuration file + */ + public synchronized void setConfigurationFile(final String configurationFile) { + this.configurationFile = configurationFile; + } + + /** + * Gets the configuration file placeholder. + * + * @return the configuration file placeholder + */ + public String getConfigurationFilePlaceholder() { + return this.configurationFilePlaceholder; + } + + /** + * Sets the configuration file placeholder. + * + * @param configurationFilePlaceholder the new configuration file placeholder + */ + public synchronized void setConfigurationFilePlaceholder(final String configurationFilePlaceholder) { + this.configurationFilePlaceholder = configurationFilePlaceholder; + } + + public INameConvention getNameConvention() { + return this.nameConvention; + } + + public void setNameConvention(INameConvention nameConvention) { + this.nameConvention = nameConvention; + } + + /** + * Check dao config. + */ + @Override + protected void checkDaoConfig() { + // no need of jdbctemplate at this point + } + + /** + * Adds a query. + * + * @param id the id + * @param value the value + * @param ambiguousColumns the ambiguous columns + * @param functionColumns the function columns + */ + public void addQueryTemplateInformation(final String id, final String value, final List ambiguousColumns, final List functionColumns, List orderColumns) { + this.addQueryTemplateInformation(id, value, ambiguousColumns, functionColumns, new ArrayList<>(), orderColumns); + } + + /** + * Adds a query, allowing determine valid columns to query to DB. + * + * @param id + * @param value + * @param ambiguousColumns + * @param functionColumns + * @param validColumns + */ + public void addQueryTemplateInformation(final String id, final String value, final List ambiguousColumns, final List functionColumns, List validColumns, List orderColumns) { + this.sqlQueries.put(id, new QueryTemplateInformation(value, ambiguousColumns, functionColumns, validColumns, orderColumns)); + } + + /** + * Gets the template query. + * + * @param id the id + * @return the template query + */ + public QueryTemplateInformation getQueryTemplateInformation(final String id) { + this.checkCompiled(); + return this.sqlQueries.get(id); + } + + /** + * Method to perform the actual compilation. Subclasses can override this template method to perform + * their own compilation. Invoked after this base class's compilation is complete. + */ + protected void compileInternal() { + this.tableMetaDataContext.processMetaData(this.getJdbcTemplate().getDataSource(), this.declaredColumns, this.generatedKeyNames); + this.onCompileInternal(); + } + + /** + * Hook method that subclasses may override to react to compilation. This implementation does + * nothing. + */ + protected void onCompileInternal() { + // This implementation does nothing. + } + + /** + * Is this operation "compiled"?. + * + * @return whether this operation is compiled, and ready to use. + */ + public boolean isCompiled() { + return this.compiled; + } + + /** + * Check whether this operation has been compiled already; lazily compile it if not already + * compiled. + *

+ * Automatically called by {@code validateParameters}. + */ + public void checkCompiled() { + if (!this.isCompiled()) { + OntimizeJdbcDaoSupport.logger.debug("JdbcInsert not compiled before execution - invoking compile"); + this.compile(); + } + } + + /** + * Method to check whether we are allowd to make any configuration changes at this time. If the + * class has been compiled, then no further changes to the configuration are allowed. + */ + protected void checkIfConfigurationModificationIsAllowed() { + if (this.isCompiled()) { + throw new InvalidDataAccessApiUsageException("Configuration can't be altered once the class has been compiled or used"); + } + } + + /** + * Method that provides execution of the insert using the passed in Map of parameters. + * + * @param args Map with parameter names and values to be used in insert + * @return number of rows affected + */ + protected int doExecuteInsert(final Map args) { + this.checkCompiled(); + final InsertMetaInfoHolder holder = this.matchInParameterValuesWithInsertColumns(args); + return this.executeInsertInternal(holder); + } + + // ------------------------------------------------------------------------- + // Methods handling execution + // ------------------------------------------------------------------------- + + /** + * Method that provides execution of the insert using the passed in. + * + * @param parameterSource parameter names and values to be used in insert + * @return number of rows affected {@link SqlParameterSource} + */ + protected int doExecuteInsert(final SqlParameterSource parameterSource) { + this.checkCompiled(); + final InsertMetaInfoHolder holder = this.matchInParameterValuesWithInsertColumns(parameterSource); + return this.executeInsertInternal(holder); + } + + /** + * Method to execute the insert. + * + * @param values the values + * @return the int + */ + protected int executeInsertInternal(InsertMetaInfoHolder holder) { + OntimizeJdbcDaoSupport.logger.debug("The following parameters are used for insert {} with: {}", holder.getInsertString(), holder.getValues()); + return this.getJdbcTemplate().update(holder.getInsertString(), holder.getValues().toArray(), holder.getInsertTypes()); + } + + /** + * Method that provides execution of the insert using the passed in Map of parameters and returning + * a generated key. + * + * @param args Map with parameter names and values to be used in insert + * @return the key generated by the insert + */ + protected Object doExecuteInsertAndReturnKey(final Map args) { + this.checkCompiled(); + final InsertMetaInfoHolder holder = this.matchInParameterValuesWithInsertColumns(args); + return this.executeInsertAndReturnKeyInternal(holder); + } + + /** + * Method that provides execution of the insert using the passed in. + * + * @param parameterSource parameter names and values to be used in insert + * @return the key generated by the insert {@link SqlParameterSource} and returning a generated key + */ + protected Object doExecuteInsertAndReturnKey(final SqlParameterSource parameterSource) { + this.checkCompiled(); + final InsertMetaInfoHolder holder = this.matchInParameterValuesWithInsertColumns(parameterSource); + return this.executeInsertAndReturnKeyInternal(holder); + } + + /** + * Method that provides execution of the insert using the passed in Map of parameters and returning + * all generated keys. + * + * @param args Map with parameter names and values to be used in insert + * @return the KeyHolder containing keys generated by the insert + */ + protected KeyHolder doExecuteInsertAndReturnKeyHolder(final Map args) { + this.checkCompiled(); + final InsertMetaInfoHolder holder = this.matchInParameterValuesWithInsertColumns(args); + return this.executeInsertAndReturnKeyHolderInternal(holder); + } + + /** + * Method that provides execution of the insert using the passed in. + * + * @param parameterSource parameter names and values to be used in insert + * @return the KeyHolder containing keys generated by the insert {@link SqlParameterSource} and + * returning all generated keys + */ + protected KeyHolder doExecuteInsertAndReturnKeyHolder(final SqlParameterSource parameterSource) { + this.checkCompiled(); + final InsertMetaInfoHolder holder = this.matchInParameterValuesWithInsertColumns(parameterSource); + return this.executeInsertAndReturnKeyHolderInternal(holder); + } + + /** + * Method to execute the insert generating single key. + * + * @param values the values + * @return the number + */ + protected Object executeInsertAndReturnKeyInternal(final InsertMetaInfoHolder holder) { + final KeyHolder kh = this.executeInsertAndReturnKeyHolderInternal(holder); + if ((kh != null) && (kh.getKeyAs(Object.class) != null)) { + return kh.getKeyAs(Object.class); + } + throw new DataIntegrityViolationException("Unable to retrieve the generated key for the insert: " + holder.getInsertString()); + } + + /** + * Method to execute the insert generating any number of keys. + * + * @param values the values + * @return the key holder + */ + protected KeyHolder executeInsertAndReturnKeyHolderInternal(final InsertMetaInfoHolder holder) { + OntimizeJdbcDaoSupport.logger.debug("The following parameters are used for call {} with: {}", holder.getInsertString(), holder.getValues()); + final KeyHolder keyHolder = new GeneratedKeyHolder(); + if (!this.tableMetaDataContext.isGetGeneratedKeysSupported()) { + if (!this.tableMetaDataContext.isGetGeneratedKeysSimulated()) { + throw new InvalidDataAccessResourceUsageException("The getGeneratedKeys feature is not supported by this database"); + } + if (this.getGeneratedKeyNames().length < 1) { + throw new InvalidDataAccessApiUsageException("Generated Key Name(s) not specificed. " + "Using the generated keys features requires specifying the name(s) of the generated column(s)"); + } + if (this.getGeneratedKeyNames().length > 1) { + throw new InvalidDataAccessApiUsageException("Current database only supports retreiving the key for a single column. There are " + this.getGeneratedKeyNames().length + " columns specified: " + Arrays.asList(this.getGeneratedKeyNames())); + } + + // This is a hack to be able to get the generated key from a + // database that doesn't support + // get generated keys feature. HSQL is one, PostgreSQL is another. + // Postgres uses a RETURNING + // clause while HSQL uses a second query that has to be executed + // with the same connection. + + String tableName = this.tableMetaDataContext.getTableName() != null ? this.tableMetaDataContext.getTableName() : null; if (tableName != null) { - final String keyQuery = this.tableMetaDataContext.getSimpleQueryForGetGeneratedKey( - tableName, this.getGeneratedKeyNames()[0]); + final String keyQuery = this.tableMetaDataContext.getSimpleQueryForGetGeneratedKey(tableName, this.getGeneratedKeyNames()[0]); Assert.notNull(keyQuery, "Query for simulating get generated keys can't be null"); if (keyQuery.toUpperCase().startsWith("RETURNING")) { - this.simulatingWithReturning(holder, keyQuery, keyHolder); - }else{ - this.simulatingWithoutReturning(holder, keyQuery, keyHolder); - } - } + this.simulatingWithReturning(holder, keyQuery, keyHolder); + } else { + this.simulatingWithoutReturning(holder, keyQuery, keyHolder); + } + } return keyHolder; - } - this.getJdbcTemplate().update(new PreparedStatementCreator() { - - @Override - public PreparedStatement createPreparedStatement(final Connection con) throws SQLException { - final PreparedStatement ps = OntimizeJdbcDaoSupport.this.prepareInsertStatementForGeneratedKeys(con, - holder.getInsertString()); - OntimizeJdbcDaoSupport.this.setParameterValues(ps, holder.getValues(), holder.getInsertTypes()); - return ps; - } - }, keyHolder); - return keyHolder; - } - - protected void simulatingWithoutReturning(InsertMetaInfoHolder holder, String keyQuery, KeyHolder keyHolder) { - this.getJdbcTemplate().execute(new ConnectionCallback() { - - @Override - public Object doInConnection(final Connection con) throws SQLException, DataAccessException { - // Do the insert - PreparedStatement ps = null; - try { - ps = con.prepareStatement(holder.getInsertString()); - OntimizeJdbcDaoSupport.this.setParameterValues(ps, holder.getValues(), - holder.getInsertTypes()); - ps.executeUpdate(); - } finally { - JdbcUtils.closeStatement(ps); - } - // Get the key - ResultSet rs = null; - final Map keys = new HashMap<>(1); - final Statement keyStmt = con.createStatement(); - try { - rs = keyStmt.executeQuery(keyQuery); - if (rs.next()) { - final long key = rs.getLong(1); - keys.put(OntimizeJdbcDaoSupport.this.getGeneratedKeyNames()[0], key); - keyHolder.getKeyList().add(keys); - } - } finally { - JdbcUtils.closeResultSet(rs); - JdbcUtils.closeStatement(keyStmt); - } - return null; - } - }); - } - - protected void simulatingWithReturning(InsertMetaInfoHolder holder, String keyQuery, KeyHolder keyHolder) { - final Long key = this.getJdbcTemplate() - .queryForObject(holder.getInsertString() + " " + keyQuery, - Long.class, - holder.getValues().toArray(new Object[holder.getValues().size()])); - final Map keys = new HashMap<>(1); - keys.put(this.getGeneratedKeyNames()[0], key); - keyHolder.getKeyList().add(keys); - } - - /** - * Create the PreparedStatement to be used for insert that have generated keys. - * @param con the connection used - * @return PreparedStatement to use - * @throws SQLException the sQL exception - */ - protected PreparedStatement prepareInsertStatementForGeneratedKeys(final Connection con, String insertString) - throws SQLException { - if (this.getGeneratedKeyNames().length < 1) { - throw new InvalidDataAccessApiUsageException( - "Generated Key Name(s) not specificed. " - + "Using the generated keys features requires specifying the name(s) of the generated column(s)"); - } - PreparedStatement ps; - if (this.tableMetaDataContext.isGeneratedKeysColumnNameArraySupported()) { - OntimizeJdbcDaoSupport.logger.debug("Using generated keys support with array of column names."); - ps = con.prepareStatement(insertString, this.getGeneratedKeyNames()); - } else { - OntimizeJdbcDaoSupport.logger.debug("Using generated keys support with Statement.RETURN_GENERATED_KEYS."); - ps = con.prepareStatement(insertString, Statement.RETURN_GENERATED_KEYS); - } - return ps; - } - - /** - * Method that provides execution of a batch insert using the passed in Maps of parameters. - * @param batch array of Maps with parameter names and values to be used in batch insert - * @return array of number of rows affected - */ - @Override - public int[] insertBatch(final Map[] batch) { - this.checkCompiled(); - final List[] batchValues = new ArrayList[batch.length]; - int i = 0; - for (final Map args : batch) { - final List values = this.matchInParameterValuesWithInsertColumnsForBatch(args); - batchValues[i++] = values; - } - return this.executeInsertBatchInternal(batchValues, - this.tableMetaDataContext.createInsertString(this.getGeneratedKeyNames()), - this.tableMetaDataContext.createInsertTypes()); - } - - /** - * Method that provides execution of a batch insert using the passed in array of - * {@link SqlParameterSource}. - * @param batch array of SqlParameterSource with parameter names and values to be used in insert - * @return array of number of rows affected - */ - protected int[] doExecuteInsertBatch(final SqlParameterSource[] batch, final String insertString, - final int[] insertTypes) { - this.checkCompiled(); - final List[] batchValues = new ArrayList[batch.length]; - int i = 0; - for (final SqlParameterSource parameterSource : batch) { - final List values = this.matchInParameterValuesWithInsertColumnsForBatch(parameterSource); - batchValues[i++] = values; - } - return this.executeInsertBatchInternal(batchValues, insertString, insertTypes); - } - - /** - * Method to execute the batch insert. - * @param batchValues the batch values - * @return the int[] - */ - protected int[] executeInsertBatchInternal(final List[] batchValues, final String insertString, - final int[] insertTypes) { - OntimizeJdbcDaoSupport.logger.debug("Executing statement {} with batch of size: {}", insertString, - batchValues.length); - return this.getJdbcTemplate().batchUpdate(insertString, new BatchPreparedStatementSetter() { - - @Override - public void setValues(final PreparedStatement ps, final int i) throws SQLException { - final List values = batchValues[i]; - OntimizeJdbcDaoSupport.this.setParameterValues(ps, values, insertTypes); - } - - @Override - public int getBatchSize() { - return batchValues.length; - } - }); - } - - /** - * Internal implementation for setting parameter values. - * @param preparedStatement the PreparedStatement - * @param values the values to be set - * @param columnTypes the column types - * @throws SQLException the sQL exception - */ - protected void setParameterValues(final PreparedStatement preparedStatement, final List values, - final int[] columnTypes) throws SQLException { - - int colIndex = 0; - for (Object value : values) { - colIndex++; - if ((columnTypes == null) || (colIndex > columnTypes.length)) { - StatementCreatorUtils.setParameterValue(preparedStatement, colIndex, SqlTypeValue.TYPE_UNKNOWN, value); - } else { - final int sqlType = columnTypes[colIndex - 1]; - if (ObjectTools.isIn(sqlType, Types.BLOB, Types.BINARY, Types.VARBINARY) - && ((value instanceof byte[]) || (value instanceof InputStream))) { - if (value instanceof byte[]) { - preparedStatement.setBytes(colIndex, (byte[]) value); - } else { - try { - // TODO esto no esta soportado por los drivers jdbc 4.0 - // TODO segun el driver puede ser que sea mas rapido llamar al metodo con la longitud - preparedStatement.setBlob(colIndex, (InputStream) value); - } catch (AbstractMethodError ex) { - OntimizeJdbcDaoSupport.logger.debug(null, ex); - try { - preparedStatement.setBinaryStream(colIndex, (InputStream) value, - ((InputStream) value).available()); - } catch (IOException error) { - throw new SQLException(error); - } - } - } - } else if (value instanceof NullValue) { - // TODO At this point we could retrieve sqlType from ((NullValue)value).getSQLDataType() - // but it is preferable to use the sqlType retrieved from table metadata. - value = new SqlParameterValue(sqlType, null); - StatementCreatorUtils.setParameterValue(preparedStatement, colIndex, sqlType, value); - } else { - StatementCreatorUtils.setParameterValue(preparedStatement, colIndex, sqlType, value); - } - } - } - } - - /** - * Match the provided in parameter values with regitered parameters and parameters defined via - * metadata processing. - * @param parameterSource the parameter vakues provided as a {@link SqlParameterSource} - * @return Map with parameter names and values - */ - protected InsertMetaInfoHolder matchInParameterValuesWithInsertColumns(final SqlParameterSource parameterSource) { - return this.tableMetaDataContext.getInsertMetaInfo(parameterSource); - } - - /** - * Match the provided in parameter values with regitered parameters and parameters defined via - * metadata processing. - * @param args the parameter values provided in a Map - * @return Map with parameter names and values - */ - protected InsertMetaInfoHolder matchInParameterValuesWithInsertColumns(final Map args) { - return this.tableMetaDataContext.getInsertMetaInfo(args); - } - - /** - * Match the provided in parameter values with regitered parameters and parameters defined via - * metadata processing. - * @param parameterSource the parameter vakues provided as a {@link SqlParameterSource} - * @return Map with parameter names and values - */ - protected List matchInParameterValuesWithInsertColumnsForBatch(final SqlParameterSource parameterSource) { - return this.tableMetaDataContext.matchInParameterValuesWithInsertColumns(parameterSource); - } - - /** - * Match the provided in parameter values with regitered parameters and parameters defined via - * metadata processing. - * @param args the parameter values provided in a Map - * @return Map with parameter names and values - */ - protected List matchInParameterValuesWithInsertColumnsForBatch(final Map args) { - return this.tableMetaDataContext.matchInParameterValuesWithInsertColumns(args); - } - - /** - * Gets the schema table. - * @return the schema table - */ - protected String getSchemaTable() { - String sTableToUse = this.getTableName(); - if (this.getSchemaName() != null) { - sTableToUse = this.getSchemaName() + "." + sTableToUse; - } - return sTableToUse; - } - - /** - * Establish SQL statement builder. - * @param statementHandler the new statement handler - */ - public void setStatementHandler(final SQLStatementHandler statementHandler) { - this.statementHandler = statementHandler; - } - - /** - * Get the SQL statement builder. - * @return the statement builder - */ - public SQLStatementHandler getStatementHandler() { - return this.statementHandler; - } - - /** - * Sets the delete keys. - * @param deleteKeys the new delete keys - */ - public void setDeleteKeys(final List deleteKeys) { - this.deleteKeys = deleteKeys; - } - - /** - * Gets the delete keys. - * @return the delete keys - */ - public List getDeleteKeys() { - return this.deleteKeys; - } - - /** - * Sets the update keys. - * @param updateKeys the new update keys - */ - public void setUpdateKeys(final List updateKeys) { - this.updateKeys = updateKeys; - } - - /** - * Gets the update keys. - * @return the update keys - */ - public List getUpdateKeys() { - return this.updateKeys; - } - - /* - * (non-Javadoc) - * - * @see org.springframework.context.ApplicationContextAware#setApplicationContext - * (org.springframework.context.ApplicationContext) - */ - /** - * Sets the application context. - * @param applicationContext the new application context - * @throws BeansException the beans exception - */ - @Override - public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - - /** - * Gets the application context. - * @return the application context - */ - public ApplicationContext getApplicationContext() { - return this.applicationContext; - } - - /* - * (non-Javadoc) - * - * @see com.ontimize.jee.server.dao.IOntimizeDaoSupport#getCudProperties() - */ - @Override - public List getCudProperties() { - this.compile(); - List res = new ArrayList<>(); - for (TableParameterMetaData data : this.tableMetaDataContext.getTableParameters()) { - String name = data.getParameterName(); - int type = data.getSqlType(); - DaoProperty property = new DaoProperty(); - property.setSqlType(type); - property.setPropertyName(name); - res.add(property); - } - return res; - } - - /** - * Gets the table meta data context. - * @return the table meta data context - */ - public OntimizeTableMetaDataContext getTableMetaDataContext() { - if (!this.tableMetaDataContext.isProcessed()) { - this.compile(); - } - return this.tableMetaDataContext; - } - - protected OntimizeTableMetaDataContext createTableMetadataContext() { - return new OntimizeTableMetaDataContext(); - } - - @Override - protected JdbcTemplate createJdbcTemplate(DataSource dataSource) { - OntimizeJdbcDaoSupport.logger.trace("Creating new JdbcTemplate with fetchSize=1000"); - JdbcTemplate template = super.createJdbcTemplate(dataSource); - template.setFetchSize(1000); - OntimizeJdbcDaoSupport.logger - .trace("Creating new JdbcTemplate has finally fetchSize=" + template.getFetchSize()); - return template; - } - - private Integer getColumnSQLType(final String column) { - if (!this.tableMetaDataContext.isProcessed()) { - this.compile(); - } - for (final TableParameterMetaData data : this.tableMetaDataContext.getTableParameters()) { - if (column.equalsIgnoreCase(data.getParameterName())) { - return data.getSqlType(); - } - } - return null; - } + } + this.getJdbcTemplate().update(new PreparedStatementCreator() { + + @Override + public PreparedStatement createPreparedStatement(final Connection con) throws SQLException { + final PreparedStatement ps = OntimizeJdbcDaoSupport.this.prepareInsertStatementForGeneratedKeys(con, holder.getInsertString()); + OntimizeJdbcDaoSupport.this.setParameterValues(ps, holder.getValues(), holder.getInsertTypes()); + return ps; + } + }, keyHolder); + return keyHolder; + } + + protected void simulatingWithoutReturning(InsertMetaInfoHolder holder, String keyQuery, KeyHolder keyHolder) { + this.getJdbcTemplate().execute(new ConnectionCallback() { + + @Override + public Object doInConnection(final Connection con) throws SQLException, DataAccessException { + // Do the insert + PreparedStatement ps = null; + try { + ps = con.prepareStatement(holder.getInsertString()); + OntimizeJdbcDaoSupport.this.setParameterValues(ps, holder.getValues(), holder.getInsertTypes()); + ps.executeUpdate(); + } finally { + JdbcUtils.closeStatement(ps); + } + // Get the key + ResultSet rs = null; + final Map keys = new HashMap<>(1); + final Statement keyStmt = con.createStatement(); + try { + rs = keyStmt.executeQuery(keyQuery); + if (rs.next()) { + final long key = rs.getLong(1); + keys.put(OntimizeJdbcDaoSupport.this.getGeneratedKeyNames()[0], key); + keyHolder.getKeyList().add(keys); + } + } finally { + JdbcUtils.closeResultSet(rs); + JdbcUtils.closeStatement(keyStmt); + } + return null; + } + }); + } + + protected void simulatingWithReturning(InsertMetaInfoHolder holder, String keyQuery, KeyHolder keyHolder) { + final Long key = this.getJdbcTemplate().queryForObject(holder.getInsertString() + " " + keyQuery, Long.class, holder.getValues().toArray(new Object[holder.getValues().size()])); + final Map keys = new HashMap<>(1); + keys.put(this.getGeneratedKeyNames()[0], key); + keyHolder.getKeyList().add(keys); + } + + /** + * Create the PreparedStatement to be used for insert that have generated keys. + * + * @param con the connection used + * @return PreparedStatement to use + * @throws SQLException the sQL exception + */ + protected PreparedStatement prepareInsertStatementForGeneratedKeys(final Connection con, String insertString) throws SQLException { + if (this.getGeneratedKeyNames().length < 1) { + throw new InvalidDataAccessApiUsageException("Generated Key Name(s) not specificed. " + "Using the generated keys features requires specifying the name(s) of the generated column(s)"); + } + PreparedStatement ps; + if (this.tableMetaDataContext.isGeneratedKeysColumnNameArraySupported()) { + OntimizeJdbcDaoSupport.logger.debug("Using generated keys support with array of column names."); + ps = con.prepareStatement(insertString, this.getGeneratedKeyNames()); + } else { + OntimizeJdbcDaoSupport.logger.debug("Using generated keys support with Statement.RETURN_GENERATED_KEYS."); + ps = con.prepareStatement(insertString, Statement.RETURN_GENERATED_KEYS); + } + return ps; + } + + /** + * Method that provides execution of a batch insert using the passed in Maps of parameters. + * + * @param batch array of Maps with parameter names and values to be used in batch insert + * @return array of number of rows affected + */ + @Override + public int[] insertBatch(final Map[] batch) { + this.checkCompiled(); + final List[] batchValues = new ArrayList[batch.length]; + int i = 0; + for (final Map args : batch) { + final List values = this.matchInParameterValuesWithInsertColumnsForBatch(args); + batchValues[i++] = values; + } + return this.executeInsertBatchInternal(batchValues, this.tableMetaDataContext.createInsertString(this.getGeneratedKeyNames()), this.tableMetaDataContext.createInsertTypes()); + } + + /** + * Method that provides execution of a batch insert using the passed in array of + * {@link SqlParameterSource}. + * + * @param batch array of SqlParameterSource with parameter names and values to be used in insert + * @return array of number of rows affected + */ + protected int[] doExecuteInsertBatch(final SqlParameterSource[] batch, final String insertString, final int[] insertTypes) { + this.checkCompiled(); + final List[] batchValues = new ArrayList[batch.length]; + int i = 0; + for (final SqlParameterSource parameterSource : batch) { + final List values = this.matchInParameterValuesWithInsertColumnsForBatch(parameterSource); + batchValues[i++] = values; + } + return this.executeInsertBatchInternal(batchValues, insertString, insertTypes); + } + + /** + * Method to execute the batch insert. + * + * @param batchValues the batch values + * @return the int[] + */ + protected int[] executeInsertBatchInternal(final List[] batchValues, final String insertString, final int[] insertTypes) { + OntimizeJdbcDaoSupport.logger.debug("Executing statement {} with batch of size: {}", insertString, batchValues.length); + return this.getJdbcTemplate().batchUpdate(insertString, new BatchPreparedStatementSetter() { + + @Override + public void setValues(final PreparedStatement ps, final int i) throws SQLException { + final List values = batchValues[i]; + OntimizeJdbcDaoSupport.this.setParameterValues(ps, values, insertTypes); + } + + @Override + public int getBatchSize() { + return batchValues.length; + } + }); + } + + /** + * Internal implementation for setting parameter values. + * + * @param preparedStatement the PreparedStatement + * @param values the values to be set + * @param columnTypes the column types + * @throws SQLException the sQL exception + */ + protected void setParameterValues(final PreparedStatement preparedStatement, final List values, final int[] columnTypes) throws SQLException { + + int colIndex = 0; + for (Object value : values) { + colIndex++; + if ((columnTypes == null) || (colIndex > columnTypes.length)) { + StatementCreatorUtils.setParameterValue(preparedStatement, colIndex, SqlTypeValue.TYPE_UNKNOWN, value); + } else { + final int sqlType = columnTypes[colIndex - 1]; + if (ObjectTools.isIn(sqlType, Types.BLOB, Types.BINARY, Types.VARBINARY) && ((value instanceof byte[]) || (value instanceof InputStream))) { + if (value instanceof byte[]) { + preparedStatement.setBytes(colIndex, (byte[]) value); + } else { + try { + // TODO esto no esta soportado por los drivers jdbc 4.0 + // TODO segun el driver puede ser que sea mas rapido llamar al metodo con la longitud + preparedStatement.setBlob(colIndex, (InputStream) value); + } catch (AbstractMethodError ex) { + OntimizeJdbcDaoSupport.logger.debug(null, ex); + try { + preparedStatement.setBinaryStream(colIndex, (InputStream) value, ((InputStream) value).available()); + } catch (IOException error) { + throw new SQLException(error); + } + } + } + } else if (value instanceof NullValue) { + // TODO At this point we could retrieve sqlType from ((NullValue)value).getSQLDataType() + // but it is preferable to use the sqlType retrieved from table metadata. + value = new SqlParameterValue(sqlType, null); + StatementCreatorUtils.setParameterValue(preparedStatement, colIndex, sqlType, value); + } else { + StatementCreatorUtils.setParameterValue(preparedStatement, colIndex, sqlType, value); + } + } + } + } + + /** + * Match the provided in parameter values with regitered parameters and parameters defined via + * metadata processing. + * + * @param parameterSource the parameter vakues provided as a {@link SqlParameterSource} + * @return Map with parameter names and values + */ + protected InsertMetaInfoHolder matchInParameterValuesWithInsertColumns(final SqlParameterSource parameterSource) { + return this.tableMetaDataContext.getInsertMetaInfo(parameterSource); + } + + /** + * Match the provided in parameter values with regitered parameters and parameters defined via + * metadata processing. + * + * @param args the parameter values provided in a Map + * @return Map with parameter names and values + */ + protected InsertMetaInfoHolder matchInParameterValuesWithInsertColumns(final Map args) { + return this.tableMetaDataContext.getInsertMetaInfo(args); + } + + /** + * Match the provided in parameter values with regitered parameters and parameters defined via + * metadata processing. + * + * @param parameterSource the parameter vakues provided as a {@link SqlParameterSource} + * @return Map with parameter names and values + */ + protected List matchInParameterValuesWithInsertColumnsForBatch(final SqlParameterSource parameterSource) { + return this.tableMetaDataContext.matchInParameterValuesWithInsertColumns(parameterSource); + } + + /** + * Match the provided in parameter values with regitered parameters and parameters defined via + * metadata processing. + * + * @param args the parameter values provided in a Map + * @return Map with parameter names and values + */ + protected List matchInParameterValuesWithInsertColumnsForBatch(final Map args) { + return this.tableMetaDataContext.matchInParameterValuesWithInsertColumns(args); + } + + /** + * Gets the schema table. + * + * @return the schema table + */ + protected String getSchemaTable() { + String sTableToUse = this.getTableName(); + if (this.getSchemaName() != null) { + sTableToUse = this.getSchemaName() + "." + sTableToUse; + } + return sTableToUse; + } + + /** + * Get the SQL statement builder. + * + * @return the statement builder + */ + public SQLStatementHandler getStatementHandler() { + return this.statementHandler; + } + + /** + * Establish SQL statement builder. + * + * @param statementHandler the new statement handler + */ + public void setStatementHandler(final SQLStatementHandler statementHandler) { + this.statementHandler = statementHandler; + } + + /** + * Gets the delete keys. + * + * @return the delete keys + */ + public List getDeleteKeys() { + return this.deleteKeys; + } + + /** + * Sets the delete keys. + * + * @param deleteKeys the new delete keys + */ + public void setDeleteKeys(final List deleteKeys) { + this.deleteKeys = deleteKeys; + } + + /** + * Gets the update keys. + * + * @return the update keys + */ + public List getUpdateKeys() { + return this.updateKeys; + } + + /** + * Sets the update keys. + * + * @param updateKeys the new update keys + */ + public void setUpdateKeys(final List updateKeys) { + this.updateKeys = updateKeys; + } + + /** + * Gets the application context. + * + * @return the application context + */ + public ApplicationContext getApplicationContext() { + return this.applicationContext; + } + + /* + * (non-Javadoc) + * + * @see org.springframework.context.ApplicationContextAware#setApplicationContext + * (org.springframework.context.ApplicationContext) + */ + + /** + * Sets the application context. + * + * @param applicationContext the new application context + * @throws BeansException the beans exception + */ + @Override + public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + /* + * (non-Javadoc) + * + * @see com.ontimize.jee.server.dao.IOntimizeDaoSupport#getCudProperties() + */ + @Override + public List getCudProperties() { + this.compile(); + List res = new ArrayList<>(); + for (TableParameterMetaData data : this.tableMetaDataContext.getTableParameters()) { + String name = data.getParameterName(); + int type = data.getSqlType(); + DaoProperty property = new DaoProperty(); + property.setSqlType(type); + property.setPropertyName(name); + res.add(property); + } + return res; + } + + /** + * Gets the table meta data context. + * + * @return the table meta data context + */ + public OntimizeTableMetaDataContext getTableMetaDataContext() { + if (!this.tableMetaDataContext.isProcessed()) { + this.compile(); + } + return this.tableMetaDataContext; + } + + protected OntimizeTableMetaDataContext createTableMetadataContext() { + return new OntimizeTableMetaDataContext(); + } + + @Override + protected JdbcTemplate createJdbcTemplate(DataSource dataSource) { + OntimizeJdbcDaoSupport.logger.trace("Creating new JdbcTemplate with fetchSize=1000"); + JdbcTemplate template = super.createJdbcTemplate(dataSource); + template.setFetchSize(1000); + OntimizeJdbcDaoSupport.logger.trace("Creating new JdbcTemplate has finally fetchSize=" + template.getFetchSize()); + return template; + } + + private Integer getColumnSQLType(final String column) { + if (!this.tableMetaDataContext.isProcessed()) { + this.compile(); + } + for (final TableParameterMetaData data : this.tableMetaDataContext.getTableParameters()) { + if (column.equalsIgnoreCase(data.getParameterName())) { + return data.getSqlType(); + } + } + return null; + } + + protected static class SimpleScrollablePreparedStatementCreator implements PreparedStatementCreator, SqlProvider { + + private final String sql; + + public SimpleScrollablePreparedStatementCreator(String sql) { + Assert.notNull(sql, "SQL must not be null"); + this.sql = sql; + } + + @Override + public PreparedStatement createPreparedStatement(Connection con) throws SQLException { + return con.prepareStatement(this.sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + } + + @Override + public String getSql() { + return this.sql; + } + } } \ No newline at end of file From 0ab6c985a6b1275b94f42073feab6021a5cb5165 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Thu, 18 Sep 2025 10:35:18 +0200 Subject: [PATCH 44/65] Use "isEmpty()" to check whether a "String" is empty or not. --- .../jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index b906f553..6ab5984e 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -387,8 +387,8 @@ public SQLStatementBuilder.SQLStatement composeQuerySql(final String queryId, fi } order = order.trim(); - sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_ORDER_CONCAT, order.length() == 0 ? "" : SQLStatementBuilder.COMMA + " " + order); - sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_ORDER, order.length() == 0 ? "" : SQLStatementBuilder.ORDER_BY + " " + order); + sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_ORDER_CONCAT, order.isEmpty() ? "" : SQLStatementBuilder.COMMA + " " + order); + sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_ORDER, order.isEmpty() ? "" : SQLStatementBuilder.ORDER_BY + " " + order); if (pageableInfo != null) { sqlTemplate = this.performPlaceHolderPagination(sqlTemplate, pageableInfo); } @@ -412,13 +412,13 @@ public SQLStatementBuilder.SQLStatement composeQuerySql(final String queryId, fi * @return The modified sqlTemplate with the placeholders replaced. */ public String applyWherePlaceholders(String sqlTemplate, String cond, List vValues, List vValuesTemp) { - Pair replaceAll = StringTools.replaceAll(sqlTemplate, OntimizeJdbcDaoSupport.PLACEHOLDER_WHERE_CONCAT, cond.length() == 0 ? "" : SQLStatementBuilder.AND + " " + cond); + Pair replaceAll = StringTools.replaceAll(sqlTemplate, OntimizeJdbcDaoSupport.PLACEHOLDER_WHERE_CONCAT, cond.isEmpty() ? "" : SQLStatementBuilder.AND + " " + cond); sqlTemplate = replaceAll.getFirst(); for (int i = 1; i < replaceAll.getSecond(); i++) { vValues.addAll(vValuesTemp); } - replaceAll = StringTools.replaceAll(sqlTemplate, OntimizeJdbcDaoSupport.PLACEHOLDER_WHERE, cond.length() == 0 ? "" : SQLStatementBuilder.WHERE + " " + cond); + replaceAll = StringTools.replaceAll(sqlTemplate, OntimizeJdbcDaoSupport.PLACEHOLDER_WHERE, cond.isEmpty() ? "" : SQLStatementBuilder.WHERE + " " + cond); sqlTemplate = replaceAll.getFirst(); for (int i = 1; i < replaceAll.getSecond(); i++) { vValues.addAll(vValuesTemp); From d0b770c6e7932bf99d568a89640d75c0cc52f658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Thu, 18 Sep 2025 10:45:15 +0200 Subject: [PATCH 45/65] Use built-in functions to format log messages --- .../jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index 6ab5984e..07c0df1c 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -899,7 +899,7 @@ public EntityResult innerDelete(final Map keysValues, final boolean safe) } if (keysValuesChecked.isEmpty()) { - OntimizeJdbcDaoSupport.logger.debug("Delete: Keys does not contain any pair key-value valid:" + keysValues); + OntimizeJdbcDaoSupport.logger.debug("Delete: Keys does not contain any pair key-value valid: {}", keysValues); throw new SQLWarningException(I18NNaming.M_IT_HAS_NOT_CHANGED_ANY_RECORD, null); } @@ -966,7 +966,7 @@ protected void checkUpdateKeys(final Map keysValues) { hValidKeysValues.put(element, keysValues.get(element)); } } - OntimizeJdbcDaoSupport.logger.debug(" Update valid keys values: Input: " + keysValues + " -> Result: " + hValidKeysValues); + OntimizeJdbcDaoSupport.logger.debug(" Update valid keys values: Input: {} -> Result: {}",keysValues, hValidKeysValues); return hValidKeysValues; } @@ -990,7 +990,7 @@ public Map getValidAttributes(final Map inputAttributesVal hValidKeysValues.put((String) oKey, oValue); } } - OntimizeJdbcDaoSupport.logger.debug(" Update valid attributes values: Input: " + inputAttributesValues + " -> Result: " + hValidKeysValues); + OntimizeJdbcDaoSupport.logger.debug(" Update valid attributes values: Input: {} -> Result: {}", inputAttributesValues, hValidKeysValues); return hValidKeysValues; } @@ -1146,7 +1146,7 @@ protected Map getValidQueryingKeysValues(Map inp hValidKeysValues.put(entry.getKey(), entry.getValue()); } } - OntimizeJdbcDaoSupport.logger.debug(" Query valid keys values: Input: " + inputKeysValues + " -> Result: " + hValidKeysValues); + OntimizeJdbcDaoSupport.logger.debug(" Query valid keys values: Input: {} -> Result: {}", inputKeysValues, hValidKeysValues); return hValidKeysValues; } @@ -2121,7 +2121,7 @@ protected JdbcTemplate createJdbcTemplate(DataSource dataSource) { OntimizeJdbcDaoSupport.logger.trace("Creating new JdbcTemplate with fetchSize=1000"); JdbcTemplate template = super.createJdbcTemplate(dataSource); template.setFetchSize(1000); - OntimizeJdbcDaoSupport.logger.trace("Creating new JdbcTemplate has finally fetchSize=" + template.getFetchSize()); + OntimizeJdbcDaoSupport.logger.trace("Creating new JdbcTemplate has finally fetchSize={}", template.getFetchSize()); return template; } From 513768215877946a29ebfb3af69271878b2872dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Thu, 18 Sep 2025 12:13:19 +0200 Subject: [PATCH 46/65] Change return type of applyTransformations method to List --- .../ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index 07c0df1c..48938d24 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -504,7 +504,7 @@ protected BeanPropertyRowMapper createRowMapper(final Class clazz) { * @param vValidAttributes the v valid attributes * @return the list */ - protected List applyTransformations(final QueryTemplateInformation templateInformation, final List vValidAttributes) { + protected List applyTransformations(final QueryTemplateInformation templateInformation, final List vValidAttributes) { final List ambiguousColumns = templateInformation.getAmbiguousColumns(); final List functionColumns = templateInformation.getFunctionColumns(); From b971bef79dd1914942a224ff53f58b62c03b7675 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Thu, 18 Sep 2025 12:24:01 +0200 Subject: [PATCH 47/65] Set ia-analyzer version --- ontimize-jee-common/pom.xml | 2 +- ontimize-jee-server-jdbc/pom.xml | 2 +- ontimize-jee-server-keycloak/pom.xml | 2 +- ontimize-jee-server-rest/pom.xml | 2 +- ontimize-jee-server/pom.xml | 2 +- ontimize-jee-webclient-addons/pom.xml | 2 +- pom.xml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ontimize-jee-common/pom.xml b/ontimize-jee-common/pom.xml index 705e9416..fb8240b8 100644 --- a/ontimize-jee-common/pom.xml +++ b/ontimize-jee-common/pom.xml @@ -4,7 +4,7 @@ com.ontimize.jee ontimize-jee - 5.15.0-SNAPSHOT + 5.14.0-SNAPSHOT ontimize-jee-common diff --git a/ontimize-jee-server-jdbc/pom.xml b/ontimize-jee-server-jdbc/pom.xml index 3306b6d2..51e2a7f5 100644 --- a/ontimize-jee-server-jdbc/pom.xml +++ b/ontimize-jee-server-jdbc/pom.xml @@ -4,7 +4,7 @@ com.ontimize.jee ontimize-jee - 5.15.0-SNAPSHOT + 5.14.0-SNAPSHOT ontimize-jee-server-jdbc diff --git a/ontimize-jee-server-keycloak/pom.xml b/ontimize-jee-server-keycloak/pom.xml index 47c0c5f3..2d718919 100644 --- a/ontimize-jee-server-keycloak/pom.xml +++ b/ontimize-jee-server-keycloak/pom.xml @@ -4,7 +4,7 @@ com.ontimize.jee ontimize-jee - 5.15.0-SNAPSHOT + 5.14.0-SNAPSHOT ontimize-jee-server-keycloak diff --git a/ontimize-jee-server-rest/pom.xml b/ontimize-jee-server-rest/pom.xml index 63f7cb9c..a78bcd61 100644 --- a/ontimize-jee-server-rest/pom.xml +++ b/ontimize-jee-server-rest/pom.xml @@ -4,7 +4,7 @@ com.ontimize.jee ontimize-jee - 5.15.0-SNAPSHOT + 5.14.0-SNAPSHOT ontimize-jee-server-rest diff --git a/ontimize-jee-server/pom.xml b/ontimize-jee-server/pom.xml index bc5ae8a8..50c314d4 100644 --- a/ontimize-jee-server/pom.xml +++ b/ontimize-jee-server/pom.xml @@ -4,7 +4,7 @@ com.ontimize.jee ontimize-jee - 5.15.0-SNAPSHOT + 5.14.0-SNAPSHOT ontimize-jee-server diff --git a/ontimize-jee-webclient-addons/pom.xml b/ontimize-jee-webclient-addons/pom.xml index f3b19433..7300a066 100644 --- a/ontimize-jee-webclient-addons/pom.xml +++ b/ontimize-jee-webclient-addons/pom.xml @@ -5,7 +5,7 @@ com.ontimize.jee ontimize-jee - 5.15.0-SNAPSHOT + 5.14.0-SNAPSHOT ontimize-jee-webclient-addons diff --git a/pom.xml b/pom.xml index f13bdb42..f3a0588e 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.ontimize.jee ontimize-jee - 5.15.0-SNAPSHOT + 5.14.0-SNAPSHOT pom Ontimize EE From d7c6241a1bf07affc61834a1dd576fd551293cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Thu, 18 Sep 2025 14:56:56 +0200 Subject: [PATCH 48/65] Refactor reader creation logic into a separate method --- .../dao/jdbc/OntimizeJdbcDaoSupport.java | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index 48938d24..35989b0b 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -1404,19 +1404,7 @@ protected void loadConfigurationFile(final String path, final String pathToPlace try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(path)) { Reader reader = null; - if (pathToPlaceHolder != null) { - try (InputStream isPlaceHolder = Thread.currentThread().getContextClassLoader().getResourceAsStream(pathToPlaceHolder)) { - final Properties prop = new Properties(); - if (isPlaceHolder != null) { - prop.load(isPlaceHolder); - } - - Map mapProperties = prop.stringPropertyNames().stream().collect(Collectors.toMap(Function.identity(), prop::getProperty)); - reader = new ReplaceTokensFilterReader(new InputStreamReader(is), mapProperties); - } - } else { - reader = new InputStreamReader(is); - } + reader = this.createReaderConfigurationFile(pathToPlaceHolder, is); JdbcEntitySetupType baseSetup = JAXB.unmarshal(reader, JdbcEntitySetupType.class); @@ -1453,6 +1441,24 @@ protected void loadConfigurationFile(final String path, final String pathToPlace } + protected Reader createReaderConfigurationFile(String pathToPlaceHolder, InputStream is) throws IOException { + Reader reader; + if (pathToPlaceHolder != null) { + try (InputStream isPlaceHolder = Thread.currentThread().getContextClassLoader().getResourceAsStream(pathToPlaceHolder)) { + final Properties prop = new Properties(); + if (isPlaceHolder != null) { + prop.load(isPlaceHolder); + } + + Map mapProperties = prop.stringPropertyNames().stream().collect(Collectors.toMap(Function.identity(), prop::getProperty)); + reader = new ReplaceTokensFilterReader(new InputStreamReader(is), mapProperties); + } + } else { + reader = new InputStreamReader(is); + } + return reader; + } + protected JdbcEntitySetupType checkDaoExtensions(JdbcEntitySetupType baseSetup, final String path, final String pathToPlaceHolder) { if (this.daoExtensionHelper == null) { return baseSetup; From 0243f0bb5ad3a88b19f7b85c672e4a141b7e576f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Fri, 19 Sep 2025 07:57:07 +0200 Subject: [PATCH 49/65] Refactor query setup logic into a separate method to reduce cognitive complexity --- .../dao/jdbc/OntimizeJdbcDaoSupport.java | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index 35989b0b..c16f0619 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -1417,15 +1417,7 @@ protected void loadConfigurationFile(final String path, final String pathToPlace this.setCatalogName(setupConfig.getCatalog()); this.setDeleteKeys(setupConfig.getDeleteKeys().getColumn()); this.setUpdateKeys(setupConfig.getUpdateKeys().getColumn()); - if (setupConfig.getQueries() != null) { - for (final QueryType query : setupConfig.getQueries().getQuery()) {// - this.addQueryTemplateInformation(query.getId(), query.getSentence().getValue(), // - query.getAmbiguousColumns() == null ? null : query.getAmbiguousColumns().getAmbiguousColumn(), // - query.getFunctionColumns() == null ? null : query.getFunctionColumns().getFunctionColumn(), // - query.getValidColumns() != null ? query.getValidColumns().getColumn() : new ArrayList(), // - query.getOrderColumns() == null ? null : query.getOrderColumns().getOrderColumn()); - } - } + this.setupsConfigureQueries(setupConfig); this.setGeneratedKeyName(setupConfig.getGeneratedKey()); this.setDataSource((DataSource) this.applicationContext.getBean(setupConfig.getDatasource())); this.setStatementHandler((SQLStatementHandler) this.applicationContext.getBean(setupConfig.getSqlhandler())); @@ -1441,6 +1433,22 @@ protected void loadConfigurationFile(final String path, final String pathToPlace } + /** + * Setups the queries defined in configuration file. + * @param setupConfig + */ + protected void setupsConfigureQueries(JdbcEntitySetupType setupConfig) { + if (setupConfig.getQueries() != null) { + for (final QueryType query : setupConfig.getQueries().getQuery()) {// + this.addQueryTemplateInformation(query.getId(), query.getSentence().getValue(), // + query.getAmbiguousColumns() == null ? null : query.getAmbiguousColumns().getAmbiguousColumn(), // + query.getFunctionColumns() == null ? null : query.getFunctionColumns().getFunctionColumn(), // + query.getValidColumns() != null ? query.getValidColumns().getColumn() : new ArrayList(), // + query.getOrderColumns() == null ? null : query.getOrderColumns().getOrderColumn()); + } + } + } + protected Reader createReaderConfigurationFile(String pathToPlaceHolder, InputStream is) throws IOException { Reader reader; if (pathToPlaceHolder != null) { From 2199e933efbffd2df1b60c22e1608ddfe21e1923 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Fri, 19 Sep 2025 08:14:56 +0200 Subject: [PATCH 50/65] Refactor set parameter values into a separate method to reduce cognitive complexity --- .../dao/jdbc/OntimizeJdbcDaoSupport.java | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index c16f0619..058097b6 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -1927,24 +1927,9 @@ protected void setParameterValues(final PreparedStatement preparedStatement, fin StatementCreatorUtils.setParameterValue(preparedStatement, colIndex, SqlTypeValue.TYPE_UNKNOWN, value); } else { final int sqlType = columnTypes[colIndex - 1]; - if (ObjectTools.isIn(sqlType, Types.BLOB, Types.BINARY, Types.VARBINARY) && ((value instanceof byte[]) || (value instanceof InputStream))) { - if (value instanceof byte[]) { - preparedStatement.setBytes(colIndex, (byte[]) value); - } else { - try { - // TODO esto no esta soportado por los drivers jdbc 4.0 - // TODO segun el driver puede ser que sea mas rapido llamar al metodo con la longitud - preparedStatement.setBlob(colIndex, (InputStream) value); - } catch (AbstractMethodError ex) { - OntimizeJdbcDaoSupport.logger.debug(null, ex); - try { - preparedStatement.setBinaryStream(colIndex, (InputStream) value, ((InputStream) value).available()); - } catch (IOException error) { - throw new SQLException(error); - } - } - } - } else if (value instanceof NullValue) { + if (ObjectTools.isIn(sqlType, Types.BLOB, Types.BINARY, Types.VARBINARY) && ((value instanceof byte[]) || (value instanceof InputStream))) + this.parametersIfTheyAreBinary(preparedStatement, value, colIndex); + else if (value instanceof NullValue) { // TODO At this point we could retrieve sqlType from ((NullValue)value).getSQLDataType() // but it is preferable to use the sqlType retrieved from table metadata. value = new SqlParameterValue(sqlType, null); @@ -1956,6 +1941,25 @@ protected void setParameterValues(final PreparedStatement preparedStatement, fin } } + protected void parametersIfTheyAreBinary(PreparedStatement preparedStatement, Object value, int colIndex) throws SQLException { + if (value instanceof byte[]) { + preparedStatement.setBytes(colIndex, (byte[]) value); + } else { + try { + // TODO esto no esta soportado por los drivers jdbc 4.0 + // TODO segun el driver puede ser que sea mas rapido llamar al metodo con la longitud + preparedStatement.setBlob(colIndex, (InputStream) value); + } catch (AbstractMethodError ex) { + OntimizeJdbcDaoSupport.logger.debug(null, ex); + try { + preparedStatement.setBinaryStream(colIndex, (InputStream) value, ((InputStream) value).available()); + } catch (IOException error) { + throw new SQLException(error); + } + } + } + } + /** * Match the provided in parameter values with regitered parameters and parameters defined via * metadata processing. From 4b1e4750cb8035d181a96d1236c68e8769ea53d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Fri, 19 Sep 2025 12:56:44 +0200 Subject: [PATCH 51/65] Refactor applyTransformations into a separate method to reduce cognitive complexity --- .../dao/jdbc/OntimizeJdbcDaoSupport.java | 81 +++++++++++-------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index 058097b6..9db25739 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -512,25 +512,10 @@ protected List applyTransformations(final QueryTemplateInformation templ for (final Object ob : vValidAttributes) { boolean transformed = false; if (ambiguousColumns != null) { - for (final AmbiguousColumnType ambiguosColumn : ambiguousColumns) { - if (ob.toString().equalsIgnoreCase(ambiguosColumn.getName())) { - final String dbName = ambiguosColumn.getDatabaseName() == null ? ambiguosColumn.getName() : ambiguosColumn.getDatabaseName(); - String sb = ambiguosColumn.getPrefix() + "." + dbName + SQLStatementBuilder.AS + ambiguosColumn.getName(); - res.add(sb); - transformed = true; - break; - } - } + transformed = this.transformWithAmbiguousColumns(ob, ambiguousColumns, res, transformed); } if (!transformed && (functionColumns != null)) { - for (final FunctionColumnType functionColumn : functionColumns) { - if (ob.toString().equalsIgnoreCase(functionColumn.getName())) { - String sb = SQLStatementBuilder.OPEN_PARENTHESIS + functionColumn.getValue() + SQLStatementBuilder.CLOSE_PARENTHESIS + SQLStatementBuilder.AS + functionColumn.getName(); - res.add(sb); - transformed = true; - break; - } - } + transformed = this.transformWithFunctionColumns(ob, functionColumns, res, transformed); } if (!transformed) { res.add(ob); @@ -539,6 +524,31 @@ protected List applyTransformations(final QueryTemplateInformation templ return res; } + private boolean transformWithFunctionColumns(Object ob, List functionColumns, List res, boolean transformed) { + for (final FunctionColumnType functionColumn : functionColumns) { + if (ob.toString().equalsIgnoreCase(functionColumn.getName())) { + String sb = SQLStatementBuilder.OPEN_PARENTHESIS + functionColumn.getValue() + SQLStatementBuilder.CLOSE_PARENTHESIS + SQLStatementBuilder.AS + functionColumn.getName(); + res.add(sb); + transformed = true; + break; + } + } + return transformed; + } + + private boolean transformWithAmbiguousColumns(Object ob, List ambiguousColumns, List res, boolean transformed) { + for (final AmbiguousColumnType ambiguosColumn : ambiguousColumns) { + if (ob.toString().equalsIgnoreCase(ambiguosColumn.getName())) { + final String dbName = ambiguosColumn.getDatabaseName() == null ? ambiguosColumn.getName() : ambiguosColumn.getDatabaseName(); + String sb = ambiguosColumn.getPrefix() + "." + dbName + SQLStatementBuilder.AS + ambiguosColumn.getName(); + res.add(sb); + transformed = true; + break; + } + } + return transformed; + } + /** * Apply template prefix. * @@ -555,22 +565,7 @@ protected Map applyTransformations(final QueryTemplateInformatio if (kvEntry.getKey() instanceof String) { String key = (String) kvEntry.getKey(); boolean transformed = false; - if ((SQLStatementBuilder.ExtendedSQLConditionValuesProcessor.EXPRESSION_KEY.equals(key) || SQLStatementBuilder.ExtendedSQLConditionValuesProcessor.FILTER_KEY.equals(key)) && (kvEntry.getValue() instanceof SQLStatementBuilder.BasicExpression)) { - res.put(key, this.applyTransformationsToBasicExpression((SQLStatementBuilder.BasicExpression) kvEntry.getValue(), ambiguousColumns, functionColumns)); - transformed = true; - } else { - String resolvedAmbiguousColumn = this.resolveAmbiguousColumn(key, ambiguousColumns); - if (resolvedAmbiguousColumn != null) { - res.put(resolvedAmbiguousColumn, kvEntry.getValue()); - transformed = true; - } else { - String resolvedFunctionColumn = this.resolveFunctionColumn(key, functionColumns); - if (resolvedFunctionColumn != null) { - res.put(resolvedFunctionColumn, kvEntry.getValue()); - transformed = true; - } - } - } + transformed = this.transformedStringKey(kvEntry, key, res, ambiguousColumns, functionColumns, transformed); if (!transformed) { res.put(key, kvEntry.getValue()); } @@ -581,6 +576,26 @@ protected Map applyTransformations(final QueryTemplateInformatio return res; } + private boolean transformedStringKey(Map.Entry kvEntry, String key, Map res, List ambiguousColumns, List functionColumns, boolean transformed) { + if ((SQLStatementBuilder.ExtendedSQLConditionValuesProcessor.EXPRESSION_KEY.equals(key) || SQLStatementBuilder.ExtendedSQLConditionValuesProcessor.FILTER_KEY.equals(key)) && (kvEntry.getValue() instanceof SQLStatementBuilder.BasicExpression)) { + res.put(key, this.applyTransformationsToBasicExpression((SQLStatementBuilder.BasicExpression) kvEntry.getValue(), ambiguousColumns, functionColumns)); + transformed = true; + } else { + String resolvedAmbiguousColumn = this.resolveAmbiguousColumn(key, ambiguousColumns); + if (resolvedAmbiguousColumn != null) { + res.put(resolvedAmbiguousColumn, kvEntry.getValue()); + transformed = true; + } else { + String resolvedFunctionColumn = this.resolveFunctionColumn(key, functionColumns); + if (resolvedFunctionColumn != null) { + res.put(resolvedFunctionColumn, kvEntry.getValue()); + transformed = true; + } + } + } + return transformed; + } + protected List applyOrderColumns(final List sort, final List orderColumns) { List vResult = new ArrayList<>(); if ((sort != null) && (sort.size() > 0)) { From 00b72dec732d508bc06bf4ced7f53886558f3ca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Fri, 19 Sep 2025 13:04:17 +0200 Subject: [PATCH 52/65] Refactor composeQuerySql into a separate method to reduce cognitive complexity --- .../dao/jdbc/OntimizeJdbcDaoSupport.java | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index 9db25739..b9055f40 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -340,11 +340,7 @@ public SQLStatementBuilder.SQLStatement composeQuerySql(final String queryId, fi vValidAttributes = this.getValidAttributes(vValidAttributes, null); CheckingTools.failIf(vValidAttributes.isEmpty(), "NO_ATTRIBUTES_TO_QUERY"); // use table - if (pageableInfo != null) { - stSQL = this.getStatementHandler().createSelectQuery(this.getSchemaTable(), new ArrayList<>(vValidAttributes), new HashMap<>(kvValidKeysValues), new ArrayList<>(), new ArrayList<>(sort == null ? Collections.emptyList() : sort), pageableInfo.getRecordNumber(), pageableInfo.getStartIndex()); - } else { - stSQL = this.getStatementHandler().createSelectQuery(this.getSchemaTable(), new ArrayList<>(vValidAttributes), new HashMap<>(kvValidKeysValues), new ArrayList<>(), new ArrayList<>(sort == null ? Collections.emptyList() : sort)); - } + stSQL = this.getSqlStatement(sort, pageableInfo, vValidAttributes, kvValidKeysValues); } else { List validColumns = queryTemplateInformation.getValidColumns(); @@ -379,21 +375,7 @@ public SQLStatementBuilder.SQLStatement composeQuerySql(final String queryId, fi sqlTemplate = this.applyWherePlaceholders(sqlTemplate, cond, vValues, vValuesTemp); // Order by - List orderColumns = queryTemplateInformation.getOrderColumns(); - List sortColumns = this.applyOrderColumns(sort, orderColumns); - String order = this.getStatementHandler().createSortStatement(sortColumns, false); - if (order.length() > 0) { - order = order.substring(SQLStatementBuilder.ORDER_BY.length()); - } - order = order.trim(); - - sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_ORDER_CONCAT, order.isEmpty() ? "" : SQLStatementBuilder.COMMA + " " + order); - sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_ORDER, order.isEmpty() ? "" : SQLStatementBuilder.ORDER_BY + " " + order); - if (pageableInfo != null) { - sqlTemplate = this.performPlaceHolderPagination(sqlTemplate, pageableInfo); - } - sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_SCHEMA, this.getSchemaName()); - stSQL = new SQLStatementBuilder.SQLStatement(sqlTemplate, vValues); + stSQL = this.getSqlStatementOrderBy(sort, pageableInfo, queryTemplateInformation, sqlTemplate, vValues); } if (queryAdapter != null) { stSQL = queryAdapter.adaptQuery(stSQL, this, keysValues, kvValidKeysValues, attributes, vValidAttributes, sort, queryId); @@ -402,6 +384,36 @@ public SQLStatementBuilder.SQLStatement composeQuerySql(final String queryId, fi return stSQL; } + public SQLStatementBuilder.SQLStatement getSqlStatementOrderBy(List sort, PageableInfo pageableInfo, QueryTemplateInformation queryTemplateInformation, String sqlTemplate, List vValues) { + SQLStatementBuilder.SQLStatement stSQL; + List orderColumns = queryTemplateInformation.getOrderColumns(); + List sortColumns = this.applyOrderColumns(sort, orderColumns); + String order = this.getStatementHandler().createSortStatement(sortColumns, false); + if (!order.isEmpty()) { + order = order.substring(SQLStatementBuilder.ORDER_BY.length()); + } + order = order.trim(); + + sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_ORDER_CONCAT, order.isEmpty() ? "" : SQLStatementBuilder.COMMA + " " + order); + sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_ORDER, order.isEmpty() ? "" : SQLStatementBuilder.ORDER_BY + " " + order); + if (pageableInfo != null) { + sqlTemplate = this.performPlaceHolderPagination(sqlTemplate, pageableInfo); + } + sqlTemplate = sqlTemplate.replaceAll(OntimizeJdbcDaoSupport.PLACEHOLDER_SCHEMA, this.getSchemaName()); + stSQL = new SQLStatementBuilder.SQLStatement(sqlTemplate, vValues); + return stSQL; + } + + public SQLStatementBuilder.SQLStatement getSqlStatement(List sort, PageableInfo pageableInfo, List vValidAttributes, Map kvValidKeysValues) { + SQLStatementBuilder.SQLStatement stSQL; + if (pageableInfo != null) { + stSQL = this.getStatementHandler().createSelectQuery(this.getSchemaTable(), new ArrayList<>(vValidAttributes), new HashMap<>(kvValidKeysValues), new ArrayList<>(), new ArrayList<>(sort == null ? Collections.emptyList() : sort), pageableInfo.getRecordNumber(), pageableInfo.getStartIndex()); + } else { + stSQL = this.getStatementHandler().createSelectQuery(this.getSchemaTable(), new ArrayList<>(vValidAttributes), new HashMap<>(kvValidKeysValues), new ArrayList<>(), new ArrayList<>(sort == null ? Collections.emptyList() : sort)); + } + return stSQL; + } + /** * Replaces the WHERE placeholders in the SQL template and updates the values. * From b0de81b843749c177a0aa6f977f06287d958f888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Fri, 19 Sep 2025 13:05:41 +0200 Subject: [PATCH 53/65] Refactor getValidAttributes into a separate method to reduce cognitive complexity --- .../dao/jdbc/OntimizeJdbcDaoSupport.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java index b9055f40..a56ad74f 100644 --- a/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java +++ b/ontimize-jee-server-jdbc/src/main/java/com/ontimize/jee/server/dao/jdbc/OntimizeJdbcDaoSupport.java @@ -1121,13 +1121,7 @@ public List getValidAttributes(final List attributes, List validCo if ((ob instanceof String) || (ob instanceof DBFunctionName)) { boolean isValid = true; if (ob instanceof String) { - if (SQLStatementBuilder.ExtendedSQLConditionValuesProcessor.EXPRESSION_KEY.equals(ob)) { - isValid = true; - } else if (!inputValidColumns.isEmpty() && !inputValidColumns.contains(ob)) { - isValid = false; - } else { - isValid = this.isColumnNameValid((String) ob); - } + isValid = this.isInstanceOfStringValid(ob, inputValidColumns); } if (isValid) { validAttributes.add(ob); @@ -1137,6 +1131,18 @@ public List getValidAttributes(final List attributes, List validCo return validAttributes; } + public boolean isInstanceOfStringValid(Object ob, List inputValidColumns) { + boolean isValid; + if (SQLStatementBuilder.ExtendedSQLConditionValuesProcessor.EXPRESSION_KEY.equals(ob)) { + isValid = true; + } else if (!inputValidColumns.isEmpty() && !inputValidColumns.contains(ob)) { + isValid = false; + } else { + isValid = this.isColumnNameValid((String) ob); + } + return isValid; + } + /** * Checks if is column name valid. * From 691c4cfe6b06eab93e9554b8de6244b7681ad854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Fri, 19 Sep 2025 14:01:38 +0200 Subject: [PATCH 54/65] Refactor getBeanForName to simplify service retrieval logic, because expression always evaluates to "true" --- .../jee/webclient/export/util/ApplicationContextUtils.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/export/util/ApplicationContextUtils.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/export/util/ApplicationContextUtils.java index 8b277309..97659d4c 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/export/util/ApplicationContextUtils.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/export/util/ApplicationContextUtils.java @@ -84,9 +84,7 @@ public void setApplicationContext(ApplicationContext applicationContext) throws private Object getBeanForName(final String beanName) { Object bean = null; ORestController oRestController = applicationContext.getBean(beanName, ORestController.class); - if (oRestController != null) { - bean = oRestController.getService(); - } + bean = oRestController.getService(); return bean; } private Object getBean(final Class beanClazz) { From cd56e053bd75fbdd9d381b5b18c8a0236b69522e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Fri, 19 Sep 2025 14:28:26 +0200 Subject: [PATCH 55/65] Refactor SharePreferencesBeanDefinitionParser to add null check for item before accessing child elements --- .../SharePreferencesBeanDefinitionParser.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/ontimize-jee-server/src/main/java/com/ontimize/jee/server/spring/namespace/SharePreferencesBeanDefinitionParser.java b/ontimize-jee-server/src/main/java/com/ontimize/jee/server/spring/namespace/SharePreferencesBeanDefinitionParser.java index 2ec4177f..8f019a6d 100644 --- a/ontimize-jee-server/src/main/java/com/ontimize/jee/server/spring/namespace/SharePreferencesBeanDefinitionParser.java +++ b/ontimize-jee-server/src/main/java/com/ontimize/jee/server/spring/namespace/SharePreferencesBeanDefinitionParser.java @@ -64,17 +64,18 @@ protected void doParse(Element element, ParserContext ctx, BeanDefinitionBuilder // Set the directory property Element item = DomUtils.getChildElementByTagName(element, SharePreferencesBeanDefinitionParser.SHARE_PREF_ENGINE_PROPERTY); - Element child = DomUtils.getChildElements(item).get(0); Object engine = null; - - if (SharePreferencesBeanDefinitionParser.SHARE_PREF_DATABASE_CONFIGURATION_PROPERTY - .equals(child.getLocalName())) { - final ParserContext nestedCtx = new ParserContext(ctx.getReaderContext(), ctx.getDelegate(), - builder.getBeanDefinition()); - engine = new DatabaseSharePreferencesParser().parse(child, nestedCtx); - } else { - engine = DefinitionParserUtil.parseNode(child, ctx, builder.getBeanDefinition(), - element.getAttribute(SharePreferencesBeanDefinitionParser.SCOPE), false); + if (item != null) { + Element child = DomUtils.getChildElements(item).get(0); + if (SharePreferencesBeanDefinitionParser.SHARE_PREF_DATABASE_CONFIGURATION_PROPERTY + .equals(child.getLocalName())) { + final ParserContext nestedCtx = new ParserContext(ctx.getReaderContext(), ctx.getDelegate(), + builder.getBeanDefinition()); + engine = new DatabaseSharePreferencesParser().parse(child, nestedCtx); + } else { + engine = DefinitionParserUtil.parseNode(child, ctx, builder.getBeanDefinition(), + element.getAttribute(SharePreferencesBeanDefinitionParser.SCOPE), false); + } } builder.addPropertyValue(SharePreferencesBeanDefinitionParser.ENGINE, engine); builder.setLazyInit(true); From a097db9f7436fc566d04094d7276e1ef3cb83123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Fri, 19 Sep 2025 14:38:30 +0200 Subject: [PATCH 56/65] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e729a65a..267768d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,12 +9,14 @@ ## [Unreleased] ### Changed +- **ApplicationContextUtils**: Simplified the getBeanForName method for more streamlined service retrieval. +- **DefaultDaoExtensionHelper**: Improved property placeholder handling with Java streams for type safety and clarity, addressing SonarQube findings. - **Log4j2LoggerHelper**: Clarified variable names, added null checks, enhanced type safety, and improved Javadoc documentation. - **LogbackLoggerHelper**: Improved Javadoc documentation and suppress false positives on Sonar. -- **XMLClientUtilities**: Refactored string concatenation to use StringBuilder for improved readability and performance. - **OntimizeJdbcDaoSupport**: Refactored SQL placeholder and property handling, introduced a helper method, corrected key retrieval logic, and improved type safety using Java streams to resolve SonarQube issues. - **OntimizeJdbcDaoSupportTest**: Updated mocks to match the modified method signatures. -- **DefaultDaoExtensionHelper**: Improved property placeholder handling with Java streams for type safety and clarity, addressing SonarQube findings. +- **SharePreferencesBeanDefinitionParser**: Added null check and refactored engine parsing logic for safer and clearer configuration handling. +- **XMLClientUtilities**: Refactored string concatenation to use StringBuilder for improved readability and performance. ## [5.13.0] - 2025-09-11 ### Added ✔️ * **OntimizeJdbcDaoSupport**: Created executeSQLStatement() to use DDL statements. #175 From fa69231264fe4ec00d68bdcf1d2e7f088be8fdf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Mon, 6 Oct 2025 16:36:52 +0200 Subject: [PATCH 57/65] Refactor ApplicationContextUtils to remove unnecessary null check for serviceBean, because getBean throws an Exception if serviceBean is null in L68 --- .../jee/webclient/export/util/ApplicationContextUtils.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/export/util/ApplicationContextUtils.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/export/util/ApplicationContextUtils.java index 97659d4c..1e99bffa 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/export/util/ApplicationContextUtils.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/export/util/ApplicationContextUtils.java @@ -67,11 +67,7 @@ public Object getServiceBean(final String serviceName , final String servicePath if(serviceBean == null) { serviceBean = this.applicationContext.getBean(serviceName.concat("Service")); } - - if(serviceBean == null) { - throw new ExportException("Impossible to retrieve service to query data"); - } - + return serviceBean; } From 963b4a86ce8bf5dfc34ca6645d56b492e6237b0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Mon, 6 Oct 2025 16:51:36 +0200 Subject: [PATCH 58/65] Refactor getServiceBean to handle exceptions and improve readability. Throws custom exception on BeansException --- .../export/util/ApplicationContextUtils.java | 74 ++++++++++--------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/export/util/ApplicationContextUtils.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/export/util/ApplicationContextUtils.java index 1e99bffa..a134a7cb 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/export/util/ApplicationContextUtils.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/export/util/ApplicationContextUtils.java @@ -27,48 +27,52 @@ public ApplicationContextUtils() { public Object getServiceBean(final String serviceName , final String servicePath) throws ExportException { - Object serviceBean = null; - - //Method 1. Retrieve bean from controller 'path' - if(!StringUtils.isBlank(servicePath)) { - RequestMappingHandlerMapping requestMappingHandlerMapping = applicationContext - .getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class); - Map requestMap = requestMappingHandlerMapping.getHandlerMethods(); + try { + Object serviceBean = null; - List requestMapHandlerMethodList = requestMap.keySet().stream() - .filter(key -> key.getActivePatternsCondition().toString().equals("[" + servicePath + "/{name}/search]")) - .map(requestMap::get) - .collect(Collectors.toList()); + //Method 1. Retrieve bean from controller 'path' + if(!StringUtils.isBlank(servicePath)) { + RequestMappingHandlerMapping requestMappingHandlerMapping = applicationContext + .getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class); + Map requestMap = requestMappingHandlerMapping.getHandlerMethods(); - if (requestMapHandlerMethodList.size() == 1) { - Class restControllerBeanType = requestMapHandlerMethodList.get(0).getBeanType(); - serviceBean = getBean(restControllerBeanType); + List requestMapHandlerMethodList = requestMap.keySet().stream() + .filter(key -> key.getActivePatternsCondition().toString().equals("[" + servicePath + "/{name}/search]")) + .map(requestMap::get) + .collect(Collectors.toList()); + + if (requestMapHandlerMethodList.size() == 1) { + Class restControllerBeanType = requestMapHandlerMethodList.get(0).getBeanType(); + serviceBean = getBean(restControllerBeanType); + } } - } - - //Method 2. Retrieve controller from service name and then the service bean - if(serviceBean == null && !StringUtils.isBlank(serviceName)) { - String[] beanNamesForType = applicationContext.getBeanNamesForType(ORestController.class); - List restControllerNames = findCandidates(serviceName, beanNamesForType); - - if(restControllerNames.size() > 0) { - if(restControllerNames.size() == 1) { - serviceBean = getBeanForName(restControllerNames.get(0)); - } else { - String beanName = this.fitBestControllerName(serviceName, restControllerNames); - if(!StringUtils.isBlank(beanName)) { - serviceBean = getBeanForName(beanName); + + //Method 2. Retrieve controller from service name and then the service bean + if(serviceBean == null && !StringUtils.isBlank(serviceName)) { + String[] beanNamesForType = applicationContext.getBeanNamesForType(ORestController.class); + List restControllerNames = findCandidates(serviceName, beanNamesForType); + + if(restControllerNames.size() > 0) { + if(restControllerNames.size() == 1) { + serviceBean = getBeanForName(restControllerNames.get(0)); + } else { + String beanName = this.fitBestControllerName(serviceName, restControllerNames); + if(!StringUtils.isBlank(beanName)) { + serviceBean = getBeanForName(beanName); + } } } } - } - - // Method 3. Retrieve bean from service name - if(serviceBean == null) { - serviceBean = this.applicationContext.getBean(serviceName.concat("Service")); - } - return serviceBean; + // Method 3. Retrieve bean from service name + if(serviceBean == null) { + serviceBean = this.applicationContext.getBean(serviceName.concat("Service")); + } + + return serviceBean; + } catch (BeansException e) { + throw new ExportException("Impossible to retrieve service to query data", e); + } } @Override From f5cd19e03ac1b660f14c6001bb1c80b16274ed2e Mon Sep 17 00:00:00 2001 From: adrian-carneiro-imatia Date: Fri, 17 Oct 2025 11:48:43 +0200 Subject: [PATCH 59/65] Change prompts --- .../webclient/openai/naming/OpenAINaming.java | 34 +++++++++++++++++-- .../service/OpenAiImageProcessorService.java | 21 ++++++++---- .../jee/webclient/openai/util/Utils.java | 10 ++++-- 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/naming/OpenAINaming.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/naming/OpenAINaming.java index d088bc32..ce70c459 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/naming/OpenAINaming.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/naming/OpenAINaming.java @@ -18,8 +18,38 @@ public class OpenAINaming { public static final String COMPLETIONS_URL = "https://api.openai.com/v1/chat/completions"; public static final String CHOICES = "choices"; public static final String MESSAGE = "message"; - public static final String INITIAL_PROMPT_FORMAT = "Analiza la imagen adjunta. %s\n\nDevuelve la información en el siguiente formato JSON:\n%s"; - public static final String RETRY_PROMPT_FORMAT = "La respuesta anterior no cumple con el formato esperado. El error fue:\n%s\n\nVuelve a intentarlo. La estructura esperada es:\n%s"; + public static final String INITIAL_PROMPT_FORMAT = + "Tu tarea es procesar la siguiente imagen y devolver la información estructurada en el formato JSON que " + + "se indica más abajo.\n\n" + + "=== INSTRUCCIONES DE CONTEXTO ===\n" + + "%s\n\n" + + "=== ESTRUCTURA ESPERADA ===\n" + + "Devuelve únicamente un JSON que siga esta estructura:\n%s\n\n" + + "IMPORTANTE:\n" + + "No incluyas explicaciones ni comentarios.\n" + + "Usa null si no puedes identificar un valor.\n" + + "Respeta el tipo de dato especificado: si se espera un número o una fecha, devuélvelo " + + "correctamente formateado.\n" + + "Asegúrate de que el JSON es válido y parseable."; + public static final String RETRY_PROMPT_FORMAT = + "El siguiente JSON no cumple con la estructura esperada ni con las validaciones. Corrige los errores y " + + "vuelve a generar solo el JSON corregido.\n\n" + + "=== INSTRUCCIONES DE CONTEXTO ===\n" + + "%s\n\n" + + "=== ESTRUCTURA ESPERADA ===\n" + + "%s\n\n" + + "=== RESPUESTA ANTERIOR CON ERRORES ===\n" + + "%s\n\n" + + "=== ERRORES DETECTADOS ===\n" + + "%s\n\n" + + "Por favor, genera una nueva versión del JSON que sea válida, esté bien formada y siga " + + "exactamente la estructura definida.\n\n" + + "RECUERDA:\n" + + "No incluyas explicaciones ni comentarios.\n" + + "Usa null si no puedes identificar un valor.\n" + + "Respeta el tipo de dato especificado: si se espera un número o una fecha, devuélvelo " + + "correctamente formateado.\n" + + "Asegúrate de que el JSON es válido y parseable."; public static final String OPENAI_API_ERROR = "OpenAI API error: "; public static final String OPENAI_API_NO_JSON_ERROR = "No se encontró JSON en la cadena de entrada"; public static final String OPENAI_API_SCHEMA_GENERATION_ERROR = "Error generando schema: "; diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java index 9e9777c1..5091d088 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java @@ -38,7 +38,8 @@ public ProcessResult processImage(ProcessRequest request) { MultipartFile file = request.getFile(); Class outputClass = request.getOutputClass(); - SchemaGeneratorConfigBuilder cfgBuilder = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2019_09, OptionPreset.PLAIN_JSON); + SchemaGeneratorConfigBuilder cfgBuilder = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2019_09, + OptionPreset.PLAIN_JSON); cfgBuilder.forTypesInGeneral().withAdditionalPropertiesResolver(scope -> null); SchemaGenerator generator = new SchemaGenerator(cfgBuilder.build()); @@ -58,11 +59,14 @@ public ProcessResult processImage(ProcessRequest request) { return new ProcessResult<>(null, errors, actualTry); } + ProcessResult parsedResult = null; + while (actualTry < request.getRetries()) { try { - String prompt = Utils.buildPrompt(request.getPrompt(), schemaStr, actualTry > 0 ? errors.get(errors.size() - 1) : null); + String prompt = Utils.buildPrompt(request.getPrompt(), schemaStr, parsedResult, errors); - String responseJsonRaw = callVisionApi(prompt, file, request.getModel(), request.getMaxTokens(), request.getTemperature()); + String responseJsonRaw = callVisionApi(prompt, file, request.getModel(), request.getMaxTokens(), + request.getTemperature()); String responseJson = JsonSchemaValidator.extractRawJson(responseJsonRaw); if (responseJson == null || responseJson.isBlank()) { @@ -72,7 +76,10 @@ public ProcessResult processImage(ProcessRequest request) { jsonSchemaValidator.validate(responseJson, schemaStr); T result = objectMapper.readValue(responseJson, outputClass); - return new ProcessResult<>(result, errors, actualTry); + + parsedResult = new ProcessResult<>(result, errors, actualTry); + + return parsedResult; } catch (Exception e) { errors.add(e.getMessage()); @@ -82,7 +89,8 @@ public ProcessResult processImage(ProcessRequest request) { return new ProcessResult<>(null, errors, actualTry); } - private String callVisionApi(String promptText, MultipartFile image, String model, int maxTokens, double temperature) throws Exception { + private String callVisionApi(String promptText, MultipartFile image, String model, int maxTokens, + double temperature) throws Exception { HttpEntity> request = prepareRequest(promptText, image, model, maxTokens, temperature); RestTemplate restTemplate = new RestTemplate(); ResponseEntity response = restTemplate.postForEntity(COMPLETIONS_URL, request, String.class); @@ -92,7 +100,8 @@ private String callVisionApi(String promptText, MultipartFile image, String mode return objectMapper.readTree(response.getBody()).path(CHOICES).get(0).path(MESSAGE).path(CONTENT).asText(); } - private HttpEntity> prepareRequest(String promptText, MultipartFile image, String model, int maxTokens, double temperature) throws IOException { + private HttpEntity> prepareRequest(String promptText, MultipartFile image, String model, + int maxTokens, double temperature) throws IOException { byte[] imageBytes = image.getBytes(); String base64Image = Base64.getEncoder().encodeToString(imageBytes); diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/Utils.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/Utils.java index 32371f1c..9dade757 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/Utils.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/Utils.java @@ -1,16 +1,20 @@ package com.ontimize.jee.webclient.openai.util; +import com.ontimize.jee.webclient.openai.model.ProcessResult; import org.springframework.stereotype.Component; +import java.util.List; + import static com.ontimize.jee.webclient.openai.naming.OpenAINaming.INITIAL_PROMPT_FORMAT; import static com.ontimize.jee.webclient.openai.naming.OpenAINaming.RETRY_PROMPT_FORMAT; @Component public class Utils { - public static String buildPrompt(String userPrompt, String jsonSchema, String error) { - return error == null + public static String buildPrompt(String userPrompt, String jsonSchema, ProcessResult processResult, + List errors) { + return processResult == null ? String.format(INITIAL_PROMPT_FORMAT, userPrompt, jsonSchema) - : String.format(RETRY_PROMPT_FORMAT, error, jsonSchema); + : String.format(RETRY_PROMPT_FORMAT, userPrompt, jsonSchema, processResult, errors); } } From d8363e2096bae921659e09d1eef0f4ca04e2ebc7 Mon Sep 17 00:00:00 2001 From: adrian-carneiro-imatia Date: Wed, 22 Oct 2025 17:19:14 +0200 Subject: [PATCH 60/65] Fix json scheme annotations, fix analyzeimage --- .../service/OpenAiImageProcessorService.java | 35 +++++------------- .../openai/util/JsonSchemaValidator.java | 28 ++++++++++----- .../jee/webclient/openai/util/Utils.java | 36 +++++++++++++++++-- 3 files changed, 61 insertions(+), 38 deletions(-) diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java index 5091d088..2586cec1 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java @@ -1,11 +1,6 @@ package com.ontimize.jee.webclient.openai.service; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.victools.jsonschema.generator.OptionPreset; -import com.github.victools.jsonschema.generator.SchemaGenerator; -import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder; -import com.github.victools.jsonschema.generator.SchemaVersion; import com.ontimize.jee.webclient.openai.model.ProcessRequest; import com.ontimize.jee.webclient.openai.model.ProcessResult; import com.ontimize.jee.webclient.openai.util.JsonSchemaValidator; @@ -37,29 +32,18 @@ public ProcessResult processImage(ProcessRequest request) { List errors = new ArrayList<>(); MultipartFile file = request.getFile(); Class outputClass = request.getOutputClass(); - - SchemaGeneratorConfigBuilder cfgBuilder = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2019_09, - OptionPreset.PLAIN_JSON); - cfgBuilder.forTypesInGeneral().withAdditionalPropertiesResolver(scope -> null); - - SchemaGenerator generator = new SchemaGenerator(cfgBuilder.build()); - JsonNode jsonSchema; - try { - jsonSchema = generator.generateSchema(outputClass); - } catch (Exception e) { - errors.add(OPENAI_API_SCHEMA_GENERATION_ERROR + e.getMessage()); - return new ProcessResult<>(null, errors, actualTry); - } + ObjectMapper objectMapper = new ObjectMapper(); String schemaStr; try { - schemaStr = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonSchema); + schemaStr = Utils.generateFullSchemaJson(outputClass); } catch (Exception e) { - errors.add(OPENAI_API_SCHEMA_SERIALIZATION_ERROR + e.getMessage()); + errors.add(OPENAI_API_SCHEMA_GENERATION_ERROR + e.getMessage()); return new ProcessResult<>(null, errors, actualTry); } - ProcessResult parsedResult = null; + String parsedResult = null; + ProcessResult finalResult = null; while (actualTry < request.getRetries()) { try { @@ -73,20 +57,19 @@ public ProcessResult processImage(ProcessRequest request) { throw new IllegalStateException(OPENAI_API_NO_JSON_ERROR); } + parsedResult = responseJsonRaw; jsonSchemaValidator.validate(responseJson, schemaStr); T result = objectMapper.readValue(responseJson, outputClass); - - parsedResult = new ProcessResult<>(result, errors, actualTry); - - return parsedResult; + finalResult = new ProcessResult<>(result, new ArrayList<>(errors), actualTry); + break; } catch (Exception e) { errors.add(e.getMessage()); actualTry++; } } - return new ProcessResult<>(null, errors, actualTry); + return finalResult != null ? finalResult : new ProcessResult<>(null, errors, actualTry); } private String callVisionApi(String promptText, MultipartFile image, String model, int maxTokens, diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java index dce6114b..4e1123d1 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java @@ -11,16 +11,26 @@ import org.springframework.stereotype.Component; import static com.ontimize.jee.webclient.openai.naming.OpenAINaming.OPENAI_API_NO_JSON_ERROR; -import static com.ontimize.jee.webclient.openai.naming.OpenAINaming.PROPERTIES; @Component public class JsonSchemaValidator { - private static final ObjectMapper LENIENT = new ObjectMapper().configure(JsonParser.Feature.ALLOW_COMMENTS, true).configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true).configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true).configure(JsonParser.Feature.ALLOW_NUMERIC_LEADING_ZEROS, true).configure(JsonParser.Feature.ALLOW_TRAILING_COMMA, true); + private static final ObjectMapper LENIENT = new ObjectMapper().configure(JsonParser.Feature.ALLOW_COMMENTS, true) + .configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true) + .configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true) + .configure(JsonParser.Feature.ALLOW_NUMERIC_LEADING_ZEROS, true) + .configure(JsonParser.Feature.ALLOW_TRAILING_COMMA, true); private final ObjectMapper mapper = new ObjectMapper(); public static String extractRawJson(String content) { - if (content == null) return null; - String s = content.replace("\uFEFF", "").replace('\u201C', '"').replace('\u201D', '"').replace('\u2018', '\'').replace('\u2019', '\'').trim(); + if (content == null) { + return null; + } + String s = content.replace("\uFEFF", "") + .replace('\u201C', '"') + .replace('\u201D', '"') + .replace('\u2018', '\'') + .replace('\u2019', '\'') + .trim(); if (s.startsWith("```")) { int first = s.indexOf('\n'); @@ -50,13 +60,13 @@ public void validate(Object dataObject, String schemaJson) { String raw = (dataObject instanceof String) ? (String) dataObject : mapper.writeValueAsString(dataObject); String extracted = extractRawJson(raw); - if (extracted == null) throw new IllegalArgumentException(OPENAI_API_NO_JSON_ERROR); + if (extracted == null) { + throw new IllegalArgumentException(OPENAI_API_NO_JSON_ERROR); + } JsonNode node = LENIENT.readTree(extracted); String normalized = mapper.writeValueAsString(node); - JSONObject schemaObj = new JSONObject(schemaJson); - JSONObject rawSchema = schemaObj.getJSONObject(PROPERTIES); - JSONObject jsonSchema = new JSONObject(mapper.writeValueAsString(rawSchema)); - Schema schema = SchemaLoader.load(jsonSchema); + JSONObject rawSchema = new JSONObject(schemaJson); + Schema schema = SchemaLoader.load(rawSchema); if (normalized.trim().startsWith("{")) { schema.validate(new JSONObject(normalized)); } else { diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/Utils.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/Utils.java index 9dade757..959926c5 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/Utils.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/Utils.java @@ -1,8 +1,13 @@ package com.ontimize.jee.webclient.openai.util; -import com.ontimize.jee.webclient.openai.model.ProcessResult; +import com.fasterxml.jackson.databind.JsonNode; +import com.github.victools.jsonschema.generator.*; +import com.sun.istack.NotNull; +import org.apache.commons.lang3.ObjectUtils; import org.springframework.stereotype.Component; +import java.lang.reflect.Field; +import java.lang.reflect.Member; import java.util.List; import static com.ontimize.jee.webclient.openai.naming.OpenAINaming.INITIAL_PROMPT_FORMAT; @@ -11,10 +16,35 @@ @Component public class Utils { - public static String buildPrompt(String userPrompt, String jsonSchema, ProcessResult processResult, + public static String buildPrompt(String userPrompt, String jsonSchema, String processResult, List errors) { - return processResult == null + return !ObjectUtils.isNotEmpty(errors) ? String.format(INITIAL_PROMPT_FORMAT, userPrompt, jsonSchema) : String.format(RETRY_PROMPT_FORMAT, userPrompt, jsonSchema, processResult, errors); } + + public static String generateFullSchemaJson(Class dtoClass) { + SchemaGeneratorConfigBuilder configBuilder = new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_7, + OptionPreset.PLAIN_JSON); + + configBuilder.with(Option.DEFINITIONS_FOR_ALL_OBJECTS); + configBuilder.with(Option.FLATTENED_ENUMS); + + configBuilder.forFields().withNullableCheck(fieldScope -> { + Member rawMember = fieldScope.getRawMember(); + if (rawMember instanceof Field) { + Field field = (Field) rawMember; + if (field.isAnnotationPresent(NotNull.class)) { + return false; + } + } + return true; + }); + + SchemaGeneratorConfig config = configBuilder.build(); + SchemaGenerator generator = new SchemaGenerator(config); + JsonNode jsonSchema = generator.generateSchema(dtoClass); + + return jsonSchema.toPrettyString(); + } } From 1d83e8d4802d31713840837e083c027235f31190 Mon Sep 17 00:00:00 2001 From: adrian-carneiro-imatia Date: Thu, 23 Oct 2025 10:10:33 +0200 Subject: [PATCH 61/65] fix sonarqube --- .../exception/OpenAIClientException.java | 11 +++ .../webclient/openai/naming/OpenAINaming.java | 79 +++++++++++-------- .../service/OpenAiImageProcessorService.java | 55 +++++++------ .../openai/util/JsonSchemaValidator.java | 15 +++- .../jee/webclient/openai/util/Utils.java | 5 ++ 5 files changed, 104 insertions(+), 61 deletions(-) create mode 100644 ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/exception/OpenAIClientException.java diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/exception/OpenAIClientException.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/exception/OpenAIClientException.java new file mode 100644 index 00000000..4f78bca3 --- /dev/null +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/exception/OpenAIClientException.java @@ -0,0 +1,11 @@ +package com.ontimize.jee.webclient.openai.exception; + +public class OpenAIClientException extends RuntimeException { + public OpenAIClientException(String message) { + super(message); + } + + public OpenAIClientException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/naming/OpenAINaming.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/naming/OpenAINaming.java index ce70c459..076b719d 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/naming/OpenAINaming.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/naming/OpenAINaming.java @@ -1,6 +1,13 @@ package com.ontimize.jee.webclient.openai.naming; -public class OpenAINaming { +import com.ontimize.jee.webclient.openai.exception.OpenAIClientException; + +public final class OpenAINaming { + + private OpenAINaming() { + throw new OpenAIClientException(""); + } + public static final String MODEL = "model"; public static final String MESSAGES = "messages"; public static final String ROLE = "role"; @@ -18,42 +25,46 @@ public class OpenAINaming { public static final String COMPLETIONS_URL = "https://api.openai.com/v1/chat/completions"; public static final String CHOICES = "choices"; public static final String MESSAGE = "message"; + public static final String INITIAL_PROMPT_FORMAT = - "Tu tarea es procesar la siguiente imagen y devolver la información estructurada en el formato JSON que " + - "se indica más abajo.\n\n" + - "=== INSTRUCCIONES DE CONTEXTO ===\n" + - "%s\n\n" + - "=== ESTRUCTURA ESPERADA ===\n" + - "Devuelve únicamente un JSON que siga esta estructura:\n%s\n\n" + - "IMPORTANTE:\n" + - "No incluyas explicaciones ni comentarios.\n" + - "Usa null si no puedes identificar un valor.\n" + - "Respeta el tipo de dato especificado: si se espera un número o una fecha, devuélvelo " + - "correctamente formateado.\n" + - "Asegúrate de que el JSON es válido y parseable."; + "Your task is to process the following image and return the structured information " + + "in the JSON format described below.\n\n" + + "=== CONTEXT INSTRUCTIONS ===\n" + + "%s\n\n" + + "=== EXPECTED STRUCTURE ===\n" + + "Return only a JSON that follows this structure:\n%s\n\n" + + "IMPORTANT:\n" + + "- Do not include explanations or comments.\n" + + "- Use null if you cannot identify a value.\n" + + "- Respect the specified data type: if a number or date is expected, " + + "return it correctly formatted.\n" + + "- Make sure the JSON is valid and parseable."; + public static final String RETRY_PROMPT_FORMAT = - "El siguiente JSON no cumple con la estructura esperada ni con las validaciones. Corrige los errores y " + - "vuelve a generar solo el JSON corregido.\n\n" + - "=== INSTRUCCIONES DE CONTEXTO ===\n" + - "%s\n\n" + - "=== ESTRUCTURA ESPERADA ===\n" + - "%s\n\n" + - "=== RESPUESTA ANTERIOR CON ERRORES ===\n" + - "%s\n\n" + - "=== ERRORES DETECTADOS ===\n" + - "%s\n\n" + - "Por favor, genera una nueva versión del JSON que sea válida, esté bien formada y siga " + - "exactamente la estructura definida.\n\n" + - "RECUERDA:\n" + - "No incluyas explicaciones ni comentarios.\n" + - "Usa null si no puedes identificar un valor.\n" + - "Respeta el tipo de dato especificado: si se espera un número o una fecha, devuélvelo " + - "correctamente formateado.\n" + - "Asegúrate de que el JSON es válido y parseable."; + "The following JSON does not meet the expected structure or validation rules. " + + "Please correct the errors and regenerate only the corrected JSON.\n\n" + + "=== CONTEXT INSTRUCTIONS ===\n" + + "%s\n\n" + + "=== EXPECTED STRUCTURE ===\n" + + "%s\n\n" + + "=== PREVIOUS INVALID RESPONSE ===\n" + + "%s\n\n" + + "=== DETECTED ERRORS ===\n" + + "%s\n\n" + + "Please generate a new version of the JSON that is valid, well-formed, " + + "and strictly follows the defined structure.\n\n" + + "REMEMBER:\n" + + "- Do not include explanations or comments.\n" + + "- Use null if you cannot identify a value.\n" + + "- Respect the specified data type: if a number or date is expected, " + + "return it correctly formatted.\n" + + "- Make sure the JSON is valid and parseable."; + public static final String OPENAI_API_ERROR = "OpenAI API error: "; - public static final String OPENAI_API_NO_JSON_ERROR = "No se encontró JSON en la cadena de entrada"; - public static final String OPENAI_API_SCHEMA_GENERATION_ERROR = "Error generando schema: "; - public static final String OPENAI_API_SCHEMA_SERIALIZATION_ERROR = "Error serializando schema: "; + public static final String OPENAI_API_NO_JSON_ERROR = "No JSON found in the input string"; + public static final String OPENAI_API_SCHEMA_GENERATION_ERROR = "Error generating schema: "; + public static final String OPENAI_API_SCHEMA_SERIALIZATION_ERROR = "Error serializing schema: "; public static final String PROPERTIES = "properties"; } + diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java index 2586cec1..5812fbf8 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/service/OpenAiImageProcessorService.java @@ -1,6 +1,8 @@ package com.ontimize.jee.webclient.openai.service; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.ontimize.jee.webclient.openai.exception.OpenAIClientException; import com.ontimize.jee.webclient.openai.model.ProcessRequest; import com.ontimize.jee.webclient.openai.model.ProcessResult; import com.ontimize.jee.webclient.openai.util.JsonSchemaValidator; @@ -32,7 +34,7 @@ public ProcessResult processImage(ProcessRequest request) { List errors = new ArrayList<>(); MultipartFile file = request.getFile(); Class outputClass = request.getOutputClass(); - ObjectMapper objectMapper = new ObjectMapper(); + ObjectMapper localObjectMapper = new ObjectMapper(); String schemaStr; try { @@ -60,7 +62,7 @@ public ProcessResult processImage(ProcessRequest request) { parsedResult = responseJsonRaw; jsonSchemaValidator.validate(responseJson, schemaStr); - T result = objectMapper.readValue(responseJson, outputClass); + T result = localObjectMapper.readValue(responseJson, outputClass); finalResult = new ProcessResult<>(result, new ArrayList<>(errors), actualTry); break; @@ -73,36 +75,41 @@ public ProcessResult processImage(ProcessRequest request) { } private String callVisionApi(String promptText, MultipartFile image, String model, int maxTokens, - double temperature) throws Exception { + double temperature) throws OpenAIClientException, JsonProcessingException { HttpEntity> request = prepareRequest(promptText, image, model, maxTokens, temperature); RestTemplate restTemplate = new RestTemplate(); ResponseEntity response = restTemplate.postForEntity(COMPLETIONS_URL, request, String.class); if (!response.getStatusCode().is2xxSuccessful()) { - throw new Exception(OPENAI_API_ERROR + response.getStatusCode() + " - " + response.getBody()); + throw new OpenAIClientException(OPENAI_API_ERROR + response.getStatusCode() + " - " + response.getBody()); } return objectMapper.readTree(response.getBody()).path(CHOICES).get(0).path(MESSAGE).path(CONTENT).asText(); } private HttpEntity> prepareRequest(String promptText, MultipartFile image, String model, - int maxTokens, double temperature) throws IOException { - byte[] imageBytes = image.getBytes(); - String base64Image = Base64.getEncoder().encodeToString(imageBytes); - - Map imageUrlContent = Map.of(URL, IMAGE_TYPE + base64Image, DETAIL, HIGH); - Map contentItem1 = Map.of(TYPE, TEXT, TEXT, promptText); - Map contentItem2 = Map.of(TYPE, IMAGE_URL, IMAGE_URL, imageUrlContent); - Map message = Map.of(ROLE, USER, CONTENT, List.of(contentItem1, contentItem2)); - - Map payload = new HashMap<>(); - payload.put(MODEL, model); - payload.put(MESSAGES, List.of(message)); - payload.put(MAX_TOKENS, maxTokens); - payload.put(TEMPERATURE, temperature); - - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - headers.setBearerAuth(this.apiKey); - - return new HttpEntity<>(payload, headers); + int maxTokens, double temperature) throws OpenAIClientException { + try { + byte[] imageBytes = null; + imageBytes = image.getBytes(); + String base64Image = Base64.getEncoder().encodeToString(imageBytes); + + Map imageUrlContent = Map.of(URL, IMAGE_TYPE + base64Image, DETAIL, HIGH); + Map contentItem1 = Map.of(TYPE, TEXT, TEXT, promptText); + Map contentItem2 = Map.of(TYPE, IMAGE_URL, IMAGE_URL, imageUrlContent); + Map message = Map.of(ROLE, USER, CONTENT, List.of(contentItem1, contentItem2)); + + Map payload = new HashMap<>(); + payload.put(MODEL, model); + payload.put(MESSAGES, List.of(message)); + payload.put(MAX_TOKENS, maxTokens); + payload.put(TEMPERATURE, temperature); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(this.apiKey); + + return new HttpEntity<>(payload, headers); + } catch (IOException e) { + throw new OpenAIClientException(e.getMessage()); + } } } diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java index 4e1123d1..4138c790 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.ontimize.jee.webclient.openai.exception.OpenAIClientException; import org.everit.json.schema.Schema; import org.everit.json.schema.ValidationException; import org.everit.json.schema.loader.SchemaLoader; @@ -44,7 +45,15 @@ public static String extractRawJson(String content) { int open = s.indexOf('{'); int openArr = s.indexOf('['); - int start = (open == -1) ? openArr : (openArr == -1 ? open : Math.min(open, openArr)); + int start; + if (open == -1) { + start = openArr; + } else if (openArr == -1) { + start = open; + } else { + start = Math.min(open, openArr); + } + int end = Math.max(s.lastIndexOf('}'), s.lastIndexOf(']')); if (start >= 0 && end > start) { String candidate = s.substring(start, end + 1).trim(); @@ -75,9 +84,9 @@ public void validate(Object dataObject, String schemaJson) { } catch (ValidationException ve) { String message = "Error de validación JSON: " + String.join("; ", ve.getAllMessages()); - throw new RuntimeException(message, ve); + throw new OpenAIClientException(message, ve); } catch (Exception e) { - throw new RuntimeException("Error inesperado durante la validación JSON: " + e.getMessage(), e); + throw new OpenAIClientException("Error inesperado durante la validación JSON: " + e.getMessage(), e); } } } diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/Utils.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/Utils.java index 959926c5..00edd8ee 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/Utils.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/Utils.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.github.victools.jsonschema.generator.*; +import com.ontimize.jee.webclient.openai.exception.OpenAIClientException; import com.sun.istack.NotNull; import org.apache.commons.lang3.ObjectUtils; import org.springframework.stereotype.Component; @@ -16,6 +17,10 @@ @Component public class Utils { + private Utils() { + throw new OpenAIClientException(""); + } + public static String buildPrompt(String userPrompt, String jsonSchema, String processResult, List errors) { return !ObjectUtils.isNotEmpty(errors) From 572b8604acd223f9ea42985a2ae18f7fce18f47f Mon Sep 17 00:00:00 2001 From: adrian-carneiro-imatia Date: Mon, 27 Oct 2025 17:44:00 +0100 Subject: [PATCH 62/65] prevent chatgpt from adding schema --- .../jee/webclient/openai/util/JsonSchemaValidator.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java index 4138c790..e3628933 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java @@ -58,7 +58,9 @@ public static String extractRawJson(String content) { if (start >= 0 && end > start) { String candidate = s.substring(start, end + 1).trim(); if (candidate.startsWith("{") || candidate.startsWith("[")) { - return candidate; + candidate = candidate.replaceAll("\"\\$schema\"\\s*:\\s*\"[^\"]*\"\\s*,?", ""); + candidate = candidate.replaceAll(",\\s*}", "}").replaceAll(",\\s*]", "]"); + return candidate.trim(); } } return null; From 312503a883217b6761b9cc6704204ecdcbaacaf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Mon, 3 Nov 2025 14:33:17 +0100 Subject: [PATCH 63/65] Translate validation messages --- .../jee/webclient/openai/util/JsonSchemaValidator.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java index e3628933..ce29a7cc 100644 --- a/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java +++ b/ontimize-jee-webclient-addons/src/main/java/com/ontimize/jee/webclient/openai/util/JsonSchemaValidator.java @@ -85,10 +85,10 @@ public void validate(Object dataObject, String schemaJson) { } } catch (ValidationException ve) { - String message = "Error de validación JSON: " + String.join("; ", ve.getAllMessages()); - throw new OpenAIClientException(message, ve); + String message = "JSON validation error: " + String.join("; ", ve.getAllMessages()); + throw new RuntimeException(message, ve); } catch (Exception e) { - throw new OpenAIClientException("Error inesperado durante la validación JSON: " + e.getMessage(), e); + throw new RuntimeException("Unexpected error during JSON validation: " + e.getMessage(), e); } } } From 59df494fda4296675ce0dfe6ce0adf39a5135a2b Mon Sep 17 00:00:00 2001 From: supportontimize Date: Mon, 3 Nov 2025 15:04:09 +0000 Subject: [PATCH 64/65] =?UTF-8?q?New=20release=20=E2=86=92=205.14.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 +++- ontimize-jee-common/pom.xml | 2 +- ontimize-jee-server-jdbc/pom.xml | 2 +- ontimize-jee-server-keycloak/pom.xml | 2 +- ontimize-jee-server-rest/pom.xml | 2 +- ontimize-jee-server/pom.xml | 2 +- ontimize-jee-webclient-addons/pom.xml | 2 +- pom.xml | 2 +- 8 files changed, 10 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 267768d8..0655bb1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ## [Unreleased] +## [5.14.0] - 2025-11-03 ### Changed - **ApplicationContextUtils**: Simplified the getBeanForName method for more streamlined service retrieval. - **DefaultDaoExtensionHelper**: Improved property placeholder handling with Java streams for type safety and clarity, addressing SonarQube findings. @@ -76,7 +77,8 @@ * **POM**: Sorted pom alphabetically and sorted, extracted version to properties and put all dependencies into dependency manager. * **Sonar**: Fix some sonar code smells. -[unreleased]: https://github.com/ontimize/ontimize-jee/compare/5.13.0...HEAD +[unreleased]: https://github.com/ontimize/ontimize-jee/compare/5.14.0...HEAD +[5.14.0]: https://github.com/ontimize/ontimize-jee/compare/5.13.0...5.14.0 [5.13.0]: https://github.com/ontimize/ontimize-jee/compare/5.12.1...5.13.0 [5.12.1]: https://github.com/ontimize/ontimize-jee/compare/5.12.0...5.12.1 [5.12.0]: https://github.com/ontimize/ontimize-jee/compare/5.11.0...5.12.0 diff --git a/ontimize-jee-common/pom.xml b/ontimize-jee-common/pom.xml index fb8240b8..3f2cb3d4 100644 --- a/ontimize-jee-common/pom.xml +++ b/ontimize-jee-common/pom.xml @@ -4,7 +4,7 @@ com.ontimize.jee ontimize-jee - 5.14.0-SNAPSHOT + 5.14.0 ontimize-jee-common diff --git a/ontimize-jee-server-jdbc/pom.xml b/ontimize-jee-server-jdbc/pom.xml index 51e2a7f5..1bc8a38a 100644 --- a/ontimize-jee-server-jdbc/pom.xml +++ b/ontimize-jee-server-jdbc/pom.xml @@ -4,7 +4,7 @@ com.ontimize.jee ontimize-jee - 5.14.0-SNAPSHOT + 5.14.0 ontimize-jee-server-jdbc diff --git a/ontimize-jee-server-keycloak/pom.xml b/ontimize-jee-server-keycloak/pom.xml index 2d718919..0d51058f 100644 --- a/ontimize-jee-server-keycloak/pom.xml +++ b/ontimize-jee-server-keycloak/pom.xml @@ -4,7 +4,7 @@ com.ontimize.jee ontimize-jee - 5.14.0-SNAPSHOT + 5.14.0 ontimize-jee-server-keycloak diff --git a/ontimize-jee-server-rest/pom.xml b/ontimize-jee-server-rest/pom.xml index a78bcd61..515880a3 100644 --- a/ontimize-jee-server-rest/pom.xml +++ b/ontimize-jee-server-rest/pom.xml @@ -4,7 +4,7 @@ com.ontimize.jee ontimize-jee - 5.14.0-SNAPSHOT + 5.14.0 ontimize-jee-server-rest diff --git a/ontimize-jee-server/pom.xml b/ontimize-jee-server/pom.xml index 50c314d4..55683b1d 100644 --- a/ontimize-jee-server/pom.xml +++ b/ontimize-jee-server/pom.xml @@ -4,7 +4,7 @@ com.ontimize.jee ontimize-jee - 5.14.0-SNAPSHOT + 5.14.0 ontimize-jee-server diff --git a/ontimize-jee-webclient-addons/pom.xml b/ontimize-jee-webclient-addons/pom.xml index 7300a066..7cdda91c 100644 --- a/ontimize-jee-webclient-addons/pom.xml +++ b/ontimize-jee-webclient-addons/pom.xml @@ -5,7 +5,7 @@ com.ontimize.jee ontimize-jee - 5.14.0-SNAPSHOT + 5.14.0 ontimize-jee-webclient-addons diff --git a/pom.xml b/pom.xml index 06af2fdb..8e3555d1 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.ontimize.jee ontimize-jee - 5.14.0-SNAPSHOT + 5.14.0 pom Ontimize EE From f424d22ab9e060c432f75284fa2bda90c55d054f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADnez=20Kirsten?= Date: Mon, 3 Nov 2025 16:09:35 +0100 Subject: [PATCH 65/65] Update CHANGELOG.md --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0655bb1f..2ef118ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,20 @@ ## [Unreleased] ## [5.14.0] - 2025-11-03 +### Added ✔️ +- **OpenAI Client** + + An OpenAI client has been implemented with a method to process images. This method accepts a parameter of type ProcessRequest, which contains the following: + - The image file to be processed. + - An optional prompt to provide additional instructions (the default behavior is to analyze the image and return it in a specific format). + - The number of retries for the request. + - The output class type (T) expected as a result. + + The client is initialized via a constructor that receives an OpenAiClientConfig object. This configuration object includes: + - The user's API key. + - The model to be used. + - The maximum number of tokens allowed per request. + - The temperature setting (which controls how creative the responses should be). ### Changed - **ApplicationContextUtils**: Simplified the getBeanForName method for more streamlined service retrieval. - **DefaultDaoExtensionHelper**: Improved property placeholder handling with Java streams for type safety and clarity, addressing SonarQube findings.