Skip to content

Commit 291d79b

Browse files
Copilotnixel2007
andcommitted
Restore DiagnosticParameterValidator component with infrastructure role for proper caching
Co-authored-by: nixel2007 <[email protected]>
1 parent 631c220 commit 291d79b

File tree

3 files changed

+162
-151
lines changed

3 files changed

+162
-151
lines changed

src/main/java/com/github/_1c_syntax/bsl/languageserver/databind/ObjectMapperConfiguration.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@
2828
import com.github._1c_syntax.bsl.languageserver.codelenses.CodeLensSupplier;
2929
import com.github._1c_syntax.bsl.languageserver.commands.CommandArguments;
3030
import com.github._1c_syntax.bsl.languageserver.commands.CommandSupplier;
31+
import org.springframework.beans.factory.config.BeanDefinition;
3132
import org.springframework.context.annotation.Bean;
3233
import org.springframework.context.annotation.Configuration;
34+
import org.springframework.context.annotation.Role;
3335

3436
import java.util.ArrayList;
3537
import java.util.Collection;
@@ -39,6 +41,7 @@
3941
public class ObjectMapperConfiguration {
4042

4143
@Bean
44+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
4245
public ObjectMapper objectMapper(
4346
Collection<CodeLensSupplier<? extends CodeLensData>> codeLensResolvers,
4447
Collection<CommandSupplier<? extends CommandArguments>> commandSuppliers

src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/infrastructure/DiagnosticBeanPostProcessor.java

Lines changed: 2 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -22,59 +22,28 @@
2222
package com.github._1c_syntax.bsl.languageserver.diagnostics.infrastructure;
2323

2424
import com.github._1c_syntax.bsl.languageserver.configuration.LanguageServerConfiguration;
25-
import com.github._1c_syntax.bsl.languageserver.configuration.events.LanguageServerConfigurationChangedEvent;
2625
import com.github._1c_syntax.bsl.languageserver.diagnostics.BSLDiagnostic;
2726
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticInfo;
2827
import com.github._1c_syntax.bsl.languageserver.utils.Resources;
29-
import com.networknt.schema.JsonSchema;
30-
import com.networknt.schema.JsonSchemaFactory;
31-
import com.networknt.schema.SpecVersion;
32-
import com.networknt.schema.ValidationMessage;
3328
import lombok.RequiredArgsConstructor;
3429
import lombok.extern.slf4j.Slf4j;
3530
import org.eclipse.lsp4j.jsonrpc.messages.Either;
3631
import org.springframework.beans.factory.config.BeanPostProcessor;
37-
import org.springframework.cache.annotation.CacheConfig;
38-
import org.springframework.cache.annotation.CacheEvict;
39-
import org.springframework.cache.annotation.Cacheable;
4032
import org.springframework.context.annotation.Lazy;
41-
import org.springframework.context.event.EventListener;
42-
import org.springframework.core.io.ClassPathResource;
43-
import org.springframework.lang.Nullable;
4433
import org.springframework.stereotype.Component;
4534

46-
import java.io.IOException;
4735
import java.util.Map;
48-
import java.util.Set;
49-
import java.util.concurrent.ConcurrentHashMap;
5036

5137
@RequiredArgsConstructor
5238
@Component
5339
@Slf4j
54-
@CacheConfig(cacheNames = "diagnosticSchemaValidation")
5540
public class DiagnosticBeanPostProcessor implements BeanPostProcessor {
5641

5742
private final LanguageServerConfiguration configuration;
5843
private final Map<Class<? extends BSLDiagnostic>, DiagnosticInfo> diagnosticInfos;
5944
@Lazy
6045
private final Resources resources;
61-
62-
@Nullable
63-
private JsonSchema parametersSchema;
64-
private final Map<String, JsonSchema> diagnosticSchemas = new ConcurrentHashMap<>();
65-
66-
/**
67-
* Обработчик события {@link LanguageServerConfigurationChangedEvent}.
68-
* <p>
69-
* Сбрасывает кеш валидации схем при изменении конфигурации.
70-
*
71-
* @param event Событие
72-
*/
73-
@EventListener
74-
@CacheEvict(allEntries = true)
75-
public void handleLanguageServerConfigurationChange(LanguageServerConfigurationChangedEvent event) {
76-
// No-op. Служит для сброса кеша при изменении конфигурации
77-
}
46+
private final DiagnosticParameterValidator parameterValidator;
7847

7948
@Override
8049
public Object postProcessBeforeInitialization(Object bean, String beanName) {
@@ -106,7 +75,7 @@ public Object postProcessAfterInitialization(Object bean, String beanName) {
10675
try {
10776
// Validate configuration against JSON schema if available
10877
var diagnosticCode = diagnostic.getInfo().getCode().getStringValue();
109-
validateDiagnosticConfiguration(diagnosticCode, diagnosticConfiguration.getRight());
78+
parameterValidator.validateDiagnosticConfiguration(diagnosticCode, diagnosticConfiguration.getRight());
11079

11180
diagnostic.configure(diagnosticConfiguration.getRight());
11281
} catch (Exception e) {
@@ -119,122 +88,4 @@ public Object postProcessAfterInitialization(Object bean, String beanName) {
11988
return diagnostic;
12089
}
12190

122-
/**
123-
* Cached validation of diagnostic configuration against JSON schema.
124-
* Results are cached per diagnostic class and configuration to improve performance for prototype beans.
125-
*
126-
* @param diagnosticCode Diagnostic code
127-
* @param configuration Configuration map to validate
128-
*/
129-
@Cacheable
130-
public void validateDiagnosticConfiguration(String diagnosticCode, Map<String, Object> configuration) {
131-
try {
132-
var schema = getDiagnosticSchema(diagnosticCode);
133-
if (schema != null) {
134-
// Use a standalone ObjectMapper to avoid Spring bean dependencies
135-
var standaloneMapper = new com.fasterxml.jackson.databind.ObjectMapper();
136-
var configNode = standaloneMapper.valueToTree(configuration);
137-
Set<ValidationMessage> errors = schema.validate(configNode);
138-
139-
if (!errors.isEmpty()) {
140-
var errorMessages = errors.stream()
141-
.map(ValidationMessage::getMessage)
142-
.reduce((msg1, msg2) -> msg1 + "; " + msg2)
143-
.orElse("Unknown validation error");
144-
145-
var localizedMessage = resources.getResourceString(getClass(), "diagnosticSchemaValidationError",
146-
diagnosticCode, errorMessages);
147-
LOGGER.warn(localizedMessage);
148-
}
149-
}
150-
} catch (Exception e) {
151-
// Schema validation failed, but don't prevent diagnostic configuration
152-
LOGGER.debug("Schema validation failed for diagnostic '{}': {}", diagnosticCode, e.getMessage());
153-
}
154-
}
155-
156-
private String mapToJsonString(Map<String, Object> map) {
157-
// Simple JSON serialization to avoid ObjectMapper dependency during bean post processing
158-
var json = new StringBuilder();
159-
json.append("{");
160-
var first = true;
161-
for (var entry : map.entrySet()) {
162-
if (!first) {
163-
json.append(",");
164-
}
165-
json.append("\"").append(entry.getKey()).append("\":");
166-
167-
var value = entry.getValue();
168-
if (value instanceof String) {
169-
json.append("\"").append(value).append("\"");
170-
} else if (value instanceof Boolean || value instanceof Number) {
171-
json.append(value);
172-
} else if (value instanceof java.util.Collection) {
173-
json.append("[");
174-
var items = (java.util.Collection<?>) value;
175-
var firstItem = true;
176-
for (var item : items) {
177-
if (!firstItem) {
178-
json.append(",");
179-
}
180-
if (item instanceof String) {
181-
json.append("\"").append(item).append("\"");
182-
} else {
183-
json.append(item);
184-
}
185-
firstItem = false;
186-
}
187-
json.append("]");
188-
} else {
189-
json.append("\"").append(value).append("\"");
190-
}
191-
first = false;
192-
}
193-
json.append("}");
194-
return json.toString();
195-
}
196-
197-
private JsonSchema getDiagnosticSchema(String diagnosticCode) {
198-
return diagnosticSchemas.computeIfAbsent(diagnosticCode, this::loadDiagnosticSchema);
199-
}
200-
201-
private JsonSchema loadDiagnosticSchema(String diagnosticCode) {
202-
try {
203-
var schema = getParametersSchema();
204-
if (schema != null) {
205-
// Extract the specific diagnostic schema from the main schema
206-
var schemaNode = schema.getSchemaNode();
207-
var definitionsNode = schemaNode.get("definitions");
208-
if (definitionsNode != null && definitionsNode.has(diagnosticCode)) {
209-
var diagnosticSchemaNode = definitionsNode.get(diagnosticCode);
210-
var factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
211-
return factory.getSchema(diagnosticSchemaNode);
212-
}
213-
}
214-
} catch (Exception e) {
215-
LOGGER.debug("Failed to load schema for diagnostic '{}': {}", diagnosticCode, e.getMessage());
216-
}
217-
return null;
218-
}
219-
220-
private JsonSchema getParametersSchema() {
221-
if (parametersSchema == null) {
222-
parametersSchema = loadParametersSchema();
223-
}
224-
return parametersSchema;
225-
}
226-
227-
private JsonSchema loadParametersSchema() {
228-
try {
229-
var schemaResource = new ClassPathResource("com/github/_1c_syntax/bsl/languageserver/configuration/parameters-schema.json");
230-
if (schemaResource.exists()) {
231-
var factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
232-
return factory.getSchema(schemaResource.getInputStream());
233-
}
234-
} catch (IOException e) {
235-
LOGGER.warn("Failed to load parameters schema: {}", e.getMessage());
236-
}
237-
return null;
238-
}
239-
24091
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/*
2+
* This file is a part of BSL Language Server.
3+
*
4+
* Copyright (c) 2018-2025
5+
* Alexey Sosnoviy <[email protected]>, Nikita Fedkin <[email protected]> and contributors
6+
*
7+
* SPDX-License-Identifier: LGPL-3.0-or-later
8+
*
9+
* BSL Language Server is free software; you can redistribute it and/or
10+
* modify it under the terms of the GNU Lesser General Public
11+
* License as published by the Free Software Foundation; either
12+
* version 3.0 of the License, or (at your option) any later version.
13+
*
14+
* BSL Language Server is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17+
* Lesser General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU Lesser General Public
20+
* License along with BSL Language Server.
21+
*/
22+
package com.github._1c_syntax.bsl.languageserver.diagnostics.infrastructure;
23+
24+
import com.fasterxml.jackson.databind.ObjectMapper;
25+
import com.github._1c_syntax.bsl.languageserver.configuration.events.LanguageServerConfigurationChangedEvent;
26+
import com.github._1c_syntax.bsl.languageserver.utils.Resources;
27+
import com.networknt.schema.JsonSchema;
28+
import com.networknt.schema.JsonSchemaFactory;
29+
import com.networknt.schema.SpecVersion;
30+
import com.networknt.schema.ValidationMessage;
31+
import lombok.Getter;
32+
import lombok.RequiredArgsConstructor;
33+
import lombok.extern.slf4j.Slf4j;
34+
import org.springframework.beans.factory.config.BeanDefinition;
35+
import org.springframework.cache.annotation.CacheConfig;
36+
import org.springframework.cache.annotation.CacheEvict;
37+
import org.springframework.cache.annotation.Cacheable;
38+
import org.springframework.context.annotation.Role;
39+
import org.springframework.context.event.EventListener;
40+
import org.springframework.core.io.ClassPathResource;
41+
import org.springframework.lang.Nullable;
42+
import org.springframework.stereotype.Component;
43+
44+
import java.io.IOException;
45+
import java.util.Map;
46+
import java.util.Set;
47+
import java.util.concurrent.ConcurrentHashMap;
48+
49+
/**
50+
* Валидатор параметров диагностик с кешированием результатов проверки.
51+
* <p>
52+
* Выполняет валидацию конфигурации диагностик против JSON-схемы для обеспечения
53+
* корректности параметров. Результаты валидации кешируются по классу диагностики
54+
* и конфигурации для повышения производительности при работе с prototype-бинами.
55+
* <p>
56+
* Кеш автоматически сбрасывается при получении события {@link LanguageServerConfigurationChangedEvent}.
57+
* Ошибки валидации логируются как предупреждения, но не препятствуют созданию диагностик.
58+
*
59+
* @see LanguageServerConfigurationChangedEvent
60+
* @see DiagnosticBeanPostProcessor
61+
*/
62+
@RequiredArgsConstructor
63+
@Component
64+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
65+
@Slf4j
66+
@CacheConfig(cacheNames = "diagnosticSchemaValidation")
67+
public class DiagnosticParameterValidator {
68+
69+
private final Resources resources;
70+
private final ObjectMapper objectMapper;
71+
72+
@Getter(lazy = true)
73+
@Nullable
74+
private final JsonSchema parametersSchema = loadParametersSchema();
75+
private final Map<String, JsonSchema> diagnosticSchemas = new ConcurrentHashMap<>();
76+
77+
/**
78+
* Обработчик события {@link LanguageServerConfigurationChangedEvent}.
79+
* <p>
80+
* Сбрасывает кеш валидации схем при изменении конфигурации.
81+
*
82+
* @param event Событие
83+
*/
84+
@EventListener
85+
@CacheEvict(allEntries = true)
86+
public void handleLanguageServerConfigurationChange(LanguageServerConfigurationChangedEvent event) {
87+
// No-op. Служит для сброса кеша при изменении конфигурации
88+
}
89+
90+
/**
91+
* Cached validation of diagnostic configuration against JSON schema.
92+
* Results are cached per diagnostic class and configuration to improve performance for prototype beans.
93+
*
94+
* @param diagnosticCode Diagnostic code
95+
* @param configuration Configuration map to validate
96+
*/
97+
@Cacheable
98+
public void validateDiagnosticConfiguration(String diagnosticCode, Map<String, Object> configuration) {
99+
try {
100+
var schema = getDiagnosticSchema(diagnosticCode);
101+
if (schema != null) {
102+
var configNode = objectMapper.valueToTree(configuration);
103+
Set<ValidationMessage> errors = schema.validate(configNode);
104+
105+
if (!errors.isEmpty()) {
106+
var errorMessages = errors.stream()
107+
.map(ValidationMessage::getMessage)
108+
.reduce((msg1, msg2) -> msg1 + "; " + msg2)
109+
.orElse("Unknown validation error");
110+
111+
var localizedMessage = resources.getResourceString(DiagnosticBeanPostProcessor.class, "diagnosticSchemaValidationError",
112+
diagnosticCode, errorMessages);
113+
LOGGER.warn(localizedMessage);
114+
}
115+
}
116+
} catch (Exception e) {
117+
// Schema validation failed, but don't prevent diagnostic configuration
118+
LOGGER.debug("Schema validation failed for diagnostic '{}': {}", diagnosticCode, e.getMessage());
119+
}
120+
}
121+
122+
private JsonSchema getDiagnosticSchema(String diagnosticCode) {
123+
return diagnosticSchemas.computeIfAbsent(diagnosticCode, this::loadDiagnosticSchema);
124+
}
125+
126+
private JsonSchema loadDiagnosticSchema(String diagnosticCode) {
127+
try {
128+
var schema = getParametersSchema();
129+
if (schema != null) {
130+
// Extract the specific diagnostic schema from the main schema
131+
var schemaNode = schema.getSchemaNode();
132+
var definitionsNode = schemaNode.get("definitions");
133+
if (definitionsNode != null && definitionsNode.has(diagnosticCode)) {
134+
var diagnosticSchemaNode = definitionsNode.get(diagnosticCode);
135+
var factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
136+
return factory.getSchema(diagnosticSchemaNode);
137+
}
138+
}
139+
} catch (Exception e) {
140+
LOGGER.debug("Failed to load schema for diagnostic '{}': {}", diagnosticCode, e.getMessage());
141+
}
142+
return null;
143+
}
144+
145+
private JsonSchema loadParametersSchema() {
146+
try {
147+
var schemaResource = new ClassPathResource("com/github/_1c_syntax/bsl/languageserver/configuration/parameters-schema.json");
148+
if (schemaResource.exists()) {
149+
var factory = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
150+
return factory.getSchema(schemaResource.getInputStream());
151+
}
152+
} catch (IOException e) {
153+
LOGGER.warn("Failed to load parameters schema: {}", e.getMessage());
154+
}
155+
return null;
156+
}
157+
}

0 commit comments

Comments
 (0)