diff options
Diffstat (limited to 'sdnr/wt/data-provider/database')
16 files changed, 2418 insertions, 57 deletions
diff --git a/sdnr/wt/data-provider/database/pom.xml b/sdnr/wt/data-provider/database/pom.xml new file mode 100644 index 000000000..444cd59bf --- /dev/null +++ b/sdnr/wt/data-provider/database/pom.xml @@ -0,0 +1,169 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + * ============LICENSE_START======================================================= + * ONAP : CCSDK.sdnr.wt.data-provider.model + * ================================================================================ + * Copyright (C) 2018 highstreet technologies GmbH Intellectual Property. + * All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.onap.ccsdk.features.sdnr.wt</groupId> + <artifactId>sdnr-wt-data-provider-database</artifactId> + <version>0.7.0-SNAPSHOT</version> + <name>ccsdk-features-sdnr-wt :: ${project.artifactId}</name> + <packaging>bundle</packaging> + + <parent> + <groupId>org.onap.ccsdk.parent</groupId> + <artifactId>binding-parent</artifactId> + <version>1.5.1-SNAPSHOT</version> + <relativePath /> + </parent> + + <properties> + <maven.javadoc.skip>true</maven.javadoc.skip> + <databaseport>49401</databaseport> + </properties> + + <licenses> + <license> + <name>Apache License, Version 2.0</name> + <url>http://www.apache.org/licenses/LICENSE-2.0</url> + </license> + </licenses> + + <dependencies> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.opendaylight.netconf</groupId> + <artifactId>sal-netconf-connector</artifactId> + <scope>provided</scope> + </dependency> +<!-- <dependency> --> +<!-- <groupId>org.json</groupId> --> +<!-- <artifactId>json</artifactId> --> +<!-- <scope>provided</scope> --> +<!-- </dependency> --> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.core</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>sdnr-wt-common</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>sdnr-wt-data-provider-model</artifactId> + <version>${project.version}</version> + </dependency> +<!-- <dependency> --> +<!-- <groupId>org.elasticsearch.client</groupId> --> +<!-- <artifactId>elasticsearch-rest-client</artifactId> --> +<!-- <version>6.4.3</version> --> +<!-- </dependency> --> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <configuration> + <excludes> + <exclude>**/gen/**</exclude> + <exclude>**/generated-sources/**</exclude> + <exclude>**/yang-gen-sal/**</exclude> + <exclude>**/pax/**</exclude> + </excludes> + </configuration> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>exec-maven-plugin</artifactId> + <executions> + <execution> + <id>generateDTOs</id> + <phase>generate-sources</phase> + <goals> + <goal>exec</goal> + </goals> + <configuration> + <skip>${skipTests}</skip> + <executable>${basedir}/src/main/resources/es-init.sh</executable> + <arguments> + <argument>initfile</argument> + <argument>-f</argument> + <argument>${project.build.directory}/EsInit.script</argument> + </arguments> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>com.github.alexcojocaru</groupId> + <artifactId>elasticsearch-maven-plugin</artifactId> + <version>6.16</version> + <configuration> + <skip>true</skip> + <clusterName>testCluster</clusterName> + <transportPort>9500</transportPort> + <httpPort>${databaseport}</httpPort> + <version>6.4.3</version> + <pathInitScript>${project.build.directory}/EsInit.script</pathInitScript> + </configuration> + <executions> + <execution> + <id>start-elasticsearch</id> + <phase>process-test-classes</phase> + <goals> + <goal>runforked</goal> + </goals> + </execution> + <execution> + <id>stop-elasticsearch</id> + <phase>prepare-package</phase> + <goals> + <goal>stop</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <systemProperties> + <property> + <name>databaseport</name> + <value>${databaseport}</value> + </property> + </systemProperties> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/base/netconf/util/InternalConnectionStatus.java b/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/base/netconf/util/InternalConnectionStatus.java new file mode 100644 index 000000000..81876b75b --- /dev/null +++ b/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/base/netconf/util/InternalConnectionStatus.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + ******************************************************************************/ + +package org.onap.ccsdk.features.sdnr.wt.base.netconf.util; + +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus.ConnectionStatus; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.data.provider.rev190801.ConnectionLogStatus; + +public class InternalConnectionStatus { + public static ConnectionLogStatus statusFromNodeStatus(ConnectionStatus nodeStatus) { + switch(nodeStatus) { + case Connected: + return ConnectionLogStatus.Connected; + case Connecting: + return ConnectionLogStatus.Connecting; + case UnableToConnect: + return ConnectionLogStatus.UnableToConnect; + default: + return ConnectionLogStatus.Undefined; + } + } +} diff --git a/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/base/netconf/util/NetconfTimeStamp.java b/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/base/netconf/util/NetconfTimeStamp.java new file mode 100644 index 000000000..4857da661 --- /dev/null +++ b/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/base/netconf/util/NetconfTimeStamp.java @@ -0,0 +1,206 @@ +/******************************************************************************* + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + ******************************************************************************/ +package org.onap.ccsdk.features.sdnr.wt.base.netconf.util; + +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Date; + +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.DateAndTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 2019/06/17 Redesign to ZonedDateTime because of sync problems. + * + * Function is handling the NETCONF and the format used by database and restconf communication. + * + * Input supported for the formats used in NETCONF messages: + * + * Format1 ISO 8601 2017-01-18T11:44:49.482-05:00 + * + * Format2 NETCONF - pattern from ietf-yang-types "2013-07-15" Pattern: + * "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[\+\-](\d{2}):(\d{2}))" + * + * Format3 NETCONF DateAndTime CoreModel-CoreFoundationModule-TypeDefinitions vom + * 2016-07-01 Example1: 20170118114449.1Z Example2: 20170118114449.1-0500 Pattern: + * "\d{4}\d{2}\d{2}\d{2}\d{2}\d{2}.\d+?(Z|[\+\-](\d{2})(\d{2}))" typedef DateAndTime { description + * "This primitive type defines the date and time according to the following structure: + * 'yyyyMMddhhmmss.s[Z|{+|-}HHMm]' where: yyyy '0000'..'9999' year MM '01'..'12' month dd '01'..'31' + * day hh '00'..'23' hour mm '00'..'59' minute ss '00'..'59' second s '.0'..'.9' tenth of second + * (set to '.0' if EMS or NE cannot support this granularity) Z 'Z' indicates UTC (rather than local + * time) {+|-} '+' or '-' delta from UTC HH '00'..'23' time zone difference in hours Mm '00'..'59' + * time zone difference in minutes."; type string; } Format4 E/// specific Example1: + * 2017-01-23T13:32:38-05:00 Example2: 2017-01-23T13:32-05:00 + * + * Input formats netconfTime as String according the formats given above + * + * Return format is String in ISO8601 Format for database and presentation. + * + * Example formats: + * 1) ISO8601. Example 2017-01-18T11:44:49.482-05:00 + * 2) Microwave ONF. Examples 20170118114449.1Z, 20170118114449.1-0500 + * 3.1) Ericson. Example: 2017-01-23T13:32:38-05:00 + * 3.2) Ericson. Example: 2017-01-23T13:32-05:00 + * Always 10 Groups, + * 1:Year 2:Month 3:day 4:Hour 5:minute 6:optional sec 7:optional ms 8:optional Z or 9:offset + * signedhour 10:min + * + * Template: + * private static final NetconfTimeStamp NETCONFTIME_CONVERTER = NetconfTimeStamp.getConverter(); + */ + +public class NetconfTimeStamp { + private static final Logger LOG = LoggerFactory.getLogger(NetconfTimeStamp.class); + + private static final NetconfTimeStamp CONVERTER = new NetconfTimeStamp(); + + /** + * Specify the input format expected from netconf, and from specific devices. + */ + private static DateTimeFormatter formatterInput = DateTimeFormatter.ofPattern("" + + "[yyyy-MM-dd'T'HH:mm[:ss][.SSS][.SS][.S][xxx][xx][X][Z]]" + + "[yyyyMMddHHmmss[.SSS][.SS][.S][xxx][xx][X][Z]]" + ).withZone(ZoneOffset.UTC); + + /** + * Specify output format that is used internally + */ + private static DateTimeFormatter formatterOutput = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.S'Z'") + .withZone(ZoneOffset.UTC); + + /** + * Use static access + */ + private NetconfTimeStamp() { + } + + /* + * ------------------------------------ Public function + */ + + /** + * Use this function to get the converter + * @return global converter + */ + public static NetconfTimeStamp getConverter() { + return CONVERTER; + } + + /** + * Get actual timestamp as NETCONF specific type NETCONF/YANG 1.0 Format + * + * @return String with Date in NETCONF/YANG Format Version 1.0. + */ + public String getTimeStampAsNetconfString() { + return ZonedDateTime.now(ZoneOffset.UTC).format(formatterOutput); + } + + /** + * Get actual timestamp as NETCONF specific type NETCONF/YANG 1.0 Format + + * @return String with Date in NETCONF/YANG Format Version 1.0. + */ + public String getTimeStampAsNetconfString(Date date) { + return ZonedDateTime.ofInstant(date.toInstant(),ZoneOffset.UTC).format(formatterOutput); + } + + + + /** + * Get actual timestamp as NETCONF specific type NETCONF/YANG 1.0 Format in GMT + * + * @return DateAndTime Type 1.0. Date in NETCONF/YANG Format Version 1.0. + */ + public DateAndTime getTimeStamp() { + return DateAndTime.getDefaultInstance(getTimeStampAsNetconfString()); + } + + /** + * Get time from date as NETCONF specific type NETCONF/YANG 1.0 Format in GMT + * @param date specifying the date and time + * @return DateAndTime Type 1.0. Date in NETCONF/YANG Format Version 1.0. + */ + public DateAndTime getTimeStamp(Date date) { + return DateAndTime.getDefaultInstance(getTimeStampAsNetconfString(date)); + } + /** + * Get time from date as NETCONF specific type NETCONF/YANG 1.0 Format in GMT + * @param date specifying the date and time + * @return DateAndTime Type 1.0. Date in NETCONF/YANG Format Version 1.0. + */ + public DateAndTime getTimeStamp(String date) { + return DateAndTime.getDefaultInstance(date); + } + + /** + * Return the String with a NETCONF time converted to long + * + * @param netconfTime as String according the formats given above + * @return Epoch milliseconds + * @throws IllegalArgumentException In case of no compliant time format definition for the string + */ + public long getTimeStampFromNetconfAsMilliseconds(String netconfTime) throws IllegalArgumentException { + try { + long utcMillis = doParse(netconfTime).toInstant().toEpochMilli(); + return utcMillis; + } catch (DateTimeParseException e) { + throw new IllegalArgumentException( + "No pattern for NETCONF data string: " + netconfTime + " Msg:" + e.getMessage()); + } + } + + /** + * Deliver String result. + * + * @param netconfTime as String according the formats given above + * @return If successful: String in ISO8601 Format for database and presentation. If "wrong formed + * input" the Input string with the prefix "Maleformed date" is delivered back. + */ + public String getTimeStampFromNetconf(String netconfTime) { + try { + String inputUTC = doParse(netconfTime).format(formatterOutput); + return inputUTC; + } catch (Exception e) { + LOG.info(e.getMessage()); + } + LOG.debug("No pattern for NETCONF data string: {}", netconfTime); + return "Malformed date: " + netconfTime; // Error handling + } + + /*---------------------------------------------------- + * Private functions + */ + + private OffsetDateTime doParse(String netconfTime) { + return OffsetDateTime.parse(netconfTime, formatterInput); + } + + public Date getDateFromNetconf(String netconfTime) { + return Date.from(LocalDateTime.parse(netconfTime, formatterInput).atZone(ZoneOffset.UTC).toInstant()); + } + + public String getTimeStampAsNetconfString(LocalDateTime dt) { + return formatterOutput.format(dt); + } + +} diff --git a/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/database/EsDataObjectReaderWriter.java b/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/database/EsDataObjectReaderWriter.java new file mode 100644 index 000000000..ac676024f --- /dev/null +++ b/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/database/EsDataObjectReaderWriter.java @@ -0,0 +1,339 @@ +/******************************************************************************* + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + ******************************************************************************/ +package org.onap.ccsdk.features.sdnr.wt.database; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.List; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.onap.ccsdk.features.sdnr.wt.common.database.DatabaseClient; +import org.onap.ccsdk.features.sdnr.wt.common.database.SearchHit; +import org.onap.ccsdk.features.sdnr.wt.common.database.SearchResult; +import org.onap.ccsdk.features.sdnr.wt.common.database.queries.QueryBuilder; +import org.onap.ccsdk.features.sdnr.wt.yangtools.YangToolsMapper; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.data.provider.rev190801.Entity; +import org.opendaylight.yangtools.concepts.Builder; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; + +/** + * Class to rw yang-tool generated objects into elasticsearch database. For "ES _id" exchange the esIdAddAtributteName is used. + * This attribute mast be of type String and contains for read and write operations the object id. + * The function can be used without id handling. + * If id handling is required the parameter needs to be specified by class definition in yang and setting the name by using setAttributeName() + * + * @param <T> Yang tools generated class object. + */ +public class EsDataObjectReaderWriter<T extends DataObject> { + + private final Logger LOG = LoggerFactory.getLogger(EsDataObjectReaderWriter.class); + + /** Typename for elastic search data schema **/ + private String dataTypeName; + + /** Elasticsearch Database client to be used **/ + private DatabaseClient db; + + /** Mapper with configuration to use opendaylight yang-tools builder pattern for object creation **/ + private YangToolsMapper yangtoolsMapper; + + /** Class of T as attribute to allow JSON to Class object mapping **/ + private Class<T> clazz; + + /** Field is used to write id. If null no id handling **/ + private @Nullable Field field; + + /** Attribute that is used as id field for the database object **/ + private @Nullable String esIdAddAtributteName; + + /** Interface to be used for write operations. Rule for write: T extends S and **/ + private Class<? extends DataObject> writeInterfaceClazz; // == "S" + + /** + * Elasticsearch database read and write for specific class, defined by opendaylight yang-tools. + * + * @param db Database access client + * @param dataTypeName typename in database schema + * @param clazz class of type to be handled + * @throws ClassNotFoundException + */ + public EsDataObjectReaderWriter(DatabaseClient db, Entity dataTypeName, Class<T> clazz) throws ClassNotFoundException { + this(db, dataTypeName.getName(), clazz); + } + public EsDataObjectReaderWriter(DatabaseClient db, String dataTypeName, Class<T> clazz) throws ClassNotFoundException { + LOG.info("Create {} for datatype {} class {}", this.getClass().getName(), dataTypeName, clazz.getName()); + + this.esIdAddAtributteName = null; + this.field = null; + this.writeInterfaceClazz = clazz; + this.db = db; + this.dataTypeName = dataTypeName; + this.yangtoolsMapper = new YangToolsMapper(); + //this.yangtoolsMapper.assertBuilderClass(clazz); + this.clazz = clazz; +// +// if (! db.isExistsIndex(dataTypeName)) { +// throw new IllegalArgumentException("Index "+dataTypeName+" not existing."); +// } + } + + public String getDataTypeName() { + return dataTypeName; + } + public Class<T> getClazz() { + return clazz; + } + /** + * Simlar to {@link #setEsIdAttributeName()}, but adapts the parameter to yangtools attribute naming schema + * @param esIdAttributeName is converted to UnderscoreCamelCase + * @return this for further operations. + */ + public EsDataObjectReaderWriter<T> setEsIdAttributeNameCamelized(String esIdAttributeName) { + return setEsIdAttributeName(YangToolsMapper.toCamelCaseAttributeName(esIdAttributeName)); + } + + /** + * Attribute name of class that is containing the object id + * @param esIdAttributeName of the implementation class for the yangtools interface. + * Expected attribute name format is CamelCase with leading underline. @ + * @return this for further operations. + * @throws SecurityException if no access or IllegalArgumentException if wrong type or no attribute with this name. + */ + public EsDataObjectReaderWriter<T> setEsIdAttributeName(String esIdAttributeName) { + LOG.debug("Set attribute '{}'", esIdAttributeName); + this.esIdAddAtributteName = null; // Reset status + this.field = null; + + Field attributeField; + try { + Builder<T> builder = yangtoolsMapper.getBuilder(clazz); + T object = builder.build(); + attributeField = object.getClass().getDeclaredField(esIdAttributeName); + if (attributeField.getType().equals(String.class)) { + attributeField.setAccessible(true); + this.esIdAddAtributteName = esIdAttributeName; //Set new status if everything OK + this.field = attributeField; + } else { + String msg = "Wrong field type " + attributeField.getType().getName() + " of " + esIdAttributeName; + LOG.debug(msg); + throw new IllegalArgumentException(msg); + } + } catch (NoSuchFieldException e) { + // Convert to run-time exception + String msg = "NoSuchFieldException for '" + esIdAttributeName + "' in class " + clazz.getName(); + LOG.debug(msg); + throw new IllegalArgumentException(msg); + } catch (SecurityException e) { + LOG.debug("Access problem "+esIdAttributeName,e); + throw e; + } + return this; + } + + /** + * Specify subclass of T for write operations. + * @param writeInterfaceClazz + */ + public EsDataObjectReaderWriter<T> setWriteInterface( @Nonnull Class<? extends DataObject> writeInterfaceClazz ) { + LOG.debug("Set write interface to {}", writeInterfaceClazz); + if (writeInterfaceClazz == null) + throw new IllegalArgumentException("Null not allowed here."); + + this.writeInterfaceClazz = writeInterfaceClazz; + return this; + } + + /** + * Write child object to database with specific id + * @param object + * @param @Nullable esId use the id or if null generate unique id + * @return String with id or null + */ + public @Nullable <S extends DataObject> String write(S object, @Nullable String esId) { + if (writeInterfaceClazz.isInstance(object)) { + try { + String json = yangtoolsMapper.writeValueAsString(object); + return db.doWriteRaw(dataTypeName, esId, json); + } catch (JsonProcessingException e) { + LOG.error("Write problem: ", e); + } + } else { + LOG.error("Type {} does not provide interface {}", object!=null?object.getClass().getName():"null", + writeInterfaceClazz.getName()); + } + return null; + } + /** + * Update partial child object to database with match/term query + * @param object + * @param esId + * @return String with esId or null + */ + public @Nullable <S extends DataObject> String update(S object, QueryBuilder query) { + if (writeInterfaceClazz.isInstance(object)) { + try { + String json = yangtoolsMapper.writeValueAsString(object); + return db.doUpdate(this.dataTypeName,json,query); + } catch (JsonProcessingException e) { + LOG.error("Update problem: ", e); + } + } else { + LOG.error("Type {} does not provide interface {}", object!=null?object.getClass().getName():"null", + writeInterfaceClazz.getName()); + } + return null; + } + /** + * Write/ update partial child object to database with specific id Write if not + * exists, else update + * @param object + * @param esId + * @return String with esId or null + */ + public @Nullable <S extends DataObject> String update(S object, String esId) { + return this.update(object, esId,null); + } + public @Nullable <S extends DataObject> String update(S object, String esId,List<String> onylForInsert) { + if (writeInterfaceClazz.isInstance(object)) { + try { + String json = yangtoolsMapper.writeValueAsString(object); + return db.doUpdateOrCreate(dataTypeName, esId, json,onylForInsert); + } catch (JsonProcessingException e) { + LOG.error("Update problem: ", e); + } + } else { + LOG.error("Type {} does not provide interface {}", object!=null?object.getClass().getName():"null", + writeInterfaceClazz.getName()); + } + return null; + } + + /** + * Read object from database, by using the id field + * @param object + * @return + */ + public @Nullable T read(String esId) { + @Nullable T res = (T)null; + if (esId != null) { + String json = db.doReadJsonData(dataTypeName, esId); + try { + res = yangtoolsMapper.readValue(json.getBytes(), clazz); + } catch (IOException e) { + LOG.error("Problem: ", e); + } + } + return res; + } + + /** + * Remove object + * @param esId to identify the object. + * @return success + */ + public boolean remove(String esId) { + return db.doRemove(this.dataTypeName, esId); + } + + public int remove(QueryBuilder query) { + return this.db.doRemove(this.dataTypeName, query); + } + /** + * Get all elements of related type + * @return all Elements + */ + public SearchResult<T> doReadAll() { + return doReadAll(null); + } + public SearchResult<T> doReadAll(QueryBuilder query) { + return this.doReadAll(query,false); + } + /** + * Read all existing objects of a type + * @param query for the elements + * @return the list of all objects + */ + + public SearchResult<T> doReadAll(QueryBuilder query, boolean ignoreException) { + + SearchResult<T> res = new SearchResult<T>(); + int idx = 0; //Idx for getAll + int iterateLength = 100; //Step width for iterate + + SearchResult<SearchHit> result; + List<SearchHit> hits; + do { + if(query!=null) { + LOG.debug("read data in {} with query {}",dataTypeName,query.toJSON()); + result=db.doReadByQueryJsonData( dataTypeName, query,ignoreException); + } + else { + result = db.doReadAllJsonData(dataTypeName,ignoreException); + } + hits=result.getHits(); + LOG.debug("Read: {} elements: {} Failures: {}",dataTypeName,hits.size(), yangtoolsMapper.getMappingFailures()); + + T object; + idx += result.getHits().size(); + for (SearchHit hit : hits) { + object = getT(hit.getSourceAsString()); + LOG.debug("Mapp Object: {}\nSource: '{}'\nResult: '{}'\n Failures: {}", hit.getId(), + hit.getSourceAsString(), object, yangtoolsMapper.getMappingFailures()); + if (object != null) { + setEsId(object, hit.getId()); + res.add(object); + } else { + LOG.warn("Mapp result null Object: {}\n Source: '{}'\n : '", hit.getId(), hit.getSourceAsString()); + } + } + + } while (hits.size() == iterateLength); // Do it until end indicated, because less hits than iterateLength + // allows. + res.setTotal(result.getTotal()); + return res; + } + + /* --------------------------------------------- + * Private functions + */ + + private void setEsId(T object, String esId) { + if (field != null) { + try { + field.set(object, esId); + } catch (IllegalArgumentException | IllegalAccessException e) { + LOG.debug("Field set problem.", e); } + } + } + + private @Nullable T getT(String jsonString) { + try { + return yangtoolsMapper.readValue( jsonString, clazz ); + } catch (IOException e) { + LOG.info("Mapping problem", e); + return (T)null; + } + } + +} diff --git a/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/database/EsDataObjectReaderWriter2.java b/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/database/EsDataObjectReaderWriter2.java new file mode 100644 index 000000000..b585b0c60 --- /dev/null +++ b/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/database/EsDataObjectReaderWriter2.java @@ -0,0 +1,360 @@ +/******************************************************************************* + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + ******************************************************************************/ +package org.onap.ccsdk.features.sdnr.wt.database; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.List; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.eclipse.jdt.annotation.NonNull; +import org.onap.ccsdk.features.sdnr.wt.common.database.DatabaseClient; +import org.onap.ccsdk.features.sdnr.wt.common.database.SearchHit; +import org.onap.ccsdk.features.sdnr.wt.common.database.SearchResult; +import org.onap.ccsdk.features.sdnr.wt.common.database.queries.QueryBuilder; +import org.onap.ccsdk.features.sdnr.wt.yangtools.YangToolsMapper; +import org.onap.ccsdk.features.sdnr.wt.yangtools.YangToolsMapper2; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.data.provider.rev190801.Entity; +import org.opendaylight.yangtools.concepts.Builder; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; + +/** + * Class to rw yang-tool generated objects into elasticsearch database. For "ES _id" exchange the esIdAddAtributteName is used. + * This attribute mast be of type String and contains for read and write operations the object id. + * The function can be used without id handling. + * If id handling is required the parameter needs to be specified by class definition in yang and setting the name by using setAttributeName() + * + * Due to using Jackson base interfaces the org.eclipse.jdt.annotation.NonNull needs to be used here to get rid of warnings + * + * @param <T> Yang tools generated class object. + */ +public class EsDataObjectReaderWriter2<T extends DataObject> { + + private final Logger LOG = LoggerFactory.getLogger(EsDataObjectReaderWriter2.class); + + /** Typename for elastic search data schema **/ + private String dataTypeName; + + /** Elasticsearch Database client to be used **/ + private DatabaseClient db; + + /** Mapper with configuration to use opendaylight yang-tools builder pattern for object creation **/ + private YangToolsMapper2<T> yangtoolsMapper; + + /** Class of T as attribute to allow JSON to Class object mapping **/ + private Class<T> clazz; + + /** Field is used to write id. If null no id handling **/ + private @Nullable Field field; + + /** Attribute that is used as id field for the database object **/ + private @Nullable String esIdAddAtributteName; + + /** Interface to be used for write operations. Rule for write: T extends S and **/ + private Class<? extends DataObject> writeInterfaceClazz; // == "S" + + /** + * Elasticsearch database read and write for specific class, defined by opendaylight yang-tools. + * + * @param db Database access client + * @param dataTypeName typename in database schema + * @param clazz class of type to be handled + * @throws ClassNotFoundException + */ + public <X extends T, @NonNull B extends Builder<X>> EsDataObjectReaderWriter2(DatabaseClient db, Entity dataTypeName, @Nonnull Class<T> clazz, @Nullable Class<B> builderClazz) throws ClassNotFoundException { + this(db, dataTypeName.getName(), clazz, builderClazz); + } + public <X extends T, @NonNull B extends Builder<X>> EsDataObjectReaderWriter2(DatabaseClient db, Entity dataTypeName, @Nonnull Class<T> clazz) throws ClassNotFoundException { + this(db, dataTypeName.getName(), clazz, null); + } + public <X extends T, @NonNull B extends Builder<X>> EsDataObjectReaderWriter2(DatabaseClient db, String dataTypeName, @Nonnull Class<T> clazz, @Nullable Class<B> builderClazz) throws ClassNotFoundException { + LOG.info("Create {} for datatype {} class {}", this.getClass().getName(), dataTypeName, clazz.getName()); + + this.esIdAddAtributteName = null; + this.field = null; + this.writeInterfaceClazz = clazz; + this.db = db; + this.dataTypeName = dataTypeName; + this.yangtoolsMapper = new YangToolsMapper2<>(clazz, builderClazz); + this.clazz = clazz; + } + + /** + * Simlar to {@link #setEsIdAttributeName()}, but adapts the parameter to yangtools attribute naming schema + * @param esIdAttributeName is converted to UnderscoreCamelCase + * @return this for further operations. + */ + public EsDataObjectReaderWriter2<T> setEsIdAttributeNameCamelized(String esIdAttributeName) { + return setEsIdAttributeName(YangToolsMapper.toCamelCaseAttributeName(esIdAttributeName)); + } + + /** + * Attribute name of class that is containing the object id + * @param esIdAttributeName of the implementation class for the yangtools interface. + * Expected attribute name format is CamelCase with leading underline. @ + * @return this for further operations. + * @throws SecurityException if no access or IllegalArgumentException if wrong type or no attribute with this name. + */ + public EsDataObjectReaderWriter2<T> setEsIdAttributeName(String esIdAttributeName) { + LOG.debug("Set attribute '{}'", esIdAttributeName); + this.esIdAddAtributteName = null; // Reset status + this.field = null; + + Field attributeField; + try { + Builder<T> builder = yangtoolsMapper.getBuilder(clazz); + if (builder == null) { + String msg = "No builder for " + clazz; + LOG.debug(msg); + throw new IllegalArgumentException(msg); + } else { + T object = builder.build(); + attributeField = object.getClass().getDeclaredField(esIdAttributeName); + if (attributeField.getType().equals(String.class)) { + attributeField.setAccessible(true); + this.esIdAddAtributteName = esIdAttributeName; // Set new status if everything OK + this.field = attributeField; + } else { + String msg = "Wrong field type " + attributeField.getType().getName() + " of " + esIdAttributeName; + LOG.debug(msg); + throw new IllegalArgumentException(msg); + } + } + } catch (NoSuchFieldException e) { + // Convert to run-time exception + String msg = "NoSuchFieldException for '" + esIdAttributeName + "' in class " + clazz.getName(); + LOG.debug(msg); + throw new IllegalArgumentException(msg); + } catch (SecurityException e) { + LOG.debug("Access problem "+esIdAttributeName,e); + throw e; + } + return this; + } + + /** + * Specify subclass of T for write operations. + * @param writeInterfaceClazz + */ + public EsDataObjectReaderWriter2<T> setWriteInterface( @Nonnull Class<? extends DataObject> writeInterfaceClazz ) { + LOG.debug("Set write interface to {}", writeInterfaceClazz); + if (writeInterfaceClazz == null) { + throw new IllegalArgumentException("Null not allowed here."); + } + + this.writeInterfaceClazz = writeInterfaceClazz; + return this; + } + + public interface IdGetter<S extends DataObject> { + String getId(S object); + } + + public <S extends DataObject> void write(List<S> objectList, IdGetter<S> idGetter) { + for (S object : objectList) { + write(object, idGetter.getId(object)); + } + } + + /** + * Write child object to database with specific id + * @param object + * @param @Nullable esId use the id or if null generate unique id + * @return String with id or null + */ + public @Nullable <S extends DataObject> String write(S object, @Nullable String esId) { + if (writeInterfaceClazz.isInstance(object)) { + try { + String json = yangtoolsMapper.writeValueAsString(object); + return db.doWriteRaw(dataTypeName, esId, json); + } catch (JsonProcessingException e) { + LOG.error("Write problem: ", e); + } + } else { + LOG.error("Type {} does not provide interface {}", object!=null?object.getClass().getName():"null", + writeInterfaceClazz.getName()); + } + return null; + } + /** + * Update partial child object to database with match/term query + * @param object + * @param esId + * @return String with esId or null + */ + public @Nullable <S extends DataObject> String update(S object, QueryBuilder query) { + if (writeInterfaceClazz.isInstance(object)) { + try { + String json = yangtoolsMapper.writeValueAsString(object); + return db.doUpdate(this.dataTypeName,json,query); + } catch (JsonProcessingException e) { + LOG.error("Update problem: ", e); + } + } else { + LOG.error("Type {} does not provide interface {}", object!=null?object.getClass().getName():"null", + writeInterfaceClazz.getName()); + } + return null; + } + /** + * Write/ update partial child object to database with specific id Write if not + * exists, else update + * @param object + * @param esId + * @return String with esId or null + */ + public @Nullable <S extends DataObject> String update(S object, String esId) { + return this.updateOrCreate(object, esId,null); + } + /** + * See {@link doUpdateOrCreate(String dataTypeName, String esId, String json, List<String> doNotUpdateField) } + */ + public @Nullable <S extends DataObject> String updateOrCreate(S object, String esId,List<String> onlyForInsert) { + if (writeInterfaceClazz.isInstance(object)) { + try { + String json = yangtoolsMapper.writeValueAsString(object); + return db.doUpdateOrCreate(dataTypeName, esId, json,onlyForInsert); + } catch (JsonProcessingException e) { + LOG.error("Update problem: ", e); + } + } else { + LOG.error("Type {} does not provide interface {}", object!=null?object.getClass().getName():"null", + writeInterfaceClazz.getName()); + } + return null; + } + + /** + * Read object from database, by using the id field + * @param object + * @return + */ + public @Nullable T read(String esId) { + @Nullable + T res = null; + if (esId != null) { + String json = db.doReadJsonData(dataTypeName, esId); + if (json != null) { + try { + res = yangtoolsMapper.readValue(json.getBytes(), clazz); + } catch (IOException e) { + LOG.error("Problem: ", e); + } + } else { + LOG.debug("Can not read from DB id {} type {}", esId, dataTypeName); + } + } + return res; + } + + /** + * Remove object + * @param esId to identify the object. + * @return success + */ + public boolean remove(String esId) { + return db.doRemove(this.dataTypeName, esId); + } + + public int remove(QueryBuilder query) { + return this.db.doRemove(this.dataTypeName, query); + } + /** + * Get all elements of related type + * @return all Elements + */ + public SearchResult<T> doReadAll() { + return doReadAll(null); + } + public SearchResult<T> doReadAll(QueryBuilder query) { + return this.doReadAll(query,false); + } + /** + * Read all existing objects of a type + * @param query for the elements + * @return the list of all objects + */ + + public SearchResult<T> doReadAll(QueryBuilder query, boolean ignoreException) { + + SearchResult<T> res = new SearchResult<>(); + int idx = 0; //Idx for getAll + int iterateLength = 100; //Step width for iterate + + SearchResult<SearchHit> result; + List<SearchHit> hits; + do { + if(query!=null) { + LOG.debug("read data in {} with query {}",dataTypeName,query.toJSON()); + result=db.doReadByQueryJsonData( dataTypeName, query,ignoreException); + } + else { + result = db.doReadAllJsonData(dataTypeName,ignoreException); + } + hits=result.getHits(); + LOG.debug("Read: {} elements: {} Failures: {}",dataTypeName,hits.size(), yangtoolsMapper.getMappingFailures()); + + T object; + idx += result.getHits().size(); + for (SearchHit hit : hits) { + object = getT(hit.getSourceAsString()); + LOG.debug("Mapp Object: {}\nSource: '{}'\nResult: '{}'\n Failures: {}", hit.getId(), + hit.getSourceAsString(), object, yangtoolsMapper.getMappingFailures()); + if (object != null) { + setEsId(object, hit.getId()); + res.add(object); + } else { + LOG.warn("Mapp result null Object: {}\n Source: '{}'\n : '", hit.getId(), hit.getSourceAsString()); + } + } + + } while (hits.size() == iterateLength); // Do it until end indicated, because less hits than iterateLength + // allows. + res.setTotal(result.getTotal()); + return res; + } + + /* --------------------------------------------- + * Private functions + */ + + private void setEsId(T object, String esId) { + if (field != null) { + try { + field.set(object, esId); + } catch (IllegalArgumentException | IllegalAccessException e) { + LOG.debug("Field set problem.", e); } + } + } + + private @Nullable T getT(String jsonString) { + try { + return yangtoolsMapper.readValue( jsonString, clazz ); + } catch (IOException e) { + LOG.info("Mapping problem", e); + return null; + } + } + +} diff --git a/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/database/config/EsConfig.java b/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/database/config/EsConfig.java new file mode 100644 index 000000000..c828f3302 --- /dev/null +++ b/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/database/config/EsConfig.java @@ -0,0 +1,164 @@ +/******************************************************************************* + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + ******************************************************************************/ +package org.onap.ccsdk.features.sdnr.wt.database.config; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.onap.ccsdk.features.sdnr.wt.common.configuration.Configuration; +import org.onap.ccsdk.features.sdnr.wt.common.configuration.ConfigurationFileRepresentation; +import org.onap.ccsdk.features.sdnr.wt.common.database.config.HostInfo; +import org.onap.ccsdk.features.sdnr.wt.common.database.config.HostInfo.Protocol; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EsConfig implements Configuration { + + private static final Logger LOG = LoggerFactory.getLogger(EsConfig.class); + + public static final String SECTION_MARKER_ES = "es"; + + private static final String PROPERTY_KEY_DBHOSTS = "esHosts"; + private static final String PROPERTY_KEY_ARCHIVE_LIMIT = "esArchiveLifetimeSeconds"; + private static final String PROPERTY_KEY_CLUSTER = "esCluster"; + private static final String PROPERTY_KEY_ARCHIVE_INTERVAL = "esArchiveCheckIntervalSeconds"; + private static final String PROPERTY_KEY_NODE = "esNode"; + + private static String defaultHostinfo = printHosts(new HostInfo[] { new HostInfo("sdnrdb", 9200, Protocol.HTTP) }); + private static final String DEFAULT_VALUE_CLUSTER = ""; + /** check db data in this interval [in seconds] 0 deactivated */ + private static final String DEFAULT_ARCHIVE_INTERVAL_SEC = "0"; + /** keep data for this time [in seconds] 30 days */ + private static final String DEFAULT_ARCHIVE_LIMIT_SEC = String.valueOf(60L * 60L * 24L * 30L); + private static final String DEFAULT_KEY_NODE = "elasticsearchnode"; + + private final ConfigurationFileRepresentation configuration; + + public EsConfig(ConfigurationFileRepresentation configuration) { + + this.configuration = configuration; + this.configuration.addSection(SECTION_MARKER_ES); + defaults(); + } + + /* + * Setter + */ + + public void setNode(String nodeName) { + configuration.setProperty(SECTION_MARKER_ES, PROPERTY_KEY_NODE, nodeName); + } + + /* + * Getter + */ + + public String getNode() { + return configuration.getProperty(SECTION_MARKER_ES, PROPERTY_KEY_NODE); + } + + public HostInfo[] getHosts() { + String dbHosts = configuration.getProperty(SECTION_MARKER_ES, PROPERTY_KEY_DBHOSTS); + return parseHosts(dbHosts); + } + public void setHosts(HostInfo[] hosts) { + this.configuration.setProperty(SECTION_MARKER_ES, PROPERTY_KEY_DBHOSTS, printHosts(hosts)); + } + public String getCluster() { + return configuration.getProperty(SECTION_MARKER_ES, PROPERTY_KEY_ARCHIVE_INTERVAL); + } + + public void setCluster(String cluster) { + configuration.setProperty(SECTION_MARKER_ES, PROPERTY_KEY_CLUSTER, cluster); + } + + public long getArchiveCheckIntervalSeconds() { + return configuration.getPropertyLong(SECTION_MARKER_ES, PROPERTY_KEY_ARCHIVE_INTERVAL).orElse(0L); + } + + public void setArchiveCheckIntervalSeconds(long seconds) { + configuration.setProperty(SECTION_MARKER_ES, PROPERTY_KEY_ARCHIVE_INTERVAL, seconds); + } + + public long getArchiveLifetimeSeconds() { + return configuration.getPropertyLong(SECTION_MARKER_ES, PROPERTY_KEY_ARCHIVE_LIMIT).orElse(0L); + } + + public void setArchiveLimit(long seconds) { + configuration.setProperty(SECTION_MARKER_ES, PROPERTY_KEY_ARCHIVE_LIMIT, seconds); + } + + @Override + public String getSectionName() { + return SECTION_MARKER_ES; + } + + @Override + public void defaults() { + // Add default if not available + configuration.setPropertyIfNotAvailable(SECTION_MARKER_ES, PROPERTY_KEY_DBHOSTS, defaultHostinfo); + configuration.setPropertyIfNotAvailable(SECTION_MARKER_ES, PROPERTY_KEY_ARCHIVE_LIMIT, + DEFAULT_ARCHIVE_LIMIT_SEC); + configuration.setPropertyIfNotAvailable(SECTION_MARKER_ES, PROPERTY_KEY_CLUSTER, DEFAULT_VALUE_CLUSTER); + configuration.setPropertyIfNotAvailable(SECTION_MARKER_ES, PROPERTY_KEY_ARCHIVE_INTERVAL, + DEFAULT_ARCHIVE_INTERVAL_SEC); + configuration.setPropertyIfNotAvailable(SECTION_MARKER_ES, PROPERTY_KEY_NODE, DEFAULT_KEY_NODE); + } + + /** @TODO Shift to own class **/ + private static String printHosts(HostInfo[] h) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < h.length; i++) { + sb.append(h[i].toUrl()); + if (i != h.length - 1) { + sb.append(","); + } + } + return sb.toString(); + } + + /** @TODO Shift to own class **/ + private static HostInfo[] parseHosts(String string) { + List<HostInfo> infos = new ArrayList<HostInfo>(); + String[] list = string.split(","); + if (list.length > 0) { + for (String item : list) { + try { + URL url = new URL(item); + infos.add(new HostInfo(url.getHost(), url.getPort(), Protocol.getValueOf(url.getProtocol()))); + } catch (MalformedURLException e) { + LOG.warn("problem parsing url {} : {}", item, e.getMessage()); + } + } + } + HostInfo[] a = new HostInfo[infos.size()]; + return infos.toArray(a); + } + + @Override + public String toString() { + return "EsConfig [getNode()=" + getNode() + ", getHosts()=" + Arrays.toString(getHosts()) + ", getCluster()=" + + getCluster() + ", getArchiveCheckIntervalSeconds()=" + getArchiveCheckIntervalSeconds() + + ", getArchiveLifetimeSeconds()=" + getArchiveLifetimeSeconds() + ", getSectionName()=" + + getSectionName() + "]"; + } + +} diff --git a/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/yangtools/YangToolsCloner.java b/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/yangtools/YangToolsCloner.java new file mode 100644 index 000000000..1ac19ff34 --- /dev/null +++ b/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/yangtools/YangToolsCloner.java @@ -0,0 +1,217 @@ +/******************************************************************************* + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + ******************************************************************************/ +package org.onap.ccsdk.features.sdnr.wt.yangtools; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.Nullable; + +import org.opendaylight.yangtools.concepts.Builder; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class YangToolsCloner { + + private static YangToolsMapper yangtoolsMapper = new YangToolsMapper(); + private static final Logger LOG = LoggerFactory.getLogger(YangToolsCloner.class); + public static final int ACCESSOR_FIELD = 0; + public static final int ACCESSOR_METHOD = 1; + + + private final int accessor; + + private YangToolsCloner(int ac) { + this.accessor = ac; + } + public static YangToolsCloner instance() { + return instance(ACCESSOR_METHOD); + } + public static YangToolsCloner instance(int ac) { + return new YangToolsCloner(ac); + } + /** + * + * @param source source object + * @param clazz Class of return object + * @return list of cloned object + * @return + */ + public <S extends DataObject, T extends DataObject> List<T> cloneList(List<S> source, Class<T> clazz) { + return cloneList(source, clazz, null); + } + + /** + * + * @param source source object + * @param clazz Class of return object + * @attrList filter for attribute Names to clone + * @return list of cloned object + */ + public <S extends DataObject, T extends DataObject> List<T> cloneList(List<S> source, Class<T> clazz, + @Nullable List<String> attrList) { + if (source == null) { + return null; + } + List<T> list = new ArrayList<T>(); + for (S s : source) { + list.add(clone(s, clazz, attrList)); + } + return list; + } + + /** + * + * @param source source object + * @param clazz Class of return object + * @return cloned object + */ + public <S , T extends DataObject> T clone(S source, Class<T> clazz) { + return clone(source, clazz, null); + } + /** + * + * @param source source object + * @param clazz Class of return object + * @attrList if empty copy all else list of attribute Names to clone + * @return cloned object + */ + public <S, T extends DataObject> T clone(S source, Class<T> clazz, + @Nullable List<String> attrList) { + if (source == null) { + return (T)null; + } + Field[] attributeFields; + Field sourceField; + Method m; + Builder<T> builder = yangtoolsMapper.getBuilder(clazz); + T object = builder.build(); + attributeFields = object.getClass().getDeclaredFields(); + for (Field attributeField : attributeFields) { + // check if attr is in inclusion list + if (attrList != null && !attrList.contains(attributeField.getName())) { + continue; + } + // ignore QNAME + if (attributeField.getName().equals("QNAME")) { + continue; + } + + attributeField.setAccessible(true); + try { + if(accessor==ACCESSOR_FIELD) { + sourceField = source.getClass().getDeclaredField(attributeField.getName()); + sourceField.setAccessible(true); + if (attributeField.getType().equals(String.class) && !sourceField.getType().equals(String.class)) { + attributeField.set(object, String.valueOf(sourceField.get(source))); + } else { + attributeField.set(object, sourceField.get(source)); + } + } + else if(accessor==ACCESSOR_METHOD) { + String getter = getter(attributeField.getName()); + System.out.println("getter="+getter); + m = source.getClass().getDeclaredMethod(getter); + m.setAccessible(true); + if (attributeField.getType().equals(String.class) && !m.getReturnType().equals(String.class)) { + attributeField.set(object, String.valueOf(m.invoke(source))); + } else { + attributeField.set(object, m.invoke(source)); + } + } + + } catch (NoSuchMethodException | NoSuchFieldException e) { + // Convert to run-time exception + String msg = "no such field " + attributeField.getName() + " in class " + source.getClass().getName(); + LOG.debug(msg); + // throw new IllegalArgumentException(msg); + } catch (IllegalAccessException|SecurityException e) { + LOG.debug("Access problem " + attributeField.getName(), e); + } catch (IllegalArgumentException e) { + LOG.debug("argument problem " + attributeField.getName(), e); + } catch (InvocationTargetException e) { + LOG.debug("invocation problem " + attributeField.getName(), e); + } + } + + return object; + } + + private static String getter(String name) { + return String.format("%s%s%s","get",name.substring(1, 2).toUpperCase(),name.substring(2)); + } + public <S extends DataObject, T extends DataObject,B extends Builder<T>> B cloneToBuilder(S source, B builder){ + return cloneToBuilder(source, builder,null); + } + public <S extends DataObject, T extends DataObject,B extends Builder<T>> B cloneToBuilder(S source, B builder, + @Nullable List<String> attrList) { + Field[] attributeFields; + Field sourceField; + Method m; + attributeFields = builder.getClass().getDeclaredFields(); + for (Field attributeField : attributeFields) { + // check if attr is in inclusion list + if (attrList != null && !attrList.contains(attributeField.getName())) { + continue; + } + // ignore QNAME + if (attributeField.getName().equals("QNAME")) { + continue; + } + + attributeField.setAccessible(true); + try { + if(accessor==ACCESSOR_FIELD) { + sourceField = source.getClass().getDeclaredField(attributeField.getName()); + sourceField.setAccessible(true); + if (attributeField.getType().equals(String.class) && !sourceField.getType().equals(String.class)) { + attributeField.set(builder, String.valueOf(sourceField.get(source))); + } else { + attributeField.set(builder, sourceField.get(source)); + } + } + else if(accessor==ACCESSOR_METHOD) { + m = source.getClass().getDeclaredMethod(getter(attributeField.getName())); + m.setAccessible(true); + if (attributeField.getType().equals(String.class) && !m.getReturnType().equals(String.class)) { + attributeField.set(builder, String.valueOf(m.invoke(source))); + } else { + attributeField.set(builder, m.invoke(source)); + } + } + + } catch (NoSuchMethodException | NoSuchFieldException e) { + // Convert to run-time exception + String msg = "no such field " + attributeField.getName() + " in class " + source.getClass().getName(); + LOG.debug(msg); + // throw new IllegalArgumentException(msg); + } catch (IllegalAccessException|SecurityException e) { + LOG.debug("Access problem " + attributeField.getName(), e); + } catch (IllegalArgumentException e) { + LOG.debug("argument problem " + attributeField.getName(), e); + } catch (InvocationTargetException e) { + LOG.debug("invocation problem " + attributeField.getName(), e); + } + } + return builder; + } +} diff --git a/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/yangtools/YangToolsCloner2.java b/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/yangtools/YangToolsCloner2.java new file mode 100644 index 000000000..37d7aa4ad --- /dev/null +++ b/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/yangtools/YangToolsCloner2.java @@ -0,0 +1,178 @@ +/******************************************************************************* + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + ******************************************************************************/ +package org.onap.ccsdk.features.sdnr.wt.yangtools; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class YangToolsCloner2 { + + private static final Logger LOG = LoggerFactory.getLogger(YangToolsCloner2.class); + + public enum Accessor { + ACCESSOR_FIELD, + ACCESSOR_METHOD; + } + + private Accessor accessor; + + public YangToolsCloner2() { + LOG.info("Provide new {}",this.getClass().getName()); + this.accessor = Accessor.ACCESSOR_METHOD; + } + + YangToolsCloner2 setAcessor(Accessor accessor) { + this.accessor = accessor; + return this; + } + + Accessor getAccessor() { + return accessor; + } + + public interface Builder<T> { + T build(); + } + + /** + * + * @param source source object + * @param clazz Class of return object + * @attrList filter for attribute Names to clone + * @return list of cloned object + * @throws Exception + */ + public <S, T> List<T> cloneList(List<S> source, Builder<T> builder, String ... attrList) throws Exception { + if (source == null) { + return null; + } + List<T> list = new ArrayList<T>(); + for (S s : source) { + list.add(copyAttributes(s, builder.build(), attrList)); + } + return list; + } + + /** + * Copy attributes from source to destination object. + * Copy the references. + * @param source source object + * @param clazz Class of return object + * @attrList attribute Names NOT to clone. + * @return cloned object + * @throws Exception + */ + @SuppressWarnings("null") + public @Nullable <S, T> T copyAttributes(S source, T destination, String ... attributeArray) throws Exception { + + LOG.debug("copyAttributes source.class {} destination.class {} attributes {}", source, destination, attributeArray.length); + + if (destination == null || source == null) + return null; + + List<String> attributeList = Arrays.asList(attributeArray); + LOG.debug("copyAttributes 2 attributes {}", attributeList); + + Field[] destinationAttributeFields = source.getClass().getDeclaredFields(); + String destinationName; + Class<?> destinationType; + for (Field destinationAttributeField : destinationAttributeFields) { + destinationName = destinationAttributeField.getName(); + destinationType = destinationAttributeField.getType(); + LOG.debug("Field: {}", destinationName); + // check if attr is in exclusion list + if (attributeList.contains(destinationName)) { + continue; + } + // ignore QNAME + if (destinationName.equals("QNAME")) { + continue; + } + + destinationAttributeField.setAccessible(true); + Object sourceData = null; + Class<?> sourceType = null; + Class<?> sourceListType = null; + try { + if (accessor == Accessor.ACCESSOR_FIELD) { + Field sourceField; + sourceField = source.getClass().getDeclaredField(destinationName); + sourceField.setAccessible(true); + sourceType = sourceField.getType(); + sourceData = sourceField.get(source); + sourceListType = getListClass(sourceType, sourceData); + + } else if (accessor == Accessor.ACCESSOR_METHOD) { + Method sourceMethod; + sourceMethod = source.getClass().getDeclaredMethod(getter(destinationName)); + sourceMethod.setAccessible(true); + sourceType = sourceMethod.getReturnType(); + sourceData = sourceMethod.invoke(source); + sourceListType = getListClass(sourceType, sourceData); + } + LOG.info("Handle {} {} {}", destinationName, destinationType, sourceType); + if (destinationType == sourceType) { + destinationAttributeField.set(destination, sourceData); + } else { + throw new Exception( + "Problem to copy attribute " + destinationName + +" Sourceclass:" +sourceType + +" Destinationclass:" + destinationType + +" Method:"+accessor.name()); + } + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException + | NoSuchMethodException | InvocationTargetException e) { + throw e; + } + } + return destination; + + } + + private static String getter(String name) { + if (name == null || name.length() == 0) { + return null; + } else if (name.length() == 1) { + return String.format("%s%s", "get", name.substring(1, 2).toUpperCase()); + } else { // >= 2 + return String.format("%s%s%s", "get", name.substring(1, 2).toUpperCase(), name.substring(2)); + } + } + + private static Class<?> getListClass(Class<?> sourceType, Object sourceData) { + if (sourceData != null && sourceType.equals(List.class)) { + List<Object> sourceDataList = (List<Object>)sourceData; + if (sourceDataList.size() > 0) { + LOG.info("Is list with type"+sourceDataList.get(0).getClass().getName()); + } else { + LOG.info("Is empty list"); + } + } + return(sourceType); + } + +} diff --git a/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/yangtools/YangToolsMapper.java b/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/yangtools/YangToolsMapper.java new file mode 100644 index 000000000..8306cb7d4 --- /dev/null +++ b/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/yangtools/YangToolsMapper.java @@ -0,0 +1,226 @@ +/******************************************************************************* + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + ******************************************************************************/ +package org.onap.ccsdk.features.sdnr.wt.yangtools; + +import java.io.IOException; +import javax.annotation.Nullable; + +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.Credentials; +import org.opendaylight.yangtools.concepts.Builder; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder.Value; +import com.fasterxml.jackson.databind.introspect.AnnotatedClass; +import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.DateAndTime; +/** + * YangToolsMapper is a specific Jackson mapper configuration for opendaylight yangtools serialization or deserialization of DataObject to/from JSON + * TODO ChoiceIn and Credentials deserialization only for LoginPasswordBuilder + */ +public class YangToolsMapper extends ObjectMapper { + + private final Logger LOG = LoggerFactory.getLogger(YangToolsMapper.class); + private static final long serialVersionUID = 1L; + private static BundleContext context; + + public YangToolsMapper() { + super(); + configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + setPropertyNamingStrategy(PropertyNamingStrategy.KEBAB_CASE); + setSerializationInclusion(Include.NON_NULL); + setAnnotationIntrospector(new YangToolsBuilderAnnotationIntrospector()); + SimpleModule dateAndTimeSerializerModule = new SimpleModule(); + dateAndTimeSerializerModule.addSerializer(DateAndTime.class,new CustomDateAndTimeSerializer()); + registerModule(dateAndTimeSerializerModule ); + Bundle bundle = FrameworkUtil.getBundle(YangToolsMapper.class); + context = bundle != null ? bundle.getBundleContext() : null; + } + + @Override + public String writeValueAsString(Object value) throws JsonProcessingException { + // TODO Auto-generated method stub + return super.writeValueAsString(value); + } + /** + * Get Builder object for yang tools interface. + * @param <T> yang-tools base datatype + * @param clazz class with interface. + * @return builder for interface or null if not existing + */ + @SuppressWarnings("unchecked") + public @Nullable <T extends DataObject> Builder<T> getBuilder(Class<T> clazz) { + String builder = clazz.getName() + "Builder"; + try { + Class<?> clazzBuilder = findClass(builder); + return (Builder<T>) clazzBuilder.newInstance(); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { + LOG.debug("Problem ", e); + return null; + } + } + + /** + * Callback for handling mapping failures. + * @return + */ + public int getMappingFailures() { + return 0; + } + + /** + * Provide mapping of string to attribute names, generated by yang-tools. + * "netconf-id" converted to "_netconfId" + * @param name with attribute name, not null or empty + * @return converted string or null if name was empty or null + */ + public @Nullable static String toCamelCaseAttributeName(final String name) { + if (name == null || name.isEmpty()) + return null; + + final StringBuilder ret = new StringBuilder(name.length()); + if (!name.startsWith("_")) + ret.append('_'); + int start = 0; + for (final String word : name.split("-")) { + if (!word.isEmpty()) { + if (start++ == 0) { + ret.append(Character.toLowerCase(word.charAt(0))); + } else { + ret.append(Character.toUpperCase(word.charAt(0))); + } + ret.append(word.substring(1)); + } + } + return ret.toString(); + } + + /** + * Adapted Builder callbacks + */ + private static class YangToolsBuilderAnnotationIntrospector extends JacksonAnnotationIntrospector { + private static final long serialVersionUID = 1L; + + @Override + public Class<?> findPOJOBuilder(AnnotatedClass ac) { + try { + String builder = null; + if (ac.getRawType().equals(Credentials.class)) { + builder = "org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPasswordBuilder"; + //System.out.println(DataContainer.class.isAssignableFrom(ac.getRawType())); + //System.out.println(ChoiceIn.class.isAssignableFrom(ac.getRawType())); + + } + else if(ac.getRawType().equals(DateAndTime.class)) { + builder = DateAndTimeBuilder.class.getName(); + } + + else { + if (ac.getRawType().isInterface()) { + builder = ac.getName()+"Builder"; + } + } + if (builder != null) { + //System.out.println("XX1: "+ac.getRawType()); + //System.out.println("XX2: "+builder); + //Class<?> innerBuilder = Class.forName(builder); + Class<?> innerBuilder = findClass(builder); + //System.out.println("Builder found: "+ innerBuilder); + return innerBuilder; + } + } catch( ClassNotFoundException e ) { + // No problem .. try next + } + return super.findPOJOBuilder(ac); + } + + @Override + public Value findPOJOBuilderConfig(AnnotatedClass ac) { + if (ac.hasAnnotation(JsonPOJOBuilder.class)) { + return super.findPOJOBuilderConfig(ac); + } + return new JsonPOJOBuilder.Value("build", "set"); + } + } + + private static Class<?> findClass(String name) throws ClassNotFoundException { + // Try to find in other bundles + if (context != null) { + //OSGi environment + for (Bundle b : context.getBundles()) { + try { + return b.loadClass(name); + } catch (ClassNotFoundException e) { + // No problem, this bundle doesn't have the class + } + } + throw new ClassNotFoundException("Can not find Class in OSGi context."); + } else { + return Class.forName(name); + } + // not found in any bundle + } + public static class DateAndTimeBuilder{ + + private final String _value; + + public DateAndTimeBuilder(String v) { + this._value= v; + } + + public DateAndTime build() { + return new DateAndTime(_value); + } + + } + public static class CustomDateAndTimeSerializer extends StdSerializer<DateAndTime>{ + + /** + * + */ + private static final long serialVersionUID = 1L; + + public CustomDateAndTimeSerializer() { + this(null); + } + protected CustomDateAndTimeSerializer(Class<DateAndTime> t) { + super(t); + } + + @Override + public void serialize(DateAndTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeString(value.getValue()); + } + + } +} diff --git a/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/yangtools/YangToolsMapper2.java b/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/yangtools/YangToolsMapper2.java new file mode 100644 index 000000000..0ee76b074 --- /dev/null +++ b/sdnr/wt/data-provider/database/src/main/java/org/onap/ccsdk/features/sdnr/wt/yangtools/YangToolsMapper2.java @@ -0,0 +1,263 @@ +/******************************************************************************* + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + ******************************************************************************/ +package org.onap.ccsdk.features.sdnr.wt.yangtools; + +import java.io.IOException; +import javax.annotation.Nullable; + +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.Credentials; +import org.opendaylight.yangtools.concepts.Builder; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder.Value; +import com.fasterxml.jackson.databind.introspect.AnnotatedClass; +import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.DateAndTime; +/** + * YangToolsMapper is a specific Jackson mapper configuration for opendaylight yangtools serialization or deserialization of DataObject to/from JSON + * TODO ChoiceIn and Credentials deserialization only for LoginPasswordBuilder + */ +public class YangToolsMapper2<T extends DataObject> extends ObjectMapper { + + private final Logger LOG = LoggerFactory.getLogger(YangToolsMapper2.class); + private static final long serialVersionUID = 1L; + private static String ENTITY = "Entity"; + private static String BUILDER = "Builder"; + + private @Nullable Class<T> clazz; + private @Nullable Class<? extends Builder<? extends T>> builderClazz; + + private BundleContext context; + + public <X extends T, B extends Builder<X>> YangToolsMapper2(Class<T> clazz, Class<B> builderClazz) throws ClassNotFoundException { + super(); + configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + setPropertyNamingStrategy(PropertyNamingStrategy.KEBAB_CASE); + setSerializationInclusion(Include.NON_NULL); + setAnnotationIntrospector(new YangToolsBuilderAnnotationIntrospector()); + SimpleModule dateAndTimeSerializerModule = new SimpleModule(); + dateAndTimeSerializerModule.addSerializer(DateAndTime.class, new CustomDateAndTimeSerializer()); + registerModule(dateAndTimeSerializerModule ); + Bundle bundle = FrameworkUtil.getBundle(YangToolsMapper2.class); + + this.clazz = clazz; + this.builderClazz = builderClazz != null ? builderClazz : getBuilderClass(getBuilderClassName(clazz)) ; + context = bundle != null ? bundle.getBundleContext() : null; + } + + public YangToolsMapper2() throws ClassNotFoundException { + this(null, null); + } + + + @Override + public String writeValueAsString(Object value) throws JsonProcessingException { + return super.writeValueAsString(value); + } + /** + * Get Builder object for yang tools interface. + * @param <T> yang-tools base datatype + * @param clazz class with interface. + * @return builder for interface or null if not existing + */ + @SuppressWarnings("unchecked") + public @Nullable <T extends DataObject> Builder<T> getBuilder(Class<T> clazz) { + try { + //Class<?> clazzBuilder = getBuilderClass(getBuilderClassName(clazz)); + return (Builder<T>) builderClazz.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + LOG.debug("Problem ", e); + return null; + } + } + + /** + * Callback for handling mapping failures. + * @return + */ + public int getMappingFailures() { + return 0; + } + + /** + * Provide mapping of string to attribute names, generated by yang-tools. + * "netconf-id" converted to "_netconfId" + * @param name with attribute name, not null or empty + * @return converted string or null if name was empty or null + */ + public @Nullable static String toCamelCaseAttributeName(final String name) { + if (name == null || name.isEmpty()) + return null; + + final StringBuilder ret = new StringBuilder(name.length()); + if (!name.startsWith("_")) + ret.append('_'); + int start = 0; + for (final String word : name.split("-")) { + if (!word.isEmpty()) { + if (start++ == 0) { + ret.append(Character.toLowerCase(word.charAt(0))); + } else { + ret.append(Character.toUpperCase(word.charAt(0))); + } + ret.append(word.substring(1)); + } + } + return ret.toString(); + } + + /** Verify if builder is available + * @throws ClassNotFoundException **/ + public Class<?> assertBuilderClass(Class<?> clazz) throws ClassNotFoundException { + return getBuilderClass(getBuilderClassName(clazz)); + } + + // --- Private functions + + /** + * Create name of builder class + * @param <T> + * @param clazz + * @return builders class name + * @throws ClassNotFoundException + */ + private static String getBuilderClassName(Class<?> clazz) { + return clazz.getName() + BUILDER; +// String clazzName = clazz.getName(); +// if (clazzName.endsWith(ENTITY)) { +// return clazzName.replace(ENTITY, BUILDER); +// } else { +// return clazzName + BUILDER; +// } + } + + /** + * Search builder in context + * @param name + * @return + * @throws ClassNotFoundException + */ + @SuppressWarnings("unchecked") + private <X extends T, B extends Builder<X>> Class<B> getBuilderClass(String name) throws ClassNotFoundException { + // Try to find in other bundles + if (context != null) { + //OSGi environment + for (Bundle b : context.getBundles()) { + try { + return (Class<B>) b.loadClass(name); + } catch (ClassNotFoundException e) { + // No problem, this bundle doesn't have the class + } + } + throw new ClassNotFoundException("Can not find Class in OSGi context."); + } else { + return (Class<B>) Class.forName(name); + } + // not found in any bundle + } + + // --- Classes + + /** + * Adapted Builder callbacks + */ + private class YangToolsBuilderAnnotationIntrospector extends JacksonAnnotationIntrospector { + private static final long serialVersionUID = 1L; + + @Override + public Class<?> findPOJOBuilder(AnnotatedClass ac) { + + if (ac.getRawType().equals(Credentials.class)) { + return org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPasswordBuilder.class; + + } else if (ac.getRawType().equals(DateAndTime.class)) { + return DateAndTimeBuilder.class; + + } else if (ac.getRawType().equals(clazz)) { + return builderClazz; + } + + if (ac.getRawType().isInterface()) { + String builder = getBuilderClassName(ac.getRawType()); + try { + Class<?> innerBuilder = getBuilderClass(builder); + return innerBuilder; + } catch (ClassNotFoundException e) { + // No problem .. try next + } + } + return super.findPOJOBuilder(ac); + } + + @Override + public Value findPOJOBuilderConfig(AnnotatedClass ac) { + if (ac.hasAnnotation(JsonPOJOBuilder.class)) { + return super.findPOJOBuilderConfig(ac); + } + return new JsonPOJOBuilder.Value("build", "set"); + } + } + + public static class DateAndTimeBuilder{ + + private final String _value; + + public DateAndTimeBuilder(String v) { + this._value= v; + } + + public DateAndTime build() { + return new DateAndTime(_value); + } + + } + public static class CustomDateAndTimeSerializer extends StdSerializer<@NonNull DateAndTime>{ + + private static final long serialVersionUID = 1L; + + public CustomDateAndTimeSerializer() { + this(null); + } + protected CustomDateAndTimeSerializer(Class<DateAndTime> t) { + super(t); + } + + @Override + public void serialize(DateAndTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeString(value.getValue()); + } + + } +} diff --git a/sdnr/wt/data-provider/database/src/main/resources/es-init.sh b/sdnr/wt/data-provider/database/src/main/resources/es-init.sh index be7d487f7..0b122c060 100755 --- a/sdnr/wt/data-provider/database/src/main/resources/es-init.sh +++ b/sdnr/wt/data-provider/database/src/main/resources/es-init.sh @@ -37,7 +37,7 @@ INITFILENAME="Init.script" set_definition() { def "connectionlog" '{"node-id": {"type": "keyword"},"timestamp": {"type": "date"},"status": {"type": "keyword"}}' def "maintenancemode" '{"node-id": {"type": "keyword"},"active": {"type": "boolean"}},"date_detection":false}}' - def "faultlog" '{"node-id": {"type": "keyword"},"severity": {"type": "keyword"},"timestamp": {"type": "date"},"problem": {"type": "keyword"},"counter": {"type": "keyword"},"object-id":{"type": "keyword"}}' + def "faultlog" '{"node-id": {"type": "keyword"},"severity": {"type": "keyword"},"timestamp": {"type": "date"},"problem": {"type": "keyword"},"counter": {"type": "keyword"},"object-id":{"type": "keyword"},"source-type":{"type": "keyword"}}' def "faultcurrent" '{"node-id": {"type": "keyword"},"severity": {"type": "keyword"},"timestamp": {"type": "date"},"problem": {"type": "keyword"},"counter": {"type": "keyword"},"object-id":{"type": "keyword"}}' def "eventlog" '{"node-id": {"type": "keyword"},"timestamp": {"type": "date"},"new-value": {"type": "keyword"},"attribute-name": {"type": "keyword"},"counter": {"type": "keyword"},"object-id": {"type": "keyword"}}' def "inventoryequipment" '{"date": {"type": "keyword"},"model-identifier": {"type": "keyword"},"manufacturer-identifier": {"type": "keyword"},"type-name": {"type": "keyword"},"description": {"type": "keyword"},"uuid": {"type": "keyword"},"version": {"type": "keyword"},"parent-uuid": {"type": "keyword"},"contained-holder": {"type": "keyword"},"node-id": {"type": "keyword"},"tree-level": {"type": "long"},"part-type-id": {"type": "keyword"},"serial": {"type": "keyword"}}' @@ -88,11 +88,10 @@ print_response() { body=$(echo $response | sed -E 's/HTTPSTATUS\:[0-9]{3}$//') code=$(echo $response | tr -d '\n' | sed -E 's/.*HTTPSTATUS:([0-9]{3})$/\1/') if [ "$VERBOSE" = "0" -a "$code" -ne "200" ] ; then - echo "Error response $code $body" - exit 2 + echo "Error response $code $body" fi if [ "$VERBOSE" -ge 1 ] ; then - echo "response $code" + echo "response $code" fi if [ "$VERBOSE" -ge 2 ] ; then echo "content: $body" @@ -160,6 +159,11 @@ delete_index_alias() { echo "deleting index $index" url="$index" http_delete_request "$url" + + # Delete alias that was falsely autocreated as index + echo "deleting index $index" + url="$alias" + http_delete_request "$url" } # Write mappings @@ -181,9 +185,9 @@ create_index_alias() { fi url=$index - echo "creating index $index" - if [ -z "$3" ] ; then - http_put_request "$url" "$data" + echo "creating index $index" + if [ -z "$3" ] ; then + http_put_request "$url" "$data" else file_append "$url" "$data" fi @@ -192,7 +196,7 @@ create_index_alias() { url="$index/_alias/$alias" echo "creating alias $alias for $index" if [ -z "$3" ] ; then - http_put_request "$url" + http_put_request "$url" else file_append "$url" "{}" fi @@ -210,7 +214,7 @@ es_wait_yellow() { echo "Error: Max attempts reached." exit 3 fi - attempt_counter=$(($attempt_counter+1)) + attempt_counter=$(($attempt_counter+1)) printf '.' sleep 5 done @@ -222,7 +226,7 @@ es_wait_yellow() { echo "Status $ESSTATUS reached: $RES" else echo "Error: DB Reachable, but status $ESSTATUS not reached" - exit 2 + exit 2 fi else echo "Error: $DBURL not reachable" @@ -234,20 +238,23 @@ es_wait_yellow() { cmd_create() { if [ -n "$WAITYELLOW" ] ; then - es_wait_yellow "$WAITYELLOW" + es_wait_yellow "$WAITYELLOW" fi - for i in "${!ALIAS[@]}"; do - create_index_alias "${ALIAS[$i]}" "${MAPPING[$i]}" - done + for i in "${!ALIAS[@]}"; do + create_index_alias "${ALIAS[$i]}" "${MAPPING[$i]}" + done } cmd_delete() { if [ -n "$WAITYELLOW" ] ; then - es_wait_yellow "$WAITYELLOW" + es_wait_yellow "$WAITYELLOW" fi - for i in "${!ALIAS[@]}"; do - delete_index_alias "${ALIAS[$i]}" - done + for i in "${!ALIAS[@]}"; do + delete_index_alias "${ALIAS[$i]}" + done + for i in "${!ALIAS[@]}"; do + delete_index_alias "${ALIAS[$i]}" + done } cmd_purge() { # http_get_request '_cat/aliases' @@ -262,11 +269,11 @@ cmd_purge() { cmd_initfile() { echo "Create script initfile: $INITFILENAME" if [ -f "$INITFILENAME" ] ; then - rm $INITFILENAME + rm $INITFILENAME fi - for i in "${!ALIAS[@]}"; do - create_index_alias "${ALIAS[$i]}" "${MAPPING[$i]}" file - done + for i in "${!ALIAS[@]}"; do + create_index_alias "${ALIAS[$i]}" "${MAPPING[$i]}" file + done } # Prepare database startup @@ -325,22 +332,22 @@ parse_args() { -f|--file) INITFILENAME="$value" shift - ;; - -x|--verbose) - VERBOSE="${value:-0}" - shift - ;; - -v|--version) - VERSION="${value:--v1}" - shift - ;; - -vx|--versionx) - VERSION="" - ;; - -w|--wait) + ;; + -x|--verbose) + VERBOSE="${value:-0}" + shift + ;; + -v|--version) + VERSION="${value:--v1}" + shift + ;; + -vx|--versionx) + VERSION="" + ;; + -w|--wait) WAITYELLOW="${value:-30s}" - shift - ;; + shift + ;; --cmd) STARTUP_CMD="$value" shift @@ -349,10 +356,10 @@ parse_args() { CLUSTER_ENABLED="$value" shift ;; - --index) - NODE_INDEX="$value" - shift - ;; + --index) + NODE_INDEX="$value" + shift + ;; *) ;; esac; @@ -379,32 +386,32 @@ echo " shards=$SHARDS replicas=$REPLICAS prefix=$PREFIX verbose=$VERBOSE versio case "$TASK" in "create") - getsdnrurl - if [ -z "$DBURL" ] ; then - echo "Error: unable to detect database url." - exit 1 - fi + getsdnrurl + if [ -z "$DBURL" ] ; then + echo "Error: unable to detect database url." + exit 1 + fi cmd_create ;; "delete") - getsdnrurl - if [ -z "$DBURL" ] ; then - echo "Error: unable to detect database url." - exit 1 - fi + getsdnrurl + if [ -z "$DBURL" ] ; then + echo "Error: unable to detect database url." + exit 1 + fi cmd_delete ;; "purge") - getsdnrurl - if [ -z "$DBURL" ] ; then - echo "Error: unable to detect database url." - exit 1 - fi + getsdnrurl + if [ -z "$DBURL" ] ; then + echo "Error: unable to detect database url." + exit 1 + fi cmd_purge ;; "initfile") cmd_initfile - ;; + ;; "startup") cmd_startup ;; diff --git a/sdnr/wt/data-provider/database/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/test/TestConfig.java b/sdnr/wt/data-provider/database/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/test/TestConfig.java new file mode 100644 index 000000000..ecc7edf6a --- /dev/null +++ b/sdnr/wt/data-provider/database/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/test/TestConfig.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + ******************************************************************************/ +package org.onap.ccsdk.features.sdnr.wt.dataprovider.database.test; + +import static org.junit.Assert.*; + +import java.io.File; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onap.ccsdk.features.sdnr.wt.common.configuration.ConfigurationFileRepresentation; +import org.onap.ccsdk.features.sdnr.wt.database.config.EsConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TestConfig { + + private static final Logger LOG = LoggerFactory.getLogger(TestConfig.class); + + private static final String TESTFILENAME = "testconfig.properties"; + + @After + @Before + public void afterAndBefore() { + File f=new File(TESTFILENAME); + if(f.exists()) { + LOG.info("Remove {}", f.getAbsolutePath()); + f.delete(); + } + } + @Test + public void test() { + ConfigurationFileRepresentation configuration=new ConfigurationFileRepresentation(TESTFILENAME); + + EsConfig esConfig = new EsConfig(configuration); + LOG.info("Defaultconfiguration: {}", esConfig.toString()); + assertEquals("http", esConfig.getHosts()[0].protocol.getValue()); + assertEquals(9200, esConfig.getHosts()[0].port); + assertEquals("sdnrdb", esConfig.getHosts()[0].hostname); + + } + +} diff --git a/sdnr/wt/data-provider/database/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/test/TestNetconfNodeBuilder.java b/sdnr/wt/data-provider/database/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/test/TestNetconfNodeBuilder.java new file mode 100644 index 000000000..9237aaf1f --- /dev/null +++ b/sdnr/wt/data-provider/database/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/test/TestNetconfNodeBuilder.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + ******************************************************************************/ +package org.onap.ccsdk.features.sdnr.wt.dataprovider.database.test; + +import static org.junit.Assert.*; + +import org.junit.Test; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.Credentials; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPassword; +import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.credentials.LoginPasswordBuilder; + +public class TestNetconfNodeBuilder { + + @SuppressWarnings("deprecation") + //@Test + public void test() { + + NetconfNodeBuilder netconfNodeBuilder = new NetconfNodeBuilder(); + + LoginPasswordBuilder loginPasswordBuilder = new LoginPasswordBuilder(); + loginPasswordBuilder.setUsername("myTestUsername"); + loginPasswordBuilder.setPassword("myTestPassword"); + netconfNodeBuilder.setCredentials(loginPasswordBuilder.build()); + + NetconfNode netconfNode = netconfNodeBuilder.build(); + System.out.println(netconfNode); + + Credentials credentials = netconfNode.getCredentials(); + System.out.println("Class: "+credentials.getClass()+"\nContent: "+credentials); + + if (credentials instanceof LoginPassword) { + LoginPassword loginPassword = (LoginPassword)credentials; + System.out.println("User: "+loginPassword.getUsername()+" Password"+loginPassword.getPassword()); + } else { + System.out.println("Not expected class"); + } + } + +} diff --git a/sdnr/wt/data-provider/database/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/test/TestNuMappings.java b/sdnr/wt/data-provider/database/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/test/TestNuMappings.java new file mode 100644 index 000000000..b7d0620ca --- /dev/null +++ b/sdnr/wt/data-provider/database/src/test/java/org/onap/ccsdk/features/sdnr/wt/dataprovider/database/test/TestNuMappings.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * ============LICENSE_START======================================================================== + * ONAP : ccsdk feature sdnr wt + * ================================================================================================= + * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved. + * ================================================================================================= + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * ============LICENSE_END========================================================================== + ******************************************************************************/ +package org.onap.ccsdk.features.sdnr.wt.dataprovider.database.test; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import java.io.IOException; +import org.junit.Test; +import org.onap.ccsdk.features.sdnr.wt.yangtools.YangToolsMapper; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.data.provider.rev190801.Faultcurrent; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.data.provider.rev190801.FaultcurrentBuilder; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.data.provider.rev190801.SeverityType; + +public class TestNuMappings { + + @Test + public void test33() { + Faultcurrent c = new FaultcurrentBuilder().setSeverity(SeverityType.Critical).build(); + YangToolsMapper mapper = new YangToolsMapper(); + try { + System.out.println(mapper.writeValueAsString(c)+"<=>"+SeverityType.Critical.getName()); + } catch (JsonProcessingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + try { + Faultcurrent f=mapper.readValue( "{\"severity\":\"Critical\"}", Faultcurrent.class); + System.out.println(f); + System.out.println(mapper.writeValueAsString(f)); + } catch (JsonParseException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (JsonMappingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/sdnr/wt/data-provider/database/src/test/resources/log4j2.xml b/sdnr/wt/data-provider/database/src/test/resources/log4j2.xml new file mode 100644 index 000000000..164e93f54 --- /dev/null +++ b/sdnr/wt/data-provider/database/src/test/resources/log4j2.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Configuration package="log4j.test" + status="WARN"> + <Appenders> + <Console name="Console" target="SYSTEM_OUT"> + <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> + </Console> + </Appenders> + <Loggers> + <Logger name="log4j.test.Log4jTest" level="debug"> + <AppenderRef ref="Console"/> + </Logger> + <Root level="trace"> + <AppenderRef ref="Console"/> + </Root> + </Loggers> +</Configuration>
\ No newline at end of file diff --git a/sdnr/wt/data-provider/database/src/test/resources/simplelogger.properties b/sdnr/wt/data-provider/database/src/test/resources/simplelogger.properties new file mode 100644 index 000000000..a2f1e7e76 --- /dev/null +++ b/sdnr/wt/data-provider/database/src/test/resources/simplelogger.properties @@ -0,0 +1,6 @@ +org.slf4j.simpleLogger.defaultLogLevel=debug +org.slf4j.simpleLogger.showDateTime=true +#org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z +#org.slf4j.simpleLogger.showThreadName=true +org.slf4j.simpleLogger.showLogName=true +org.slf4j.simpleLogger.showShortLogName=false
\ No newline at end of file |