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.elasticsearch;
24 import com.fasterxml.jackson.core.JsonProcessingException;
25 import java.io.IOException;
26 import java.lang.reflect.Field;
27 import java.lang.reflect.InvocationTargetException;
28 import java.util.List;
29 import javax.annotation.Nonnull;
30 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.yang.mapper.YangToolsMapper2;
37 import org.onap.ccsdk.features.sdnr.wt.yang.mapper.YangToolsMapperHelper;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.data.provider.rev201110.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;
45 * Class to rw yang-tool generated objects into elasticsearch database. For "ES _id" exchange the esIdAddAtributteName
46 * is used. This attribute mast be of type String and contains for read and write operations the object id. The function
47 * can be used without id handling. If id handling is required the parameter needs to be specified by class definition
48 * in yang and setting the name by using setAttributeName()
50 * Due to using Jackson base interfaces the org.eclipse.jdt.annotation.NonNull needs to be used here to get rid of
53 * @param <T> Yang tools generated class object.
55 public class EsDataObjectReaderWriter2<T extends DataObject> {
57 private static final Logger LOG = LoggerFactory.getLogger(EsDataObjectReaderWriter2.class);
59 /** Typename for elastic search data schema **/
60 private String dataTypeName;
62 /** Elasticsearch Database client to be used **/
63 private DatabaseClient db;
65 /** Mapper with configuration to use opendaylight yang-tools builder pattern for object creation **/
66 private YangToolsMapper2<T> yangtoolsMapper;
68 /** Class of T as attribute to allow JSON to Class object mapping **/
69 private Class<T> clazz;
71 /** Field is used to write id. If null no id handling **/
72 private @Nullable Field field;
74 /** Attribute that is used as id field for the database object **/
75 private @Nullable String esIdAddAtributteName;
77 /** Interface to be used for write operations. Rule for write: T extends S and **/
78 private Class<? extends DataObject> writeInterfaceClazz; // == "S"
79 /** Flag true to sync this attribute during write always, what is slow and false do not sync */
80 private final boolean syncAfterWrite;
82 protected boolean doFullsizeRequest;
85 * Elasticsearch database read and write for specific class, defined by opendaylight yang-tools.
89 * @param db Database access client
90 * @param dataTypeName typename in database schema
91 * @param clazz class of type to be handled
92 * @param builderClazz class to build related object if builder pattern should be used.
93 * @param syncAfterWrite
94 * @throws ClassNotFoundException
96 public <X extends T, B> EsDataObjectReaderWriter2(DatabaseClient db,
97 String dataTypeName, @Nonnull Class<T> clazz, @Nullable Class<B> builderClazz, boolean syncAfterWrite)
98 throws ClassNotFoundException {
99 LOG.info("Create {} for datatype {} class {}", this.getClass().getName(), dataTypeName, clazz.getName());
101 this.esIdAddAtributteName = null;
103 this.writeInterfaceClazz = clazz;
105 this.dataTypeName = dataTypeName;
106 this.yangtoolsMapper = new YangToolsMapper2<>(clazz, builderClazz);
108 this.syncAfterWrite = syncAfterWrite;
109 this.doFullsizeRequest = false;
112 public void setFullsizeRequest(boolean fullsizeRequest) {
113 this.doFullsizeRequest = fullsizeRequest;
115 public <X extends T, B> EsDataObjectReaderWriter2(DatabaseClient db,
116 Entity dataTypeName, @Nonnull Class<T> clazz, @Nullable Class<B> builderClazz)
117 throws ClassNotFoundException {
118 this(db, dataTypeName.getName(), clazz, builderClazz, false);
121 public <X extends T, B> EsDataObjectReaderWriter2(DatabaseClient db,
122 Entity dataTypeName, @Nonnull Class<T> clazz, @Nullable Class<B> builderClazz, boolean syncAfterWrite)
123 throws ClassNotFoundException {
124 this(db, dataTypeName.getName(), clazz, builderClazz, syncAfterWrite);
127 public EsDataObjectReaderWriter2(DatabaseClient db, Entity dataTypeName, @Nonnull Class<T> clazz,
128 boolean syncAfterWrite) throws ClassNotFoundException {
129 this(db, dataTypeName.getName(), clazz, null, syncAfterWrite);
132 public EsDataObjectReaderWriter2(DatabaseClient db, Entity dataTypeName, Class<T> clazz)
133 throws ClassNotFoundException {
134 this(db, dataTypeName.getName(), clazz, null, false);
140 * @return string with dataTypeName
142 public String getDataTypeName() {
147 * Simlar to {@link #setEsIdAttributeName()}, but adapts the parameter to yangtools attribute naming schema
149 * @param esIdAttributeName is converted to UnderscoreCamelCase
150 * @return this for further operations.
152 public EsDataObjectReaderWriter2<T> setEsIdAttributeNameCamelized(String esIdAttributeName) {
153 return setEsIdAttributeName(YangToolsMapperHelper.toCamelCaseAttributeName(esIdAttributeName));
157 * Attribute name of class that is containing the object id
159 * @param esIdAttributeName of the implementation class for the yangtools interface. Expected attribute name format
160 * is CamelCase with leading underline. @
161 * @return this for further operations.
162 * @throws SecurityException if no access or IllegalArgumentException if wrong type or no attribute with this name.
164 public <B> EsDataObjectReaderWriter2<T> setEsIdAttributeName(String esIdAttributeName) {
165 LOG.debug("Set attribute '{}'", esIdAttributeName);
166 this.esIdAddAtributteName = null; // Reset status
169 Field attributeField;
171 B builder = yangtoolsMapper.getBuilder(clazz);
172 if (builder == null) {
173 String msg = "No builder for " + clazz;
175 throw new IllegalArgumentException(msg);
177 T object = YangToolsMapperHelper.callBuild(builder);
178 attributeField = object.getClass().getDeclaredField(esIdAttributeName);
179 if (attributeField.getType().equals(String.class)) {
180 attributeField.setAccessible(true);
181 this.esIdAddAtributteName = esIdAttributeName; // Set new status if everything OK
182 this.field = attributeField;
184 String msg = "Wrong field type " + attributeField.getType().getName() + " of " + esIdAttributeName;
186 throw new IllegalArgumentException(msg);
189 } catch (NoSuchFieldException e) {
190 // Convert to run-time exception
191 String msg = "NoSuchFieldException for '" + esIdAttributeName + "' in class " + clazz.getName();
193 throw new IllegalArgumentException(msg);
194 } catch (SecurityException e) {
195 LOG.debug("Access problem " + esIdAttributeName, e);
197 } catch (NoSuchMethodException e) {
198 // TODO Auto-generated catch block
200 } catch (IllegalAccessException e) {
201 // TODO Auto-generated catch block
203 } catch (IllegalArgumentException e) {
204 // TODO Auto-generated catch block
206 } catch (InvocationTargetException e) {
207 // TODO Auto-generated catch block
214 * Specify subclass of T for write operations.
216 * @param writeInterfaceClazz
218 public EsDataObjectReaderWriter2<T> setWriteInterface(Class<? extends DataObject> writeInterfaceClazz) {
219 LOG.debug("Set write interface to {}", writeInterfaceClazz);
220 if (writeInterfaceClazz == null) {
221 throw new IllegalArgumentException("Null not allowed here.");
224 this.writeInterfaceClazz = writeInterfaceClazz;
228 public interface IdGetter<S extends DataObject> {
229 String getId(S object);
232 public <S extends DataObject> void write(List<S> objectList, IdGetter<S> idGetter) {
233 for (S object : objectList) {
234 write(object, idGetter.getId(object));
239 * Write child object to database with specific id
241 * @param object to be written
242 * @param esId use the id or if null generate unique id
243 * @return String with id or null
245 public @Nullable <S extends DataObject> String write(S object, @Nullable String esId) {
246 if (object != null && writeInterfaceClazz.isInstance(object)) {
248 String json = yangtoolsMapper.writeValueAsString(object);
249 return db.doWriteRaw(dataTypeName, esId, json, this.syncAfterWrite);
250 } catch (JsonProcessingException e) {
251 LOG.error("Write problem: ", e);
254 LOG.error("Type {} does not provide interface {}", object != null ? object.getClass().getName() : "null",
255 writeInterfaceClazz.getName());
261 * Update partial child object to database with match/term query
263 * @param <S> of object
264 * @param object to write
265 * @param query for write of specific attributes
266 * @return json string with new Object
268 public @Nullable <S extends DataObject> boolean update(S object, QueryBuilder query) {
269 if (object != null && writeInterfaceClazz.isInstance(object)) {
271 String json = yangtoolsMapper.writeValueAsString(object);
272 return db.doUpdate(this.dataTypeName, json, query);
273 } catch (JsonProcessingException e) {
274 LOG.error("Update problem: ", e);
277 LOG.error("Type {} does not provide interface {}", object != null ? object.getClass().getName() : "null",
278 writeInterfaceClazz.getName());
284 * Write/ update partial child object to database with specific id Write if not exists, else update
288 * @return String with esId or null
290 public @Nullable <S extends DataObject> String update(S object, String esId) {
291 return this.updateOrCreate(object, esId, null);
295 * See {@link doUpdateOrCreate(String dataTypeName, String esId, String json, List<String> doNotUpdateField) }
297 public @Nullable <S extends DataObject> String updateOrCreate(S object, String esId, List<String> onlyForInsert) {
298 if (object != null && writeInterfaceClazz.isInstance(object)) {
300 String json = yangtoolsMapper.writeValueAsString(object);
301 return db.doUpdateOrCreate(dataTypeName, esId, json, onlyForInsert);
302 } catch (JsonProcessingException e) {
303 LOG.error("Update problem: ", e);
306 LOG.error("Type {} does not provide interface {}", object != null ? object.getClass().getName() : "null",
307 writeInterfaceClazz.getName());
313 * Read object from database, by using the id field
318 public @Nullable T read(String esId) {
322 String json = db.doReadJsonData(dataTypeName, esId);
325 res = yangtoolsMapper.readValue(json.getBytes(), clazz);
326 } catch (IOException e) {
327 LOG.error("Problem: ", e);
330 LOG.debug("Can not read from DB id {} type {}", esId, dataTypeName);
339 * @param esId to identify the object.
342 public boolean remove(String esId) {
343 return db.doRemove(this.dataTypeName, esId);
346 public int remove(QueryBuilder query) {
347 return this.db.doRemove(this.dataTypeName, query);
351 * Get all elements of related type
353 * @return all Elements
355 public SearchResult<T> doReadAll() {
356 return doReadAll(null);
359 public SearchResult<T> doReadAll(QueryBuilder query) {
360 return this.doReadAll(query, false);
364 * Read all existing objects of a type
366 * @param query for the elements
367 * @return the list of all objects
370 public SearchResult<T> doReadAll(QueryBuilder query, boolean ignoreException) {
372 if(this.doFullsizeRequest) {
373 query.doFullsizeRequest();
375 SearchResult<T> res = new SearchResult<>();
376 SearchResult<SearchHit> result;
377 List<SearchHit> hits;
379 LOG.debug("read data in {} with query {}", dataTypeName, query.toJSON());
380 result = db.doReadByQueryJsonData(dataTypeName, query, ignoreException);
382 result = db.doReadAllJsonData(dataTypeName, ignoreException);
384 hits = result.getHits();
385 LOG.debug("Read: {} elements: {}", dataTypeName, hits.size());
388 for (SearchHit hit : hits) {
389 object = getT(hit.getSourceAsString());
390 LOG.debug("Mapp Object: {}\nSource: '{}'\nResult: '{}'\n", hit.getId(),
391 hit.getSourceAsString(), object);
392 if (object != null) {
393 setEsId(object, hit.getId());
396 LOG.warn("Mapp result null Object: {}\n Source: '{}'\n : '", hit.getId(), hit.getSourceAsString());
399 res.setTotal(result.getTotal());
403 /* ---------------------------------------------
407 private void setEsId(T object, String esId) {
410 field.set(object, esId);
411 } catch (IllegalArgumentException | IllegalAccessException e) {
412 LOG.debug("Field set problem.", e);
417 private @Nullable T getT(String jsonString) {
419 return yangtoolsMapper.readValue(jsonString, clazz);
420 } catch (IOException e) {
421 LOG.info("Mapping problem {}:", clazz.getName(), e);