1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.forgerock.audit.json;
17
18 import static org.forgerock.json.JsonValue.field;
19 import static org.forgerock.json.JsonValue.json;
20 import static org.forgerock.json.JsonValue.object;
21
22 import com.fasterxml.jackson.annotation.JsonPropertyDescription;
23 import com.fasterxml.jackson.databind.AnnotationIntrospector;
24 import com.fasterxml.jackson.databind.ObjectMapper;
25 import com.fasterxml.jackson.databind.introspect.Annotated;
26 import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
27 import com.fasterxml.jackson.module.jsonSchema.jakarta.JsonSchema;
28 import com.fasterxml.jackson.module.jsonSchema.jakarta.factories.SchemaFactoryWrapper;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.util.LinkedHashMap;
32 import java.util.Map;
33 import org.forgerock.audit.AuditException;
34 import org.forgerock.audit.AuditServiceBuilder;
35 import org.forgerock.audit.AuditServiceConfiguration;
36 import org.forgerock.audit.events.handlers.AuditEventHandler;
37 import org.forgerock.audit.events.handlers.EventHandlerConfiguration;
38 import org.forgerock.audit.util.JsonValueUtils;
39 import org.forgerock.json.JsonValue;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43
44
45
46
47 public final class AuditJsonConfig {
48 private static final Logger LOGGER = LoggerFactory.getLogger(AuditJsonConfig.class);
49
50
51 private static final String NAME_FIELD = "name";
52
53 private static final String CLASS_FIELD = "class";
54
55 private static final String CONFIG_FIELD = "config";
56
57 private static final String EVENTS_FIELD = "events";
58
59
60 private static final ObjectMapper MAPPER = new ObjectMapper();
61
62 private static final AnnotationIntrospector DEFAULT_ANNOTATION_INTROSPECTOR = new JacksonAnnotationIntrospector();
63 private static final AnnotationIntrospector HELP_APPENDER_ANNOTATION_INTROSPECTOR =
64 new HelpAppenderAnnotationIntrospector();
65
66 private AuditJsonConfig() {
67
68 }
69
70
71
72
73
74
75
76
77
78
79 public static JsonValue getJson(InputStream input) throws AuditException {
80 if (input == null) {
81 throw new AuditException("Input stream is null");
82 }
83 try {
84 Object val = MAPPER.readValue(input, LinkedHashMap.class);
85 return new JsonValue(val);
86 } catch (IOException e) {
87 throw new AuditException(String.format("Unable to retrieve json value from json input stream"), e);
88 }
89 }
90
91
92
93
94
95
96
97
98
99
100 public static AuditServiceConfiguration parseAuditServiceConfiguration(InputStream input) throws AuditException {
101 try {
102 return MAPPER.readValue(input, AuditServiceConfiguration.class);
103 } catch (IOException e) {
104 throw new AuditException(String.format("Unable to retrieve class %s from json input stream",
105 AuditServiceConfiguration.class), e);
106 }
107 }
108
109
110
111
112
113
114
115
116
117
118 public static AuditServiceConfiguration parseAuditServiceConfiguration(String json) throws AuditException {
119 if (json == null) {
120 return new AuditServiceConfiguration();
121 }
122 try {
123 return MAPPER.readValue(json, AuditServiceConfiguration.class);
124 } catch (IOException e) {
125 throw new AuditException(String.format("Unable to retrieve class %s from json: %s",
126 AuditServiceConfiguration.class, json), e);
127 }
128 }
129
130
131
132
133
134
135
136
137
138
139 public static AuditServiceConfiguration parseAuditServiceConfiguration(JsonValue json) throws AuditException {
140 return parseAuditServiceConfiguration(JsonValueUtils.extractValueAsString(json, "/"));
141 }
142
143
144
145
146
147
148
149
150
151
152
153
154 public static void registerHandlerToService(JsonValue jsonConfig, AuditServiceBuilder auditServiceBuilder)
155 throws AuditException {
156 registerHandlerToService(jsonConfig, auditServiceBuilder, auditServiceBuilder.getClass().getClassLoader());
157 }
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172 public static void registerHandlerToService(JsonValue jsonConfig,
173 AuditServiceBuilder auditServiceBuilder, ClassLoader classLoader) throws AuditException {
174 String name = getHandlerName(jsonConfig);
175 Class<? extends AuditEventHandler> handlerClass = getAuditEventHandlerClass(name, jsonConfig, classLoader);
176 Class<? extends EventHandlerConfiguration> configClass =
177 getAuditEventHandlerConfigurationClass(name, handlerClass, classLoader);
178 EventHandlerConfiguration configuration = parseAuditEventHandlerConfiguration(configClass, jsonConfig);
179 auditServiceBuilder.withAuditEventHandler(handlerClass, configuration);
180 }
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197 private static String getHandlerName(JsonValue jsonConfig) throws AuditException {
198 String name = jsonConfig.get(CONFIG_FIELD).get(NAME_FIELD).asString();
199 if (name == null) {
200 throw new AuditException(String.format("No name is defined for the provided audit handler. "
201 + "You must define a 'name' property in the configuration."));
202 }
203 return name;
204 }
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220 @SuppressWarnings("unchecked")
221 private static Class<? extends AuditEventHandler> getAuditEventHandlerClass(
222 String handlerName, JsonValue jsonConfig, ClassLoader classLoader) throws AuditException {
223
224
225 String className = jsonConfig.get(CLASS_FIELD).asString();
226 if (className == null) {
227 String errorMessage = String.format("No class is defined for the audit handler %s. "
228 + "You must define a 'class' property in the configuration.", handlerName);
229 throw new AuditException(errorMessage);
230 }
231 try {
232 return (Class<? extends AuditEventHandler>) Class.forName(className, true, classLoader);
233 } catch (ClassNotFoundException e) {
234 String errorMessage = String.format("Invalid class is defined for the audit handler %s.", handlerName);
235 throw new AuditException(errorMessage, e);
236 }
237 }
238
239 @SuppressWarnings("unchecked")
240 private static Class<? extends EventHandlerConfiguration> getAuditEventHandlerConfigurationClass(
241 String handlerName, Class<? extends AuditEventHandler> handlerClass, ClassLoader classLoader)
242 throws AuditException {
243 String className = handlerClass.getName() + "Configuration";
244 try {
245 return (Class<? extends EventHandlerConfiguration>) Class.forName(className, true, classLoader);
246 } catch (ClassNotFoundException e) {
247 String errorMessage = String.format("Unable to locate configuration class %s for the audit handler %s.",
248 className, handlerName);
249 throw new AuditException(errorMessage, e);
250 }
251 }
252
253
254
255
256
257
258
259
260
261
262
263
264
265 public static <C extends EventHandlerConfiguration> C parseAuditEventHandlerConfiguration(
266 Class<C> clazz, JsonValue jsonConfig) throws AuditException {
267 C configuration = null;
268 JsonValue conf = jsonConfig.get(CONFIG_FIELD);
269 if (conf != null) {
270 configuration = MAPPER.convertValue(conf.getObject(), clazz);
271 }
272 return configuration;
273 }
274
275
276
277
278
279
280
281
282
283 public static JsonValue getAuditEventHandlerConfigurationSchema(final String className,
284 final ClassLoader classLoader) throws AuditException {
285 final Class<? extends EventHandlerConfiguration> eventHandlerConfiguration =
286 getAuditEventHandlerConfigurationClass(
287 className,
288 getAuditEventHandlerClass(
289 className,
290 json(object(field("class", className))),
291 classLoader),
292 classLoader);
293 try {
294 MAPPER.setAnnotationIntrospector(HELP_APPENDER_ANNOTATION_INTROSPECTOR);
295 SchemaFactoryWrapper visitor = new SchemaFactoryWrapper();
296 MAPPER.acceptJsonFormatVisitor(MAPPER.constructType(eventHandlerConfiguration), visitor);
297 JsonSchema jsonSchema = visitor.finalSchema();
298 final JsonValue schema = json(MAPPER.readValue(MAPPER.writeValueAsString(jsonSchema), Map.class));
299 MAPPER.setAnnotationIntrospector(DEFAULT_ANNOTATION_INTROSPECTOR);
300 return schema;
301 } catch (IOException e) {
302 final String error = String.format("Unable to parse configuration class schema for configuration class %s",
303 eventHandlerConfiguration.getName());
304 LOGGER.error(error, e);
305 throw new AuditException(error, e);
306 }
307 }
308
309
310
311
312
313 private static class HelpAppenderAnnotationIntrospector extends JacksonAnnotationIntrospector {
314 @Override
315 public String findPropertyDescription(Annotated ann) {
316 JsonPropertyDescription desc = _findAnnotation(ann, JsonPropertyDescription.class);
317 return (desc == null) ? null : desc.value().concat(".help");
318 }
319 }
320
321 }