1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.syncope.ext.opensearch.client;
20
21 import com.fasterxml.jackson.databind.JsonNode;
22 import java.io.IOException;
23 import java.util.List;
24 import java.util.Map;
25 import org.apache.syncope.common.lib.types.AnyTypeKind;
26 import org.apache.syncope.core.persistence.api.entity.Any;
27 import org.apache.syncope.core.persistence.api.entity.Entity;
28 import org.apache.syncope.core.persistence.api.entity.Realm;
29 import org.apache.syncope.core.provisioning.api.event.EntityLifecycleEvent;
30 import org.apache.syncope.core.spring.security.SecureRandomUtils;
31 import org.identityconnectors.framework.common.objects.SyncDeltaType;
32 import org.opensearch.client.opensearch.OpenSearchClient;
33 import org.opensearch.client.opensearch._types.OpenSearchException;
34 import org.opensearch.client.opensearch._types.Refresh;
35 import org.opensearch.client.opensearch._types.analysis.CustomNormalizer;
36 import org.opensearch.client.opensearch._types.analysis.Normalizer;
37 import org.opensearch.client.opensearch._types.mapping.DynamicTemplate;
38 import org.opensearch.client.opensearch._types.mapping.KeywordProperty;
39 import org.opensearch.client.opensearch._types.mapping.ObjectProperty;
40 import org.opensearch.client.opensearch._types.mapping.Property;
41 import org.opensearch.client.opensearch._types.mapping.TextProperty;
42 import org.opensearch.client.opensearch._types.mapping.TypeMapping;
43 import org.opensearch.client.opensearch.core.DeleteRequest;
44 import org.opensearch.client.opensearch.core.DeleteResponse;
45 import org.opensearch.client.opensearch.core.IndexRequest;
46 import org.opensearch.client.opensearch.core.IndexResponse;
47 import org.opensearch.client.opensearch.indices.CreateIndexRequest;
48 import org.opensearch.client.opensearch.indices.CreateIndexResponse;
49 import org.opensearch.client.opensearch.indices.DeleteIndexRequest;
50 import org.opensearch.client.opensearch.indices.DeleteIndexResponse;
51 import org.opensearch.client.opensearch.indices.ExistsRequest;
52 import org.opensearch.client.opensearch.indices.IndexSettings;
53 import org.opensearch.client.opensearch.indices.IndexSettingsAnalysis;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56 import org.springframework.transaction.event.TransactionalEventListener;
57
58
59
60
61 public class OpenSearchIndexManager {
62
63 private static final Logger LOG = LoggerFactory.getLogger(OpenSearchIndexManager.class);
64
65 protected final OpenSearchClient client;
66
67 protected final OpenSearchUtils openSearchUtils;
68
69 protected final String numberOfShards;
70
71 protected final String numberOfReplicas;
72
73 public OpenSearchIndexManager(
74 final OpenSearchClient client,
75 final OpenSearchUtils ppenSearchUtils,
76 final String numberOfShards,
77 final String numberOfReplicas) {
78
79 this.client = client;
80 this.openSearchUtils = ppenSearchUtils;
81 this.numberOfShards = numberOfShards;
82 this.numberOfReplicas = numberOfReplicas;
83 }
84
85 public boolean existsAnyIndex(final String domain, final AnyTypeKind kind) throws IOException {
86 return client.indices().exists(new ExistsRequest.Builder().
87 index(OpenSearchUtils.getAnyIndex(domain, kind)).build()).
88 value();
89 }
90
91 public boolean existsRealmIndex(final String domain) throws IOException {
92 return client.indices().exists(new ExistsRequest.Builder().
93 index(OpenSearchUtils.getRealmIndex(domain)).build()).
94 value();
95 }
96
97 public boolean existsAuditIndex(final String domain) throws IOException {
98 return client.indices().exists(new ExistsRequest.Builder().
99 index(OpenSearchUtils.getAuditIndex(domain)).build()).
100 value();
101 }
102
103 public IndexSettings defaultSettings() throws IOException {
104 return new IndexSettings.Builder().
105 analysis(new IndexSettingsAnalysis.Builder().
106 normalizer("string_lowercase", new Normalizer.Builder().
107 custom(new CustomNormalizer.Builder().
108 charFilter(List.of()).
109 filter("lowercase").
110 build()).
111 build()).
112 build()).
113 numberOfShards(numberOfShards).
114 numberOfReplicas(numberOfReplicas).
115 build();
116 }
117
118 public TypeMapping defaultAnyMapping() throws IOException {
119 return new TypeMapping.Builder().
120 dynamicTemplates(List.of(Map.of(
121 "strings",
122 new DynamicTemplate.Builder().
123 matchMappingType("string").
124 mapping(new Property.Builder().
125 keyword(new KeywordProperty.Builder().normalizer("string_lowercase").build()).
126 build()).
127 build()))).
128 build();
129 }
130
131 public TypeMapping defaultRealmMapping() throws IOException {
132 return new TypeMapping.Builder().
133 dynamicTemplates(List.of(Map.of(
134 "strings",
135 new DynamicTemplate.Builder().
136 matchMappingType("string").
137 mapping(new Property.Builder().
138 keyword(new KeywordProperty.Builder().normalizer("string_lowercase").build()).
139 build()).
140 build()))).
141 build();
142 }
143
144 public TypeMapping defaultAuditMapping() throws IOException {
145 return new TypeMapping.Builder().
146 dynamicTemplates(List.of(Map.of(
147 "strings",
148 new DynamicTemplate.Builder().
149 matchMappingType("string").
150 mapping(new Property.Builder().
151 keyword(new KeywordProperty.Builder().normalizer("string_lowercase").build()).
152 build()).
153 build()))).
154 properties(
155 "message",
156 new Property.Builder().object(new ObjectProperty.Builder().
157 properties(
158 "before",
159 new Property.Builder().
160 text(new TextProperty.Builder().analyzer("standard").build()).
161 build()).
162 properties(
163 "inputs",
164 new Property.Builder().
165 text(new TextProperty.Builder().analyzer("standard").build()).
166 build()).
167 properties(
168 "output",
169 new Property.Builder().
170 text(new TextProperty.Builder().analyzer("standard").build()).
171 build()).
172 properties(
173 "throwable",
174 new Property.Builder().
175 text(new TextProperty.Builder().analyzer("standard").build()).
176 build()).
177 build()).
178 build()).
179 build();
180 }
181
182 protected CreateIndexResponse doCreateAnyIndex(
183 final String domain,
184 final AnyTypeKind kind,
185 final IndexSettings settings,
186 final TypeMapping mappings) throws IOException {
187
188 return client.indices().create(
189 new CreateIndexRequest.Builder().
190 index(OpenSearchUtils.getAnyIndex(domain, kind)).
191 settings(settings).
192 mappings(mappings).
193 build());
194 }
195
196 public void createAnyIndex(
197 final String domain,
198 final AnyTypeKind kind,
199 final IndexSettings settings,
200 final TypeMapping mappings)
201 throws IOException {
202
203 try {
204 CreateIndexResponse response = doCreateAnyIndex(domain, kind, settings, mappings);
205
206 LOG.debug("Successfully created {} for {}: {}",
207 OpenSearchUtils.getAnyIndex(domain, kind), kind.name(), response);
208 } catch (OpenSearchException e) {
209 LOG.debug("Could not create index {} because it already exists",
210 OpenSearchUtils.getAnyIndex(domain, kind), e);
211
212 removeAnyIndex(domain, kind);
213 doCreateAnyIndex(domain, kind, settings, mappings);
214 }
215 }
216
217 public void removeAnyIndex(final String domain, final AnyTypeKind kind) throws IOException {
218 DeleteIndexResponse response = client.indices().delete(
219 new DeleteIndexRequest.Builder().index(OpenSearchUtils.getAnyIndex(domain, kind)).build());
220 LOG.debug("Successfully removed {}: {}", OpenSearchUtils.getAnyIndex(domain, kind), response);
221 }
222
223 protected CreateIndexResponse doCreateRealmIndex(
224 final String domain,
225 final IndexSettings settings,
226 final TypeMapping mappings) throws IOException {
227
228 return client.indices().create(
229 new CreateIndexRequest.Builder().
230 index(OpenSearchUtils.getRealmIndex(domain)).
231 settings(settings).
232 mappings(mappings).
233 build());
234 }
235
236 public void createRealmIndex(
237 final String domain,
238 final IndexSettings settings,
239 final TypeMapping mappings)
240 throws IOException {
241
242 try {
243 CreateIndexResponse response = doCreateRealmIndex(domain, settings, mappings);
244
245 LOG.debug("Successfully created realm index {}: {}",
246 OpenSearchUtils.getRealmIndex(domain), response);
247 } catch (OpenSearchException e) {
248 LOG.debug("Could not create realm index {} because it already exists",
249 OpenSearchUtils.getRealmIndex(domain), e);
250
251 removeRealmIndex(domain);
252 doCreateRealmIndex(domain, settings, mappings);
253 }
254 }
255
256 public void removeRealmIndex(final String domain) throws IOException {
257 DeleteIndexResponse response = client.indices().delete(
258 new DeleteIndexRequest.Builder().index(OpenSearchUtils.getRealmIndex(domain)).build());
259 LOG.debug("Successfully removed {}: {}", OpenSearchUtils.getRealmIndex(domain), response);
260 }
261
262 protected CreateIndexResponse doCreateAuditIndex(
263 final String domain,
264 final IndexSettings settings,
265 final TypeMapping mappings) throws IOException {
266
267 return client.indices().create(
268 new CreateIndexRequest.Builder().
269 index(OpenSearchUtils.getAuditIndex(domain)).
270 settings(settings).
271 mappings(mappings).
272 build());
273 }
274
275 public void createAuditIndex(
276 final String domain,
277 final IndexSettings settings,
278 final TypeMapping mappings)
279 throws IOException {
280
281 try {
282 CreateIndexResponse response = doCreateAuditIndex(domain, settings, mappings);
283
284 LOG.debug("Successfully created audit index {}: {}",
285 OpenSearchUtils.getAuditIndex(domain), response);
286 } catch (OpenSearchException e) {
287 LOG.debug("Could not create audit index {} because it already exists",
288 OpenSearchUtils.getAuditIndex(domain), e);
289
290 removeAuditIndex(domain);
291 doCreateAuditIndex(domain, settings, mappings);
292 }
293 }
294
295 public void removeAuditIndex(final String domain) throws IOException {
296 DeleteIndexResponse response = client.indices().delete(
297 new DeleteIndexRequest.Builder().index(OpenSearchUtils.getAuditIndex(domain)).build());
298 LOG.debug("Successfully removed {}: {}", OpenSearchUtils.getAuditIndex(domain), response);
299 }
300
301 @TransactionalEventListener
302 public void entity(final EntityLifecycleEvent<Entity> event) throws IOException {
303 LOG.debug("About to {} index for {}", event.getType().name(), event.getEntity());
304
305 if (event.getEntity() instanceof Any) {
306 Any<?> any = (Any<?>) event.getEntity();
307
308 if (event.getType() == SyncDeltaType.DELETE) {
309 DeleteRequest request = new DeleteRequest.Builder().index(
310 OpenSearchUtils.getAnyIndex(event.getDomain(), any.getType().getKind())).
311 id(any.getKey()).
312 build();
313 DeleteResponse response = client.delete(request);
314 LOG.debug("Index successfully deleted for {}[{}]: {}",
315 any.getType().getKind(), any.getKey(), response);
316 } else {
317 IndexRequest<Map<String, Object>> request = new IndexRequest.Builder<Map<String, Object>>().
318 index(OpenSearchUtils.getAnyIndex(event.getDomain(), any.getType().getKind())).
319 id(any.getKey()).
320 document(openSearchUtils.document(any)).
321 build();
322 IndexResponse response = client.index(request);
323 LOG.debug("Index successfully created or updated for {}: {}", any, response);
324 }
325 } else if (event.getEntity() instanceof Realm) {
326 Realm realm = (Realm) event.getEntity();
327
328 if (event.getType() == SyncDeltaType.DELETE) {
329 DeleteRequest request = new DeleteRequest.Builder().
330 index(OpenSearchUtils.getRealmIndex(event.getDomain())).
331 id(realm.getKey()).
332 refresh(Refresh.True).
333 build();
334 DeleteResponse response = client.delete(request);
335 LOG.debug("Index successfully deleted for {}: {}", realm, response);
336 } else {
337 IndexRequest<Map<String, Object>> request = new IndexRequest.Builder<Map<String, Object>>().
338 index(OpenSearchUtils.getRealmIndex(event.getDomain())).
339 id(realm.getKey()).
340 document(openSearchUtils.document(realm)).
341 refresh(Refresh.True).
342 build();
343 IndexResponse response = client.index(request);
344 LOG.debug("Index successfully created or updated for {}: {}", realm, response);
345 }
346 }
347 }
348
349 public void audit(final String domain, final long instant, final JsonNode message) throws IOException {
350 LOG.debug("About to audit");
351
352 IndexRequest<Map<String, Object>> request = new IndexRequest.Builder<Map<String, Object>>().
353 index(OpenSearchUtils.getAuditIndex(domain)).
354 id(SecureRandomUtils.generateRandomUUID().toString()).
355 document(openSearchUtils.document(instant, message, domain)).
356 build();
357 IndexResponse response = client.index(request);
358
359 LOG.debug("Audit successfully created: {}", response);
360 }
361 }