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