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