2 * ============LICENSE_START=======================================================
3 * ONAP : ccsdk features
4 * ================================================================================
5 * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property.
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
22 package org.onap.ccsdk.features.sdnr.wt.dataprovider.database;
24 import java.io.IOException;
25 import java.lang.reflect.Field;
26 import java.util.List;
28 import javax.annotation.Nonnull;
29 import javax.annotation.Nullable;
31 import org.eclipse.jdt.annotation.NonNull;
32 import org.onap.ccsdk.features.sdnr.wt.common.database.DatabaseClient;
33 import org.onap.ccsdk.features.sdnr.wt.common.database.SearchHit;
34 import org.onap.ccsdk.features.sdnr.wt.common.database.SearchResult;
35 import org.onap.ccsdk.features.sdnr.wt.common.database.queries.QueryBuilder;
36 import org.onap.ccsdk.features.sdnr.wt.dataprovider.yangtools.YangToolsMapper;
37 import org.onap.ccsdk.features.sdnr.wt.dataprovider.yangtools.YangToolsMapper2;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.data.provider.rev190801.Entity;
39 import org.opendaylight.yangtools.concepts.Builder;
40 import org.opendaylight.yangtools.yang.binding.DataObject;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
44 import com.fasterxml.jackson.core.JsonProcessingException;
47 * Class to rw yang-tool generated objects into elasticsearch database. For "ES _id" exchange the esIdAddAtributteName
48 * is used. This attribute mast be of type String and contains for read and write operations the object id. The function
49 * can be used without id handling. If id handling is required the parameter needs to be specified by class definition
50 * in yang and setting the name by using setAttributeName()
52 * Due to using Jackson base interfaces the org.eclipse.jdt.annotation.NonNull needs to be used here to get rid of
55 * @param <T> Yang tools generated class object.
57 public class EsDataObjectReaderWriter2<T extends DataObject> {
59 private final Logger LOG = LoggerFactory.getLogger(EsDataObjectReaderWriter2.class);
61 /** Typename for elastic search data schema **/
62 private String dataTypeName;
64 /** Elasticsearch Database client to be used **/
65 private DatabaseClient db;
67 /** Mapper with configuration to use opendaylight yang-tools builder pattern for object creation **/
68 private YangToolsMapper2<T> yangtoolsMapper;
70 /** Class of T as attribute to allow JSON to Class object mapping **/
71 private Class<T> clazz;
73 /** Field is used to write id. If null no id handling **/
74 private @Nullable Field field;
76 /** Attribute that is used as id field for the database object **/
77 private @Nullable String esIdAddAtributteName;
79 /** Interface to be used for write operations. Rule for write: T extends S and **/
80 private Class<? extends DataObject> writeInterfaceClazz; // == "S"
83 * Elasticsearch database read and write for specific class, defined by opendaylight yang-tools.
85 * @param db Database access client
86 * @param dataTypeName typename in database schema
87 * @param clazz class of type to be handled
88 * @throws ClassNotFoundException
90 public <X extends T, @NonNull B extends Builder<X>> EsDataObjectReaderWriter2(DatabaseClient db,
91 Entity dataTypeName, @Nonnull Class<T> clazz, @Nullable Class<B> builderClazz)
92 throws ClassNotFoundException {
93 this(db, dataTypeName.getName(), clazz, builderClazz);
96 public <X extends T, @NonNull B extends Builder<X>> EsDataObjectReaderWriter2(DatabaseClient db,
97 Entity dataTypeName, @Nonnull Class<T> clazz) throws ClassNotFoundException {
98 this(db, dataTypeName.getName(), clazz, null);
101 public <X extends T, @NonNull B extends Builder<X>> EsDataObjectReaderWriter2(DatabaseClient db,
102 String dataTypeName, @Nonnull Class<T> clazz, @Nullable Class<B> builderClazz)
103 throws ClassNotFoundException {
104 LOG.info("Create {} for datatype {} class {}", this.getClass().getName(), dataTypeName, clazz.getName());
106 this.esIdAddAtributteName = null;
108 this.writeInterfaceClazz = clazz;
110 this.dataTypeName = dataTypeName;
111 this.yangtoolsMapper = new YangToolsMapper2<>(clazz, builderClazz);
116 * Simlar to {@link #setEsIdAttributeName()}, but adapts the parameter to yangtools attribute naming schema
118 * @param esIdAttributeName is converted to UnderscoreCamelCase
119 * @return this for further operations.
121 public EsDataObjectReaderWriter2<T> setEsIdAttributeNameCamelized(String esIdAttributeName) {
122 return setEsIdAttributeName(YangToolsMapper.toCamelCaseAttributeName(esIdAttributeName));
126 * Attribute name of class that is containing the object id
128 * @param esIdAttributeName of the implementation class for the yangtools interface. Expected attribute name format
129 * is CamelCase with leading underline. @
130 * @return this for further operations.
131 * @throws SecurityException if no access or IllegalArgumentException if wrong type or no attribute with this name.
133 public EsDataObjectReaderWriter2<T> setEsIdAttributeName(String esIdAttributeName) {
134 LOG.debug("Set attribute '{}'", esIdAttributeName);
135 this.esIdAddAtributteName = null; // Reset status
138 Field attributeField;
140 Builder<T> builder = yangtoolsMapper.getBuilder(clazz);
141 if (builder == null) {
142 String msg = "No builder for " + clazz;
144 throw new IllegalArgumentException(msg);
146 T object = builder.build();
147 attributeField = object.getClass().getDeclaredField(esIdAttributeName);
148 if (attributeField.getType().equals(String.class)) {
149 attributeField.setAccessible(true);
150 this.esIdAddAtributteName = esIdAttributeName; // Set new status if everything OK
151 this.field = attributeField;
153 String msg = "Wrong field type " + attributeField.getType().getName() + " of " + esIdAttributeName;
155 throw new IllegalArgumentException(msg);
158 } catch (NoSuchFieldException e) {
159 // Convert to run-time exception
160 String msg = "NoSuchFieldException for '" + esIdAttributeName + "' in class " + clazz.getName();
162 throw new IllegalArgumentException(msg);
163 } catch (SecurityException e) {
164 LOG.debug("Access problem " + esIdAttributeName, e);
171 * Specify subclass of T for write operations.
173 * @param writeInterfaceClazz
175 public EsDataObjectReaderWriter2<T> setWriteInterface(@Nonnull Class<? extends DataObject> writeInterfaceClazz) {
176 LOG.debug("Set write interface to {}", writeInterfaceClazz);
177 if (writeInterfaceClazz == null) {
178 throw new IllegalArgumentException("Null not allowed here.");
181 this.writeInterfaceClazz = writeInterfaceClazz;
185 public interface IdGetter<S extends DataObject> {
186 String getId(S object);
189 public <S extends DataObject> void write(List<S> objectList, IdGetter<S> idGetter) {
190 for (S object : objectList) {
191 write(object, idGetter.getId(object));
196 * Write child object to database with specific id
198 * @param object to be written
199 * @param esId use the id or if null generate unique id
200 * @return String with id or null
202 public @Nullable <S extends DataObject> String write(S object, @Nullable String esId) {
203 if (object != null && writeInterfaceClazz.isInstance(object)) {
205 String json = yangtoolsMapper.writeValueAsString(object);
206 return db.doWriteRaw(dataTypeName, esId, json);
207 } catch (JsonProcessingException e) {
208 LOG.error("Write problem: ", e);
211 LOG.error("Type {} does not provide interface {}", object != null ? object.getClass().getName() : "null",
212 writeInterfaceClazz.getName());
218 * Update partial child object to database with match/term query
220 * @param <S> of object
221 * @param object to write
222 * @param query for write of specific attributes
223 * @return json string with new Object
225 public @Nullable <S extends DataObject> boolean update(S object, QueryBuilder query) {
226 if (object != null && writeInterfaceClazz.isInstance(object)) {
228 String json = yangtoolsMapper.writeValueAsString(object);
229 return db.doUpdate(this.dataTypeName, json, query);
230 } catch (JsonProcessingException e) {
231 LOG.error("Update problem: ", e);
234 LOG.error("Type {} does not provide interface {}", object != null ? object.getClass().getName() : "null",
235 writeInterfaceClazz.getName());
241 * Write/ update partial child object to database with specific id Write if not exists, else update
245 * @return String with esId or null
247 public @Nullable <S extends DataObject> String update(S object, String esId) {
248 return this.updateOrCreate(object, esId, null);
252 * See {@link doUpdateOrCreate(String dataTypeName, String esId, String json, List<String> doNotUpdateField) }
254 public @Nullable <S extends DataObject> String updateOrCreate(S object, String esId, List<String> onlyForInsert) {
255 if (object != null && writeInterfaceClazz.isInstance(object)) {
257 String json = yangtoolsMapper.writeValueAsString(object);
258 return db.doUpdateOrCreate(dataTypeName, esId, json, onlyForInsert);
259 } catch (JsonProcessingException e) {
260 LOG.error("Update problem: ", e);
263 LOG.error("Type {} does not provide interface {}", object != null ? object.getClass().getName() : "null",
264 writeInterfaceClazz.getName());
270 * Read object from database, by using the id field
275 public @Nullable T read(String esId) {
279 String json = db.doReadJsonData(dataTypeName, esId);
282 res = yangtoolsMapper.readValue(json.getBytes(), clazz);
283 } catch (IOException e) {
284 LOG.error("Problem: ", e);
287 LOG.debug("Can not read from DB id {} type {}", esId, dataTypeName);
296 * @param esId to identify the object.
299 public boolean remove(String esId) {
300 return db.doRemove(this.dataTypeName, esId);
303 public int remove(QueryBuilder query) {
304 return this.db.doRemove(this.dataTypeName, query);
308 * Get all elements of related type
310 * @return all Elements
312 public SearchResult<T> doReadAll() {
313 return doReadAll(null);
316 public SearchResult<T> doReadAll(QueryBuilder query) {
317 return this.doReadAll(query, false);
321 * Read all existing objects of a type
323 * @param query for the elements
324 * @return the list of all objects
327 public SearchResult<T> doReadAll(QueryBuilder query, boolean ignoreException) {
329 SearchResult<T> res = new SearchResult<>();
330 int idx = 0; //Idx for getAll
331 int iterateLength = 100; //Step width for iterate
333 SearchResult<SearchHit> result;
334 List<SearchHit> hits;
337 LOG.debug("read data in {} with query {}", dataTypeName, query.toJSON());
338 result = db.doReadByQueryJsonData(dataTypeName, query, ignoreException);
340 result = db.doReadAllJsonData(dataTypeName, ignoreException);
342 hits = result.getHits();
343 LOG.debug("Read: {} elements: {} Failures: {}", dataTypeName, hits.size(),
344 yangtoolsMapper.getMappingFailures());
347 idx += result.getHits().size();
348 for (SearchHit hit : hits) {
349 object = getT(hit.getSourceAsString());
350 LOG.debug("Mapp Object: {}\nSource: '{}'\nResult: '{}'\n Failures: {}", hit.getId(),
351 hit.getSourceAsString(), object, yangtoolsMapper.getMappingFailures());
352 if (object != null) {
353 setEsId(object, hit.getId());
356 LOG.warn("Mapp result null Object: {}\n Source: '{}'\n : '", hit.getId(), hit.getSourceAsString());
360 } while (hits.size() == iterateLength); // Do it until end indicated, because less hits than iterateLength
362 res.setTotal(result.getTotal());
366 /* ---------------------------------------------
370 private void setEsId(T object, String esId) {
373 field.set(object, esId);
374 } catch (IllegalArgumentException | IllegalAccessException e) {
375 LOG.debug("Field set problem.", e);
380 private @Nullable T getT(String jsonString) {
382 return yangtoolsMapper.readValue(jsonString, clazz);
383 } catch (IOException e) {
384 LOG.info("Mapping problem", e);