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