Merge "YANG Model update for A1 Adapter"
[ccsdk/features.git] / sdnr / wt / data-provider / database / src / main / java / org / onap / ccsdk / features / sdnr / wt / database / EsDataObjectReaderWriter2.java
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.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.yangtools.YangToolsMapper;
33 import org.onap.ccsdk.features.sdnr.wt.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
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      * Update partial child object to database with match/term query
202      * @param object
203      * @param esId
204      * @return String with esId or null
205      */
206     public @Nullable <S extends DataObject> String update(S object, QueryBuilder query) {
207         if (writeInterfaceClazz.isInstance(object)) {
208             try {
209                 String json = yangtoolsMapper.writeValueAsString(object);
210                 return db.doUpdate(this.dataTypeName,json,query);
211             } catch (JsonProcessingException e) {
212                 LOG.error("Update problem: ", e);
213             }
214         } else {
215             LOG.error("Type {} does not provide interface {}", object!=null?object.getClass().getName():"null",
216                     writeInterfaceClazz.getName());
217         }
218         return null;
219     }
220     /**
221      * Write/ update partial child object to database with specific id Write if not
222      * exists, else update
223      * @param object
224      * @param esId
225      * @return String with esId or null
226      */
227     public @Nullable <S extends DataObject> String update(S object, String esId) {
228         return this.updateOrCreate(object, esId,null);
229     }
230     /**
231      * See {@link doUpdateOrCreate(String dataTypeName, String esId, String json, List<String> doNotUpdateField) }
232      */
233     public @Nullable <S extends DataObject> String updateOrCreate(S object, String esId,List<String> onlyForInsert) {
234         if (writeInterfaceClazz.isInstance(object)) {
235             try {
236                 String json = yangtoolsMapper.writeValueAsString(object);
237                 return db.doUpdateOrCreate(dataTypeName, esId, json,onlyForInsert);
238             } catch (JsonProcessingException e) {
239                 LOG.error("Update problem: ", e);
240             }
241         } else {
242             LOG.error("Type {} does not provide interface {}", object!=null?object.getClass().getName():"null",
243                     writeInterfaceClazz.getName());
244         }
245         return null;
246     }
247
248     /**
249      * Read object from database, by using the id field
250      * @param object
251      * @return
252      */
253     public @Nullable T read(String esId) {
254         @Nullable
255         T res = null;
256         if (esId != null) {
257             String json = db.doReadJsonData(dataTypeName, esId);
258             if (json != null) {
259                 try {
260                     res = yangtoolsMapper.readValue(json.getBytes(), clazz);
261                 } catch (IOException e) {
262                     LOG.error("Problem: ", e);
263                 }
264             } else {
265                 LOG.debug("Can not read from DB id {} type {}", esId, dataTypeName);
266             }
267         }
268         return res;
269     }
270
271     /**
272      * Remove object
273      * @param esId to identify the object.
274      * @return success
275      */
276     public boolean remove(String esId) {
277         return db.doRemove(this.dataTypeName, esId);
278     }
279
280     public int remove(QueryBuilder query) {
281         return this.db.doRemove(this.dataTypeName, query);
282     }
283     /**
284      * Get all elements of related type
285      * @return all Elements
286      */
287     public SearchResult<T> doReadAll() {
288         return doReadAll(null);
289     }
290     public SearchResult<T> doReadAll(QueryBuilder query) {
291         return this.doReadAll(query,false);
292     }
293     /**
294      * Read all existing objects of a type
295      * @param query for the elements
296      * @return the list of all objects
297      */
298
299     public SearchResult<T> doReadAll(QueryBuilder query, boolean ignoreException) {
300
301         SearchResult<T> res = new SearchResult<>();
302         int idx = 0;                //Idx for getAll
303         int iterateLength = 100;    //Step width for iterate
304
305         SearchResult<SearchHit> result;
306         List<SearchHit> hits;
307         do {
308             if(query!=null) {
309                 LOG.debug("read data in {} with query {}",dataTypeName,query.toJSON());
310                 result=db.doReadByQueryJsonData( dataTypeName, query,ignoreException);
311             }
312             else {
313                 result = db.doReadAllJsonData(dataTypeName,ignoreException);
314             }
315             hits=result.getHits();
316             LOG.debug("Read: {} elements: {}  Failures: {}",dataTypeName,hits.size(), yangtoolsMapper.getMappingFailures());
317
318             T object;
319             idx += result.getHits().size();
320             for (SearchHit hit : hits) {
321                 object = getT(hit.getSourceAsString());
322                 LOG.debug("Mapp Object: {}\nSource: '{}'\nResult: '{}'\n Failures: {}", hit.getId(),
323                         hit.getSourceAsString(), object, yangtoolsMapper.getMappingFailures());
324                 if (object != null) {
325                     setEsId(object, hit.getId());
326                     res.add(object);
327                 } else {
328                     LOG.warn("Mapp result null Object: {}\n Source: '{}'\n : '", hit.getId(), hit.getSourceAsString());
329                 }
330             }
331
332         } while (hits.size() == iterateLength); // Do it until end indicated, because less hits than iterateLength
333                                                 // allows.
334         res.setTotal(result.getTotal());
335         return res;
336     }
337
338     /* ---------------------------------------------
339      * Private functions
340      */
341
342     private void setEsId(T object, String esId) {
343         if (field != null) {
344             try {
345                 field.set(object, esId);
346             } catch (IllegalArgumentException | IllegalAccessException e) {
347                 LOG.debug("Field set problem.", e);            }
348         }
349     }
350
351     private @Nullable T getT(String jsonString) {
352         try {
353             return yangtoolsMapper.readValue( jsonString, clazz );
354         } catch (IOException e) {
355             LOG.info("Mapping problem", e);
356             return null;
357         }
358     }
359
360 }