ac676024fb08e2bde30f667e40a400b40381ef20
[ccsdk/features.git] /
1 /*******************************************************************************
2  * ============LICENSE_START========================================================================
3  * ONAP : ccsdk feature sdnr wt
4  * =================================================================================================
5  * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
6  * =================================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
8  * in compliance with the License. You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software distributed under the License
13  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
14  * or implied. See the License for the specific language governing permissions and limitations under
15  * the License.
16  * ============LICENSE_END==========================================================================
17  ******************************************************************************/
18 package org.onap.ccsdk.features.sdnr.wt.database;
19
20 import java.io.IOException;
21 import java.lang.reflect.Field;
22 import java.util.List;
23
24 import javax.annotation.Nonnull;
25 import javax.annotation.Nullable;
26
27 import org.onap.ccsdk.features.sdnr.wt.common.database.DatabaseClient;
28 import org.onap.ccsdk.features.sdnr.wt.common.database.SearchHit;
29 import org.onap.ccsdk.features.sdnr.wt.common.database.SearchResult;
30 import org.onap.ccsdk.features.sdnr.wt.common.database.queries.QueryBuilder;
31 import org.onap.ccsdk.features.sdnr.wt.yangtools.YangToolsMapper;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.data.provider.rev190801.Entity;
33 import org.opendaylight.yangtools.concepts.Builder;
34 import org.opendaylight.yangtools.yang.binding.DataObject;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 import com.fasterxml.jackson.core.JsonProcessingException;
39
40 /**
41  * Class to rw yang-tool generated objects into elasticsearch database. For "ES _id" exchange the esIdAddAtributteName is used.
42  * This attribute mast be of type String and contains for read and write operations the object id.
43  * The function can be used without id handling.
44  * If id handling is required the parameter needs to be specified by class definition in yang and  setting the name by using setAttributeName()
45  *
46  * @param <T> Yang tools generated class object.
47  */
48 public class EsDataObjectReaderWriter<T extends DataObject> {
49
50         private final Logger LOG = LoggerFactory.getLogger(EsDataObjectReaderWriter.class);
51
52         /** Typename for elastic search data schema **/
53         private String dataTypeName;
54
55         /** Elasticsearch Database client to be used **/
56         private DatabaseClient db;
57
58         /** Mapper with configuration to use opendaylight yang-tools builder pattern for object creation **/
59         private YangToolsMapper yangtoolsMapper;
60
61         /** Class of T as attribute to allow JSON to Class object mapping **/
62         private Class<T> clazz;
63
64         /** Field is used to write id. If null no id handling **/
65         private @Nullable Field field;
66
67         /** Attribute that is used as id field for the database object **/
68         private @Nullable String esIdAddAtributteName;
69
70         /** Interface to be used for write operations. Rule for write: T extends S and  **/
71         private Class<? extends DataObject> writeInterfaceClazz; // == "S"
72
73         /**
74          * Elasticsearch database read and write for specific class, defined by opendaylight yang-tools.
75          *
76          * @param db Database access client
77          * @param dataTypeName typename in database schema
78          * @param clazz class of type to be handled
79          * @throws ClassNotFoundException
80          */
81         public EsDataObjectReaderWriter(DatabaseClient db, Entity dataTypeName, Class<T> clazz) throws ClassNotFoundException {
82                 this(db, dataTypeName.getName(), clazz);
83         }
84         public EsDataObjectReaderWriter(DatabaseClient db, String dataTypeName, Class<T> clazz) throws ClassNotFoundException {
85                 LOG.info("Create {} for datatype {} class {}", this.getClass().getName(), dataTypeName, clazz.getName());
86
87                 this.esIdAddAtributteName = null;
88                 this.field = null;
89                 this.writeInterfaceClazz = clazz;
90                 this.db = db;
91                 this.dataTypeName = dataTypeName;
92                 this.yangtoolsMapper = new YangToolsMapper();
93                 //this.yangtoolsMapper.assertBuilderClass(clazz);
94                 this.clazz = clazz;
95 //
96 //              if (! db.isExistsIndex(dataTypeName)) {
97 //                      throw new IllegalArgumentException("Index "+dataTypeName+" not existing.");
98 //              }
99         }
100
101         public String getDataTypeName() {
102                 return dataTypeName;
103         }
104         public Class<T> getClazz() {
105                 return clazz;
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 EsDataObjectReaderWriter<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 EsDataObjectReaderWriter<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                         T object = builder.build();
132                         attributeField = object.getClass().getDeclaredField(esIdAttributeName);
133                         if (attributeField.getType().equals(String.class)) {
134                                 attributeField.setAccessible(true);
135                                 this.esIdAddAtributteName = esIdAttributeName; //Set new status if everything OK
136                                 this.field = attributeField;
137                         } else {
138                                 String msg = "Wrong field type " + attributeField.getType().getName() + " of " + esIdAttributeName;
139                                 LOG.debug(msg);
140                                 throw new IllegalArgumentException(msg);
141                         }
142                 } catch (NoSuchFieldException e) {
143                         // Convert to run-time exception
144                         String msg = "NoSuchFieldException for '" + esIdAttributeName + "' in class " + clazz.getName();
145                         LOG.debug(msg);
146                         throw new IllegalArgumentException(msg);
147                 } catch (SecurityException e) {
148                         LOG.debug("Access problem "+esIdAttributeName,e);
149                         throw e;
150                 }
151                 return this;
152         }
153
154         /**
155          * Specify subclass of T for write operations.
156          * @param writeInterfaceClazz
157          */
158         public EsDataObjectReaderWriter<T> setWriteInterface( @Nonnull Class<? extends DataObject> writeInterfaceClazz ) {
159                 LOG.debug("Set write interface to {}", writeInterfaceClazz);
160                 if (writeInterfaceClazz == null)
161                         throw new IllegalArgumentException("Null not allowed here.");
162
163                 this.writeInterfaceClazz = writeInterfaceClazz;
164                 return this;
165         }
166
167         /**
168          * Write child object to database with specific id
169          * @param object
170          * @param @Nullable esId use the id or if null generate unique id
171          * @return String with id or null
172          */
173         public @Nullable <S extends DataObject> String write(S object, @Nullable String esId) {
174                 if (writeInterfaceClazz.isInstance(object)) {
175                         try {
176                                 String json = yangtoolsMapper.writeValueAsString(object);
177                                 return db.doWriteRaw(dataTypeName, esId, json);
178                         } catch (JsonProcessingException e) {
179                                 LOG.error("Write problem: ", e);
180                         }
181                 } else {
182                         LOG.error("Type {} does not provide interface {}", object!=null?object.getClass().getName():"null",
183                                         writeInterfaceClazz.getName());
184                 }
185                 return null;
186         }
187         /**
188          * Update partial child object to database with match/term query
189          * @param object
190          * @param esId
191          * @return String with esId or null
192          */
193         public @Nullable <S extends DataObject> String update(S object, QueryBuilder query) {
194                 if (writeInterfaceClazz.isInstance(object)) {
195                         try {
196                                 String json = yangtoolsMapper.writeValueAsString(object);
197                                 return db.doUpdate(this.dataTypeName,json,query);
198                         } catch (JsonProcessingException e) {
199                                 LOG.error("Update problem: ", e);
200                         }
201                 } else {
202                         LOG.error("Type {} does not provide interface {}", object!=null?object.getClass().getName():"null",
203                                         writeInterfaceClazz.getName());
204                 }
205                 return null;
206         }
207         /**
208          * Write/ update partial child object to database with specific id Write if not
209          * exists, else update
210          * @param object
211          * @param esId
212          * @return String with esId or null
213          */
214         public @Nullable <S extends DataObject> String update(S object, String esId) {
215                 return this.update(object, esId,null);
216         }
217         public @Nullable <S extends DataObject> String update(S object, String esId,List<String> onylForInsert) {
218                 if (writeInterfaceClazz.isInstance(object)) {
219                         try {
220                                 String json = yangtoolsMapper.writeValueAsString(object);
221                                 return db.doUpdateOrCreate(dataTypeName, esId, json,onylForInsert);
222                         } catch (JsonProcessingException e) {
223                                 LOG.error("Update problem: ", e);
224                         }
225                 } else {
226                         LOG.error("Type {} does not provide interface {}", object!=null?object.getClass().getName():"null",
227                                         writeInterfaceClazz.getName());
228                 }
229                 return null;
230         }
231
232     /**
233      * Read object from database, by using the id field
234      * @param object
235      * @return
236      */
237     public @Nullable T read(String esId) {
238         @Nullable T res = (T)null;
239                 if (esId != null) {
240                         String json = db.doReadJsonData(dataTypeName, esId);
241                         try {
242                                 res = yangtoolsMapper.readValue(json.getBytes(), clazz);
243                         } catch (IOException e) {
244                                 LOG.error("Problem: ", e);
245                         }
246                 }
247         return res;
248     }
249
250     /**
251      * Remove object
252      * @param esId to identify the object.
253      * @return success
254      */
255     public boolean remove(String esId) {
256         return db.doRemove(this.dataTypeName, esId);
257     }
258
259         public int remove(QueryBuilder query) {
260                 return this.db.doRemove(this.dataTypeName, query);
261         }
262     /**
263      * Get all elements of related type
264      * @return all Elements
265      */
266     public SearchResult<T> doReadAll() {
267         return doReadAll(null);
268     }
269     public SearchResult<T> doReadAll(QueryBuilder query) {
270         return this.doReadAll(query,false);
271         }
272     /**
273      * Read all existing objects of a type
274      * @param query for the elements
275      * @return the list of all objects
276      */
277
278     public SearchResult<T> doReadAll(QueryBuilder query, boolean ignoreException) {
279
280         SearchResult<T> res = new SearchResult<T>();
281         int idx = 0;                //Idx for getAll
282         int iterateLength = 100;    //Step width for iterate
283
284         SearchResult<SearchHit> result;
285         List<SearchHit> hits;
286         do {
287             if(query!=null) {
288                 LOG.debug("read data in {} with query {}",dataTypeName,query.toJSON());
289                 result=db.doReadByQueryJsonData( dataTypeName, query,ignoreException);
290             }
291             else {
292                 result = db.doReadAllJsonData(dataTypeName,ignoreException);
293             }
294             hits=result.getHits();
295             LOG.debug("Read: {} elements: {}  Failures: {}",dataTypeName,hits.size(), yangtoolsMapper.getMappingFailures());
296
297             T object;
298             idx += result.getHits().size();
299                         for (SearchHit hit : hits) {
300                                 object = getT(hit.getSourceAsString());
301                                 LOG.debug("Mapp Object: {}\nSource: '{}'\nResult: '{}'\n Failures: {}", hit.getId(),
302                                                 hit.getSourceAsString(), object, yangtoolsMapper.getMappingFailures());
303                                 if (object != null) {
304                                         setEsId(object, hit.getId());
305                                         res.add(object);
306                                 } else {
307                                         LOG.warn("Mapp result null Object: {}\n Source: '{}'\n : '", hit.getId(), hit.getSourceAsString());
308                                 }
309                         }
310
311                 } while (hits.size() == iterateLength); // Do it until end indicated, because less hits than iterateLength
312                                                                                                 // allows.
313         res.setTotal(result.getTotal());
314         return res;
315     }
316
317         /* ---------------------------------------------
318      * Private functions
319      */
320
321         private void setEsId(T object, String esId) {
322                 if (field != null) {
323                         try {
324                                 field.set(object, esId);
325                         } catch (IllegalArgumentException | IllegalAccessException e) {
326                                 LOG.debug("Field set problem.", e);                     }
327                 }
328         }
329
330     private @Nullable T getT(String jsonString) {
331         try {
332                         return yangtoolsMapper.readValue( jsonString, clazz );
333                 } catch (IOException e) {
334                         LOG.info("Mapping problem", e);
335                         return (T)null;
336                 }
337     }
338
339 }