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