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