4a748069f46ad65573ba1e907139d4c4850dcb6d
[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 is used.
46  * This attribute mast be of type String and contains for read and write operations the object id.
47  * The function can be used without id handling.
48  * If id handling is required the parameter needs to be specified by class definition 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) throws ClassNotFoundException {
86                 this(db, dataTypeName.getName(), clazz);
87         }
88         public EsDataObjectReaderWriter(DatabaseClient db, String dataTypeName, Class<T> clazz) throws ClassNotFoundException {
89                 LOG.info("Create {} for datatype {} class {}", this.getClass().getName(), dataTypeName, clazz.getName());
90
91                 this.esIdAddAtributteName = null;
92                 this.field = null;
93                 this.writeInterfaceClazz = clazz;
94                 this.db = db;
95                 this.dataTypeName = dataTypeName;
96                 this.yangtoolsMapper = new YangToolsMapper();
97                 //this.yangtoolsMapper.assertBuilderClass(clazz);
98                 this.clazz = clazz;
99 //
100 //              if (! db.isExistsIndex(dataTypeName)) {
101 //                      throw new IllegalArgumentException("Index "+dataTypeName+" not existing.");
102 //              }
103         }
104
105         public String getDataTypeName() {
106                 return dataTypeName;
107         }
108         public Class<T> getClazz() {
109                 return clazz;
110         }
111         /**
112          * Simlar to {@link #setEsIdAttributeName()}, but adapts the parameter to yangtools attribute naming schema
113          * @param esIdAttributeName is converted to UnderscoreCamelCase
114          * @return this for further operations.
115          */
116         public EsDataObjectReaderWriter<T> setEsIdAttributeNameCamelized(String esIdAttributeName) {
117                 return setEsIdAttributeName(YangToolsMapper.toCamelCaseAttributeName(esIdAttributeName));
118         }
119
120         /**
121          * Attribute name of class that is containing the object id
122          * @param esIdAttributeName of the implementation class for the yangtools interface.
123          *        Expected attribute name format is CamelCase with leading underline. @
124          * @return this for further operations.
125          * @throws SecurityException if no access or IllegalArgumentException if wrong type or no attribute with this name.
126          */
127         public EsDataObjectReaderWriter<T> setEsIdAttributeName(String esIdAttributeName) {
128                 LOG.debug("Set attribute '{}'", esIdAttributeName);
129                 this.esIdAddAtributteName = null; // Reset status
130                 this.field = null;
131
132                 Field attributeField;
133                 try {
134                         Builder<T> builder = yangtoolsMapper.getBuilder(clazz);
135                         T object = builder.build();
136                         attributeField = object.getClass().getDeclaredField(esIdAttributeName);
137                         if (attributeField.getType().equals(String.class)) {
138                                 attributeField.setAccessible(true);
139                                 this.esIdAddAtributteName = esIdAttributeName; //Set new status if everything OK
140                                 this.field = attributeField;
141                         } else {
142                                 String msg = "Wrong field type " + attributeField.getType().getName() + " of " + esIdAttributeName;
143                                 LOG.debug(msg);
144                                 throw new IllegalArgumentException(msg);
145                         }
146                 } catch (NoSuchFieldException e) {
147                         // Convert to run-time exception
148                         String msg = "NoSuchFieldException for '" + esIdAttributeName + "' in class " + clazz.getName();
149                         LOG.debug(msg);
150                         throw new IllegalArgumentException(msg);
151                 } catch (SecurityException e) {
152                         LOG.debug("Access problem "+esIdAttributeName,e);
153                         throw e;
154                 }
155                 return this;
156         }
157
158         /**
159          * Specify subclass of T for write operations.
160          * @param writeInterfaceClazz
161          */
162         public EsDataObjectReaderWriter<T> setWriteInterface( @Nonnull Class<? extends DataObject> writeInterfaceClazz ) {
163                 LOG.debug("Set write interface to {}", writeInterfaceClazz);
164                 if (writeInterfaceClazz == null)
165                         throw new IllegalArgumentException("Null not allowed here.");
166
167                 this.writeInterfaceClazz = writeInterfaceClazz;
168                 return this;
169         }
170
171         /**
172          * Write child object to database with specific id
173          * @param object
174          * @param @Nullable esId use the id or if null generate unique id
175          * @return String with id or null
176          */
177         public @Nullable <S extends DataObject> String write(S object, @Nullable String esId) {
178                 if (writeInterfaceClazz.isInstance(object)) {
179                         try {
180                                 String json = yangtoolsMapper.writeValueAsString(object);
181                                 return db.doWriteRaw(dataTypeName, esId, json);
182                         } catch (JsonProcessingException e) {
183                                 LOG.error("Write problem: ", e);
184                         }
185                 } else {
186                         LOG.error("Type {} does not provide interface {}", object!=null?object.getClass().getName():"null",
187                                         writeInterfaceClazz.getName());
188                 }
189                 return null;
190         }
191         /**
192          * Update partial child object to database with match/term query
193          * @param object
194          * @param esId
195          * @return String with esId or null
196          */
197         public @Nullable <S extends DataObject> boolean update(S object, QueryBuilder query) {
198                 if (writeInterfaceClazz.isInstance(object)) {
199                         try {
200                                 String json = yangtoolsMapper.writeValueAsString(object);
201                                 return db.doUpdate(this.dataTypeName,json,query);
202                         } catch (JsonProcessingException e) {
203                                 LOG.error("Update problem: ", e);
204                         }
205                 } else {
206                         LOG.error("Type {} does not provide interface {}", object!=null?object.getClass().getName():"null",
207                                         writeInterfaceClazz.getName());
208                 }
209                 return false;
210         }
211         /**
212          * Write/ update partial child object to database with specific id Write if not
213          * exists, else update
214          * @param object
215          * @param esId
216          * @return String with esId or null
217          */
218         public @Nullable <S extends DataObject> String update(S object, String esId) {
219                 return this.update(object, esId,null);
220         }
221         public @Nullable <S extends DataObject> String update(S object, String esId,List<String> onylForInsert) {
222                 if (writeInterfaceClazz.isInstance(object)) {
223                         try {
224                                 String json = yangtoolsMapper.writeValueAsString(object);
225                                 return db.doUpdateOrCreate(dataTypeName, esId, json,onylForInsert);
226                         } catch (JsonProcessingException e) {
227                                 LOG.error("Update problem: ", e);
228                         }
229                 } else {
230                         LOG.error("Type {} does not provide interface {}", object!=null?object.getClass().getName():"null",
231                                         writeInterfaceClazz.getName());
232                 }
233                 return null;
234         }
235
236     /**
237      * Read object from database, by using the id field
238      * @param object
239      * @return
240      */
241     public @Nullable T read(String esId) {
242         @Nullable T res = (T)null;
243                 if (esId != null) {
244                         String json = db.doReadJsonData(dataTypeName, esId);
245                         try {
246                                 res = yangtoolsMapper.readValue(json.getBytes(), clazz);
247                         } catch (IOException e) {
248                                 LOG.error("Problem: ", e);
249                         }
250                 }
251         return res;
252     }
253
254     /**
255      * Remove object
256      * @param esId to identify the object.
257      * @return success
258      */
259     public boolean remove(String esId) {
260         return db.doRemove(this.dataTypeName, esId);
261     }
262
263         public int remove(QueryBuilder query) {
264                 return this.db.doRemove(this.dataTypeName, query);
265         }
266     /**
267      * Get all elements of related type
268      * @return all Elements
269      */
270     public SearchResult<T> doReadAll() {
271         return doReadAll(null);
272     }
273     public SearchResult<T> doReadAll(QueryBuilder query) {
274         return this.doReadAll(query,false);
275         }
276     /**
277      * Read all existing objects of a type
278      * @param query for the elements
279      * @return the list of all objects
280      */
281
282     public SearchResult<T> doReadAll(QueryBuilder query, boolean ignoreException) {
283
284         SearchResult<T> res = new SearchResult<T>();
285         int idx = 0;                //Idx for getAll
286         int iterateLength = 100;    //Step width for iterate
287
288         SearchResult<SearchHit> result;
289         List<SearchHit> hits;
290         do {
291             if(query!=null) {
292                 LOG.debug("read data in {} with query {}",dataTypeName,query.toJSON());
293                 result=db.doReadByQueryJsonData( dataTypeName, query,ignoreException);
294             }
295             else {
296                 result = db.doReadAllJsonData(dataTypeName,ignoreException);
297             }
298             hits=result.getHits();
299             LOG.debug("Read: {} elements: {}  Failures: {}",dataTypeName,hits.size(), yangtoolsMapper.getMappingFailures());
300
301             T object;
302             idx += result.getHits().size();
303                         for (SearchHit hit : hits) {
304                                 object = getT(hit.getSourceAsString());
305                                 LOG.debug("Mapp Object: {}\nSource: '{}'\nResult: '{}'\n Failures: {}", hit.getId(),
306                                                 hit.getSourceAsString(), object, yangtoolsMapper.getMappingFailures());
307                                 if (object != null) {
308                                         setEsId(object, hit.getId());
309                                         res.add(object);
310                                 } else {
311                                         LOG.warn("Mapp result null Object: {}\n Source: '{}'\n : '", hit.getId(), hit.getSourceAsString());
312                                 }
313                         }
314
315                 } while (hits.size() == iterateLength); // Do it until end indicated, because less hits than iterateLength
316                                                                                                 // allows.
317         res.setTotal(result.getTotal());
318         return res;
319     }
320
321         /* ---------------------------------------------
322      * Private functions
323      */
324
325         private void setEsId(T object, String esId) {
326                 if (field != null) {
327                         try {
328                                 field.set(object, esId);
329                         } catch (IllegalArgumentException | IllegalAccessException e) {
330                                 LOG.debug("Field set problem.", e);                     }
331                 }
332         }
333
334     private @Nullable T getT(String jsonString) {
335         try {
336                         return yangtoolsMapper.readValue( jsonString, clazz );
337                 } catch (IOException e) {
338                         LOG.info("Mapping problem", e);
339                         return (T)null;
340                 }
341     }
342
343 }