a10864c96dc11d8a5574379c6c4c9c972a1d094e
[ccsdk/features.git] /
1 /*
2  * ============LICENSE_START=======================================================
3  * ONAP : ccsdk features
4  * ================================================================================
5  * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property.
6  * All rights reserved.
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *     http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  * ============LICENSE_END=========================================================
20  *
21  */
22 package org.onap.ccsdk.features.sdnr.wt.dataprovider.database.elasticsearch;
23
24 import com.fasterxml.jackson.core.JsonProcessingException;
25 import java.io.IOException;
26 import java.lang.reflect.Field;
27 import java.lang.reflect.InvocationTargetException;
28 import java.util.List;
29 import javax.annotation.Nonnull;
30 import javax.annotation.Nullable;
31 import org.eclipse.jdt.annotation.NonNull;
32 import org.onap.ccsdk.features.sdnr.wt.common.database.DatabaseClient;
33 import org.onap.ccsdk.features.sdnr.wt.common.database.SearchHit;
34 import org.onap.ccsdk.features.sdnr.wt.common.database.SearchResult;
35 import org.onap.ccsdk.features.sdnr.wt.common.database.queries.QueryBuilder;
36 import org.onap.ccsdk.features.sdnr.wt.yang.mapper.YangToolsMapper2;
37 import org.onap.ccsdk.features.sdnr.wt.yang.mapper.YangToolsMapperHelper;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.data.provider.rev201110.Entity;
39 import org.opendaylight.yangtools.concepts.Builder;
40 import org.opendaylight.yangtools.yang.binding.DataObject;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 /**
45  * Class to rw yang-tool generated objects into elasticsearch database. For "ES _id" exchange the esIdAddAtributteName
46  * is used. This attribute mast be of type String and contains for read and write operations the object id. The function
47  * can be used without id handling. If id handling is required the parameter needs to be specified by class definition
48  * in yang and setting the name by using setAttributeName()
49  *
50  * Due to using Jackson base interfaces the org.eclipse.jdt.annotation.NonNull needs to be used here to get rid of
51  * warnings
52  *
53  * @param <T> Yang tools generated class object.
54  */
55 public class EsDataObjectReaderWriter2<T extends DataObject> {
56
57     private static final Logger LOG = LoggerFactory.getLogger(EsDataObjectReaderWriter2.class);
58
59     /** Typename for elastic search data schema **/
60     private String dataTypeName;
61
62     /** Elasticsearch Database client to be used **/
63     private DatabaseClient db;
64
65     /** Mapper with configuration to use opendaylight yang-tools builder pattern for object creation **/
66     private YangToolsMapper2<T> yangtoolsMapper;
67
68     /** Class of T as attribute to allow JSON to Class object mapping **/
69     private Class<T> clazz;
70
71     /** Field is used to write id. If null no id handling **/
72     private @Nullable Field field;
73
74     /** Attribute that is used as id field for the database object **/
75     private @Nullable String esIdAddAtributteName;
76
77     /** Interface to be used for write operations. Rule for write: T extends S and **/
78     private Class<? extends DataObject> writeInterfaceClazz; // == "S"
79     /** Flag true to sync this attribute during write always, what is slow and false do not sync */
80     private final boolean syncAfterWrite;
81
82     protected boolean doFullsizeRequest;
83
84     /**
85      * Elasticsearch database read and write for specific class, defined by opendaylight yang-tools.
86      *
87      * @param <X>
88      * @param <B>
89      * @param db Database access client
90      * @param dataTypeName typename in database schema
91      * @param clazz class of type to be handled
92      * @param builderClazz class to build related object if builder pattern should be used.
93      * @param syncAfterWrite
94      * @throws ClassNotFoundException
95      */
96     public <X extends T, B> EsDataObjectReaderWriter2(DatabaseClient db,
97             String dataTypeName, @Nonnull Class<T> clazz, @Nullable Class<B> builderClazz, boolean syncAfterWrite)
98             throws ClassNotFoundException {
99         LOG.info("Create {} for datatype {} class {}", this.getClass().getName(), dataTypeName, clazz.getName());
100
101         this.esIdAddAtributteName = null;
102         this.field = null;
103         this.writeInterfaceClazz = clazz;
104         this.db = db;
105         this.dataTypeName = dataTypeName;
106         this.yangtoolsMapper = new YangToolsMapper2<>(clazz, builderClazz);
107         this.clazz = clazz;
108         this.syncAfterWrite = syncAfterWrite;
109         this.doFullsizeRequest = false;
110     }
111
112     public void setFullsizeRequest(boolean fullsizeRequest) {
113         this.doFullsizeRequest = fullsizeRequest;
114     }
115     public <X extends T, B> EsDataObjectReaderWriter2(DatabaseClient db,
116             Entity dataTypeName, @Nonnull Class<T> clazz, @Nullable Class<B> builderClazz)
117             throws ClassNotFoundException {
118         this(db, dataTypeName.getName(), clazz, builderClazz, false);
119     }
120
121     public <X extends T, B> EsDataObjectReaderWriter2(DatabaseClient db,
122             Entity dataTypeName, @Nonnull Class<T> clazz, @Nullable Class<B> builderClazz, boolean syncAfterWrite)
123             throws ClassNotFoundException {
124         this(db, dataTypeName.getName(), clazz, builderClazz, syncAfterWrite);
125     }
126
127     public EsDataObjectReaderWriter2(DatabaseClient db, Entity dataTypeName, @Nonnull Class<T> clazz,
128             boolean syncAfterWrite) throws ClassNotFoundException {
129         this(db, dataTypeName.getName(), clazz, null, syncAfterWrite);
130     }
131
132     public EsDataObjectReaderWriter2(DatabaseClient db, Entity dataTypeName, Class<T> clazz)
133             throws ClassNotFoundException {
134         this(db, dataTypeName.getName(), clazz, null, false);
135     }
136
137     /**
138      * Get Datatype name
139      *
140      * @return string with dataTypeName
141      */
142     public String getDataTypeName() {
143         return dataTypeName;
144     }
145
146     /**
147      * Simlar to {@link #setEsIdAttributeName()}, but adapts the parameter to yangtools attribute naming schema
148      *
149      * @param esIdAttributeName is converted to UnderscoreCamelCase
150      * @return this for further operations.
151      */
152     public EsDataObjectReaderWriter2<T> setEsIdAttributeNameCamelized(String esIdAttributeName) {
153         return setEsIdAttributeName(YangToolsMapperHelper.toCamelCaseAttributeName(esIdAttributeName));
154     }
155
156     /**
157      * Attribute name of class that is containing the object id
158      *
159      * @param esIdAttributeName of the implementation class for the yangtools interface. Expected attribute name format
160      *        is CamelCase with leading underline. @
161      * @return this for further operations.
162      * @throws SecurityException if no access or IllegalArgumentException if wrong type or no attribute with this name.
163      */
164     public <B> EsDataObjectReaderWriter2<T> setEsIdAttributeName(String esIdAttributeName) {
165         LOG.debug("Set attribute '{}'", esIdAttributeName);
166         this.esIdAddAtributteName = null; // Reset status
167         this.field = null;
168
169         Field attributeField;
170         try {
171             B builder = yangtoolsMapper.getBuilder(clazz);
172             if (builder == null) {
173                 String msg = "No builder for " + clazz;
174                 LOG.debug(msg);
175                 throw new IllegalArgumentException(msg);
176             } else {
177                 T object = YangToolsMapperHelper.callBuild(builder);
178                 attributeField = object.getClass().getDeclaredField(esIdAttributeName);
179                 if (attributeField.getType().equals(String.class)) {
180                     attributeField.setAccessible(true);
181                     this.esIdAddAtributteName = esIdAttributeName; // Set new status if everything OK
182                     this.field = attributeField;
183                 } else {
184                     String msg = "Wrong field type " + attributeField.getType().getName() + " of " + esIdAttributeName;
185                     LOG.debug(msg);
186                     throw new IllegalArgumentException(msg);
187                 }
188             }
189         } catch (NoSuchFieldException e) {
190             // Convert to run-time exception
191             String msg = "NoSuchFieldException for '" + esIdAttributeName + "' in class " + clazz.getName();
192             LOG.debug(msg);
193             throw new IllegalArgumentException(msg);
194         } catch (SecurityException e) {
195             LOG.debug("Access problem " + esIdAttributeName, e);
196             throw e;
197         } catch (NoSuchMethodException e) {
198                         // TODO Auto-generated catch block
199                         e.printStackTrace();
200                 } catch (IllegalAccessException e) {
201                         // TODO Auto-generated catch block
202                         e.printStackTrace();
203                 } catch (IllegalArgumentException e) {
204                         // TODO Auto-generated catch block
205                         e.printStackTrace();
206                 } catch (InvocationTargetException e) {
207                         // TODO Auto-generated catch block
208                         e.printStackTrace();
209                 }
210         return this;
211     }
212
213     /**
214      * Specify subclass of T for write operations.
215      *
216      * @param writeInterfaceClazz
217      */
218     public EsDataObjectReaderWriter2<T> setWriteInterface(Class<? extends DataObject> writeInterfaceClazz) {
219         LOG.debug("Set write interface to {}", writeInterfaceClazz);
220         if (writeInterfaceClazz == null) {
221             throw new IllegalArgumentException("Null not allowed here.");
222         }
223
224         this.writeInterfaceClazz = writeInterfaceClazz;
225         return this;
226     }
227
228     public interface IdGetter<S extends DataObject> {
229         String getId(S object);
230     }
231
232     public <S extends DataObject> void write(List<S> objectList, IdGetter<S> idGetter) {
233         for (S object : objectList) {
234             write(object, idGetter.getId(object));
235         }
236     }
237
238     /**
239      * Write child object to database with specific id
240      *
241      * @param object to be written
242      * @param esId use the id or if null generate unique id
243      * @return String with id or null
244      */
245     public @Nullable <S extends DataObject> String write(S object, @Nullable String esId) {
246         if (object != null && writeInterfaceClazz.isInstance(object)) {
247             try {
248                 String json = yangtoolsMapper.writeValueAsString(object);
249                 return db.doWriteRaw(dataTypeName, esId, json, this.syncAfterWrite);
250             } catch (JsonProcessingException e) {
251                 LOG.error("Write problem: ", e);
252             }
253         } else {
254             LOG.error("Type {} does not provide interface {}", object != null ? object.getClass().getName() : "null",
255                     writeInterfaceClazz.getName());
256         }
257         return null;
258     }
259
260     /**
261      * Update partial child object to database with match/term query
262      *
263      * @param <S> of object
264      * @param object to write
265      * @param query for write of specific attributes
266      * @return json string with new Object
267      */
268     public @Nullable <S extends DataObject> boolean update(S object, QueryBuilder query) {
269         if (object != null && writeInterfaceClazz.isInstance(object)) {
270             try {
271                 String json = yangtoolsMapper.writeValueAsString(object);
272                 return db.doUpdate(this.dataTypeName, json, query);
273             } catch (JsonProcessingException e) {
274                 LOG.error("Update problem: ", e);
275             }
276         } else {
277             LOG.error("Type {} does not provide interface {}", object != null ? object.getClass().getName() : "null",
278                     writeInterfaceClazz.getName());
279         }
280         return false;
281     }
282
283     /**
284      * Write/ update partial child object to database with specific id Write if not exists, else update
285      *
286      * @param object
287      * @param esId
288      * @return String with esId or null
289      */
290     public @Nullable <S extends DataObject> String update(S object, String esId) {
291         return this.updateOrCreate(object, esId, null);
292     }
293
294     /**
295      * See {@link doUpdateOrCreate(String dataTypeName, String esId, String json, List<String> doNotUpdateField) }
296      */
297     public @Nullable <S extends DataObject> String updateOrCreate(S object, String esId, List<String> onlyForInsert) {
298         if (object != null && writeInterfaceClazz.isInstance(object)) {
299             try {
300                 String json = yangtoolsMapper.writeValueAsString(object);
301                 return db.doUpdateOrCreate(dataTypeName, esId, json, onlyForInsert);
302             } catch (JsonProcessingException e) {
303                 LOG.error("Update problem: ", e);
304             }
305         } else {
306             LOG.error("Type {} does not provide interface {}", object != null ? object.getClass().getName() : "null",
307                     writeInterfaceClazz.getName());
308         }
309         return null;
310     }
311
312     /**
313      * Read object from database, by using the id field
314      *
315      * @param object
316      * @return
317      */
318     public @Nullable T read(String esId) {
319         @Nullable
320         T res = null;
321         if (esId != null) {
322             String json = db.doReadJsonData(dataTypeName, esId);
323             if (json != null) {
324                 try {
325                     res = yangtoolsMapper.readValue(json.getBytes(), clazz);
326                 } catch (IOException e) {
327                     LOG.error("Problem: ", e);
328                 }
329             } else {
330                 LOG.debug("Can not read from DB id {} type {}", esId, dataTypeName);
331             }
332         }
333         return res;
334     }
335
336     /**
337      * Remove object
338      *
339      * @param esId to identify the object.
340      * @return success
341      */
342     public boolean remove(String esId) {
343         return db.doRemove(this.dataTypeName, esId);
344     }
345
346     public int remove(QueryBuilder query) {
347         return this.db.doRemove(this.dataTypeName, query);
348     }
349
350     /**
351      * Get all elements of related type
352      *
353      * @return all Elements
354      */
355     public SearchResult<T> doReadAll() {
356         return doReadAll(null);
357     }
358
359     public SearchResult<T> doReadAll(QueryBuilder query) {
360         return this.doReadAll(query, false);
361     }
362
363     /**
364      * Read all existing objects of a type
365      *
366      * @param query for the elements
367      * @return the list of all objects
368      */
369
370     public SearchResult<T> doReadAll(QueryBuilder query, boolean ignoreException) {
371
372         if(this.doFullsizeRequest) {
373             query.doFullsizeRequest();
374         }
375         SearchResult<T> res = new SearchResult<>();
376         SearchResult<SearchHit> result;
377         List<SearchHit> hits;
378         if (query != null) {
379             LOG.debug("read data in {} with query {}", dataTypeName, query.toJSON());
380             result = db.doReadByQueryJsonData(dataTypeName, query, ignoreException);
381         } else {
382             result = db.doReadAllJsonData(dataTypeName, ignoreException);
383         }
384         hits = result.getHits();
385         LOG.debug("Read: {} elements: {}", dataTypeName, hits.size());
386
387         T object;
388         for (SearchHit hit : hits) {
389             object = getT(hit.getSourceAsString());
390             LOG.debug("Mapp Object: {}\nSource: '{}'\nResult: '{}'\n", hit.getId(),
391                     hit.getSourceAsString(), object);
392             if (object != null) {
393                 setEsId(object, hit.getId());
394                 res.add(object);
395             } else {
396                 LOG.warn("Mapp result null Object: {}\n Source: '{}'\n : '", hit.getId(), hit.getSourceAsString());
397             }
398         }
399         res.setTotal(result.getTotal());
400         return res;
401     }
402
403     /* ---------------------------------------------
404      * Private functions
405      */
406
407     private void setEsId(T object, String esId) {
408         if (field != null) {
409             try {
410                 field.set(object, esId);
411             } catch (IllegalArgumentException | IllegalAccessException e) {
412                 LOG.debug("Field set problem.", e);
413             }
414         }
415     }
416
417     private @Nullable T getT(String jsonString) {
418         try {
419             return yangtoolsMapper.readValue(jsonString, clazz);
420         } catch (IOException e) {
421             LOG.info("Mapping problem {}:", clazz.getName(), e);
422             return null;
423         }
424     }
425
426 }