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);