summaryrefslogtreecommitdiffstats
path: root/cps-service
diff options
context:
space:
mode:
Diffstat (limited to 'cps-service')
-rw-r--r--cps-service/pom.xml68
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/CpService.java79
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/impl/CpServiceImpl.java100
-rw-r--r--cps-service/src/main/java/org/onap/cps/exceptions/CpsException.java62
-rw-r--r--cps-service/src/main/java/org/onap/cps/exceptions/CpsNotFoundException.java57
-rw-r--r--cps-service/src/main/java/org/onap/cps/exceptions/CpsValidationException.java55
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/DataPersistencyService.java49
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/ModelPersistencyService.java39
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/YangUtils.java99
-rw-r--r--cps-service/src/main/resources/logback-spring.xml48
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpServiceImplSpec.groovy116
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy80
-rw-r--r--cps-service/src/test/java/org/onap/cps/TestUtils.java41
-rw-r--r--cps-service/src/test/resources/bookstore.json34
-rw-r--r--cps-service/src/test/resources/bookstore.yang50
-rw-r--r--cps-service/src/test/resources/invalid.yang1
-rw-r--r--cps-service/src/test/resources/someOtherFile.txt0
17 files changed, 978 insertions, 0 deletions
diff --git a/cps-service/pom.xml b/cps-service/pom.xml
new file mode 100644
index 0000000000..3e8cc2debd
--- /dev/null
+++ b/cps-service/pom.xml
@@ -0,0 +1,68 @@
+<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.onap.cps</groupId>
+ <artifactId>cps-parent</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ <relativePath>../cps-parent/pom.xml</relativePath>
+ </parent>
+
+ <artifactId>cps-service</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-parser-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-parser-impl</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-model-util</artifactId>
+ </dependency>
+ <!-- required for processing yang data in json format -->
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-data-codec-gson</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ </dependency>
+ <dependency>
+ <!-- For logging -->
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <!-- For dependency injection -->
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context</artifactId>
+ </dependency>
+ <dependency>
+ <!-- For parsing JSON object -->
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ </dependency>
+ <!-- T E S T D E P E N D E N C I E S -->
+ <dependency>
+ <groupId>org.codehaus.groovy</groupId>
+ <artifactId>groovy</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.spockframework</groupId>
+ <artifactId>spock-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>cglib</groupId>
+ <artifactId>cglib-nodep</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/cps-service/src/main/java/org/onap/cps/api/CpService.java b/cps-service/src/main/java/org/onap/cps/api/CpService.java
new file mode 100644
index 0000000000..4d94a46547
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/api/CpService.java
@@ -0,0 +1,79 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2020 Nordix Foundation
+ * Modifications Copyright (C) 2020 Bell Canada. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.api;
+
+import java.io.File;
+import java.io.IOException;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.parser.api.YangParserException;
+
+/**
+ * Configuration and persistency service interface which holds methods for parsing and storing yang models and data.
+ */
+public interface CpService {
+
+ /**
+ * Parse and validate a string representing a yang model to generate a schema context.
+ *
+ * @param yangModelContent the input stream
+ * @return the schema context
+ */
+ SchemaContext parseAndValidateModel(final String yangModelContent);
+
+ /**
+ * Parse and validate a file representing a yang model to generate a schema context.
+ *
+ * @param yangModelFile the yang file
+ * @return the schema context
+ */
+ SchemaContext parseAndValidateModel(final File yangModelFile);
+
+ /**
+ * Store schema context for a yang model.
+ *
+ * @param schemaContext the schema context
+ * @param dataspaceName the dataspace name
+ */
+ void storeSchemaContext(final SchemaContext schemaContext, final String dataspaceName);
+
+ /**
+ * Store the JSON structure in the database.
+ *
+ * @param jsonStructure the JSON structure.
+ * @return entity ID.
+ */
+ Integer storeJsonStructure(final String jsonStructure);
+
+ /**
+ * Read a JSON Object using the object identifier.
+ *
+ * @param jsonObjectId the JSON object identifier.
+ * @return the JSON structure.
+ */
+ String getJsonById(final int jsonObjectId);
+
+ /**
+ * Delete a JSON Object using the object identifier.
+ *
+ * @param jsonObjectId the JSON object identifier.
+ */
+ void deleteJsonById(final int jsonObjectId);
+}
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpServiceImpl.java
new file mode 100644
index 0000000000..c33746e861
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpServiceImpl.java
@@ -0,0 +1,100 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2020 Nordix Foundation
+ * Modifications Copyright (C) 2020 Bell Canada. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.api.impl;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Optional;
+import org.onap.cps.api.CpService;
+import org.onap.cps.exceptions.CpsException;
+import org.onap.cps.exceptions.CpsValidationException;
+import org.onap.cps.spi.DataPersistencyService;
+import org.onap.cps.spi.ModelPersistencyService;
+import org.onap.cps.utils.YangUtils;
+import org.opendaylight.yangtools.yang.common.Revision;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.parser.api.YangParserException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+
+@Component
+public class CpServiceImpl implements CpService {
+
+ @Autowired
+ private ModelPersistencyService modelPersistencyService;
+
+ @Autowired
+ private DataPersistencyService dataPersistencyService;
+
+ @Override
+ public final SchemaContext parseAndValidateModel(final String yangModelContent) {
+
+ try {
+ final File tempFile = File.createTempFile("yang", ".yang");
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile))) {
+ writer.write(yangModelContent);
+ }
+ return parseAndValidateModel(tempFile);
+ } catch (IOException e) {
+ throw new CpsException(e);
+ }
+ }
+
+ @Override
+ public final SchemaContext parseAndValidateModel(final File yangModelFile) {
+ try {
+ return YangUtils.parseYangModelFile(yangModelFile);
+ } catch (YangParserException e) {
+ throw new CpsValidationException("Yang file validation failed", e.getMessage());
+ } catch (IOException e) {
+ throw new CpsException(e);
+ }
+ }
+
+ @Override
+ public final Integer storeJsonStructure(final String jsonStructure) {
+ return dataPersistencyService.storeJsonStructure(jsonStructure);
+ }
+
+ @Override
+ public final String getJsonById(final int jsonObjectId) {
+ return dataPersistencyService.getJsonById(jsonObjectId);
+ }
+
+ @Override
+ public void deleteJsonById(int jsonObjectId) {
+ dataPersistencyService.deleteJsonById(jsonObjectId);
+ }
+
+ @Override
+ public final void storeSchemaContext(final SchemaContext schemaContext, final String dataspaceName) {
+ for (final Module module : schemaContext.getModules()) {
+ Optional<Revision> optionalRevision = module.getRevision();
+ String revisionValue = optionalRevision.isPresent() ? optionalRevision.get().toString() : null;
+ modelPersistencyService.storeModule(module.getNamespace().toString(), module.toString(),
+ revisionValue, dataspaceName);
+ }
+ }
+}
diff --git a/cps-service/src/main/java/org/onap/cps/exceptions/CpsException.java b/cps-service/src/main/java/org/onap/cps/exceptions/CpsException.java
new file mode 100644
index 0000000000..b54453cd90
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/exceptions/CpsException.java
@@ -0,0 +1,62 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2020 Pantheon.tech
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.exceptions;
+
+import lombok.Getter;
+import org.springframework.core.NestedExceptionUtils;
+
+/**
+ * CP Service exception.
+ */
+public class CpsException extends RuntimeException {
+
+ @Getter
+ String details;
+
+ /**
+ * Constructor.
+ *
+ * @param cause the cause of the exception
+ */
+ public CpsException(Throwable cause) {
+ super(cause.getMessage(), cause);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message the error message
+ * @param cause the cause of the exception
+ */
+ public CpsException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message the error message
+ * @param details the error details
+ */
+ public CpsException(String message, String details) {
+ super(message);
+ this.details = details;
+ }
+}
diff --git a/cps-service/src/main/java/org/onap/cps/exceptions/CpsNotFoundException.java b/cps-service/src/main/java/org/onap/cps/exceptions/CpsNotFoundException.java
new file mode 100644
index 0000000000..f44fe806cf
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/exceptions/CpsNotFoundException.java
@@ -0,0 +1,57 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2020 Pantheon.tech
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.exceptions;
+
+import lombok.Getter;
+
+/**
+ * CP Service exception. Indicates the requested data being absent.
+ */
+public class CpsNotFoundException extends CpsException {
+
+ /**
+ * Constructor.
+ *
+ * @param cause the cause of the exception
+ */
+ public CpsNotFoundException(Throwable cause) {
+ super(cause.getMessage(), cause);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message the error message
+ * @param cause the cause of the exception
+ */
+ public CpsNotFoundException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message the error message
+ * @param details the error details
+ */
+ public CpsNotFoundException(String message, String details) {
+ super(message, details);
+ }
+}
diff --git a/cps-service/src/main/java/org/onap/cps/exceptions/CpsValidationException.java b/cps-service/src/main/java/org/onap/cps/exceptions/CpsValidationException.java
new file mode 100644
index 0000000000..dba9c165b8
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/exceptions/CpsValidationException.java
@@ -0,0 +1,55 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2020 Pantheon.tech
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.exceptions;
+
+/**
+ * CP Service exception. Indicates the parameter validation failure.
+ */
+public class CpsValidationException extends CpsException {
+
+ /**
+ * Constructor.
+ *
+ * @param cause the cause of the exception
+ */
+ public CpsValidationException(Throwable cause) {
+ super(cause.getMessage(), cause);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message the error message
+ * @param cause the cause of the exception
+ */
+ public CpsValidationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param message the error message
+ * @param details the error details
+ */
+ public CpsValidationException(String message, String details) {
+ super(message, details);
+ }
+}
diff --git a/cps-service/src/main/java/org/onap/cps/spi/DataPersistencyService.java b/cps-service/src/main/java/org/onap/cps/spi/DataPersistencyService.java
new file mode 100644
index 0000000000..ce51f40ac6
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/spi/DataPersistencyService.java
@@ -0,0 +1,49 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2020 Nordix Foundation
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.spi;
+
+/**
+ * Defines methods to access and manipulate data using the chosen database solution.
+ */
+public interface DataPersistencyService {
+
+ /**
+ * Store the JSON structure in the database.
+ *
+ * @param jsonStructure the JSON structure.
+ * @return jsonEntityID the ID of the JSON entity.
+ */
+ Integer storeJsonStructure(final String jsonStructure);
+
+ /**
+ * Get the JSON structure from the database using the entity identifier.
+ *
+ * @param jsonStructureId the json entity identifier.
+ * @return a JSON Structure.
+ */
+ String getJsonById(int jsonStructureId);
+
+ /**
+ * Delete the JSON structure from the database using the entity identifier.
+ *
+ * @param jsonStructureId the json entity identifier.
+ */
+ void deleteJsonById(int jsonStructureId);
+} \ No newline at end of file
diff --git a/cps-service/src/main/java/org/onap/cps/spi/ModelPersistencyService.java b/cps-service/src/main/java/org/onap/cps/spi/ModelPersistencyService.java
new file mode 100644
index 0000000000..2afdaa82a8
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/spi/ModelPersistencyService.java
@@ -0,0 +1,39 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2020 Nordix Foundation
+ * Modifications Copyright (C) 2020 Bell Canada. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.spi;
+
+/**
+ * Defines methods to access and manipulate data using the chosen database solution.
+ */
+public interface ModelPersistencyService {
+
+ /**
+ * Store the module from a yang model in the database.
+ *
+ * @param namespace module namespace
+ * @param moduleContent module content
+ * @param revision module revision
+ * @param dataspaceName the name of the dataspace the module is associated with
+ */
+ void storeModule(final String namespace, final String moduleContent, final String revision,
+ final String dataspaceName);
+
+}
diff --git a/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java b/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java
new file mode 100644
index 0000000000..e9757eca0d
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/utils/YangUtils.java
@@ -0,0 +1,99 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2020 Nordix Foundation
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.utils;
+
+import com.google.gson.stream.JsonReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Iterator;
+import java.util.ServiceLoader;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactory;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonParserStream;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.parser.api.YangParser;
+import org.opendaylight.yangtools.yang.model.parser.api.YangParserException;
+import org.opendaylight.yangtools.yang.model.parser.api.YangParserFactory;
+import org.opendaylight.yangtools.yang.model.repo.api.StatementParserMode;
+import org.opendaylight.yangtools.yang.model.repo.api.YangTextSchemaSource;
+
+public class YangUtils {
+
+ private static final YangParserFactory PARSER_FACTORY;
+
+ private YangUtils() {
+ throw new IllegalStateException("Utility class");
+ }
+
+ static {
+ final Iterator<YangParserFactory> it = ServiceLoader.load(YangParserFactory.class).iterator();
+ if (!it.hasNext()) {
+ throw new IllegalStateException("No YangParserFactory found");
+ }
+ PARSER_FACTORY = it.next();
+ }
+
+ /**
+ * Parse a file containing yang modules.
+ * @param yangModelFile a file containing one or more yang modules
+ * (please note the file has to have a .yang extension if not an exception will be thrown)
+ * @return a SchemaContext representing the yang model
+ * @throws IOException when the system as an IO issue
+ * @throws YangParserException when the file does not contain a valid yang structure
+ */
+ public static SchemaContext parseYangModelFile(final File yangModelFile) throws IOException, YangParserException {
+ YangTextSchemaSource yangTextSchemaSource = YangTextSchemaSource.forFile(yangModelFile);
+ final YangParser yangParser = PARSER_FACTORY
+ .createParser(StatementParserMode.DEFAULT_MODE);
+ yangParser.addSource(yangTextSchemaSource);
+ return yangParser.buildEffectiveModel();
+ }
+
+ /**
+ * Parse a file containing json data for a certain model (schemaContext).
+ * @param jsonData a string containing json data for the given model
+ * @param schemaContext the SchemaContext for the given data
+ * @return the NormalizedNode representing the json data
+ */
+ public static NormalizedNode<?, ?> parseJsonData(final String jsonData, final SchemaContext schemaContext)
+ throws IOException {
+ JSONCodecFactory jsonCodecFactory = JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02
+ .getShared(schemaContext);
+ final NormalizedNodeResult normalizedNodeResult = new NormalizedNodeResult();
+ final NormalizedNodeStreamWriter normalizedNodeStreamWriter = ImmutableNormalizedNodeStreamWriter
+ .from(normalizedNodeResult);
+ try (JsonParserStream jsonParserStream = JsonParserStream
+ .create(normalizedNodeStreamWriter, jsonCodecFactory)) {
+ final JsonReader jsonReader = new JsonReader(new StringReader(jsonData));
+ jsonParserStream.parse(jsonReader);
+ }
+ return normalizedNodeResult.getResult();
+ }
+
+ public static void chopNormalizedNode(NormalizedNode<?, ?> tree) {
+ //TODO Toine Siebelink, add code from proto-type (other user story)
+ }
+
+}
diff --git a/cps-service/src/main/resources/logback-spring.xml b/cps-service/src/main/resources/logback-spring.xml
new file mode 100644
index 0000000000..8a4ae07c5b
--- /dev/null
+++ b/cps-service/src/main/resources/logback-spring.xml
@@ -0,0 +1,48 @@
+<configuration scan="true" debug="false">
+ <include resource="org/springframework/boot/logging/logback/base.xml" />
+
+ <property name="queueSize" value="256" />
+ <property name="maxFileSize" value="20MB" />
+ <property name="maxHistory" value="30" />
+ <property name="totalSizeCap" value="20MB" />
+
+ <!-- log file names -->
+ <property name="logName" value="cps" />
+
+ <property name="currentTimeStamp" value="%d{&quot;yyyy-MM-dd'T'HH:mm:ss.SSSXXX&quot;,UTC}"/>
+
+ <property name="debugPattern"
+ value="%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX}|%thread|%X{RequestID}| %logger{50} - %msg%n" />
+
+ <appender name="Debug"
+ class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <file>../log/${logName}.log</file>
+ <rollingPolicy
+ class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+ <fileNamePattern>${logName}.%d{yyyy-MM-dd}.%i.log.zip
+ </fileNamePattern>
+ <maxFileSize>${maxFileSize}</maxFileSize>
+ <maxHistory>${maxHistory}</maxHistory>
+ <totalSizeCap>${totalSizeCap}</totalSizeCap>
+ </rollingPolicy>
+ <encoder>
+ <pattern>${debugPattern}</pattern>
+ </encoder>
+ </appender>
+
+ <appender name="asyncDebug" class="ch.qos.logback.classic.AsyncAppender">
+ <queueSize>256</queueSize>
+ <appender-ref ref="Debug" />
+ <includeCallerData>true</includeCallerData>
+ </appender>
+
+ <logger name="org.onap.cps" level="DEBUG" additivity="false">
+ <appender-ref ref="asyncDebug" />
+ </logger>
+
+
+ <root level="INFO">
+ <appender-ref ref="asyncDebug" />
+ </root>
+
+</configuration> \ No newline at end of file
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpServiceImplSpec.groovy
new file mode 100644
index 0000000000..5f42810bd5
--- /dev/null
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpServiceImplSpec.groovy
@@ -0,0 +1,116 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2020 Nordix Foundation
+ * Modifications Copyright (C) 2020 Bell Canada. 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.api.impl
+
+import org.onap.cps.TestUtils
+import org.onap.cps.exceptions.CpsValidationException
+import org.onap.cps.spi.DataPersistencyService
+import org.opendaylight.yangtools.yang.common.Revision
+import org.opendaylight.yangtools.yang.model.api.SchemaContext
+import spock.lang.Specification
+
+class CpServiceImplSpec extends Specification {
+
+ def mockDataPersistencyService = Mock(DataPersistencyService)
+ def objectUnderTest = new CpServiceImpl()
+
+ def setup() {
+ objectUnderTest.dataPersistencyService = mockDataPersistencyService
+ }
+
+ def 'Cps Service provides to its client the id assigned by the system when storing a data structure'() {
+ given: 'that data persistency service is giving id 123 to a data structure it is asked to store'
+ mockDataPersistencyService.storeJsonStructure(_) >> 123
+ expect: 'Cps service returns the same id when storing data structure'
+ objectUnderTest.storeJsonStructure('') == 123
+ }
+
+ def 'Parse and Validate a Yang Model with a Valid Yang Model'() {
+ given: 'a yang model (file)'
+ def yangModel = TestUtils.getResourceFileContent('bookstore.yang')
+ when: 'a valid model is parsed and validated'
+ def result = objectUnderTest.parseAndValidateModel(yangModel)
+ then: 'Verify a schema context for that model is created with the correct identity'
+ assertModule(result)
+ }
+
+ def 'Parse and Validate a Yang Model Using a File'() {
+ given: 'a yang file that contains a yang model'
+ File file = new File(ClassLoader.getSystemClassLoader().getResource('bookstore.yang').getFile())
+ when: 'a model is parsed and validated'
+ def result = objectUnderTest.parseAndValidateModel(file)
+ then: 'Verify a schema context for that model is created with the correct identity'
+ assertModule(result)
+
+ }
+
+ def assertModule(SchemaContext schemaContext){
+ def optionalModule = schemaContext.findModule('bookstore', Revision.of('2020-09-15'))
+ return schemaContext.modules.size() == 1 && optionalModule.isPresent()
+ }
+
+ def 'Parse and Validate an Invalid Model'() {
+ given: 'a yang file that contains a invalid yang model'
+ File file = new File(ClassLoader.getSystemClassLoader().getResource('invalid.yang').getFile())
+ when: 'the model is parsed and validated'
+ objectUnderTest.parseAndValidateModel(file)
+ then: 'a CpsValidationException is thrown'
+ thrown(CpsValidationException)
+ }
+
+ def 'Store a SchemaContext'() {
+ expect: 'No exception to be thrown when a valid model (schema) is stored'
+ objectUnderTest.storeSchemaContext(Stub(SchemaContext.class), "sampleDataspace")
+ }
+
+ def 'Read a JSON object with a valid identifier'(){
+ given: 'that the data persistence service returns a JSON structure for identifier 1'
+ mockDataPersistencyService.getJsonById(1) >> '{name : hello}'
+ expect: 'that the same JSON structure is returned by CPS'
+ objectUnderTest.getJsonById(1) == '{name : hello}'
+ }
+
+ def 'Read a JSON object with an identifier that does not exist'(){
+ given: 'that the data persistence service throws an exception'
+ def exceptionThrownByPersistenceService = new IllegalStateException()
+ mockDataPersistencyService.getJsonById(_) >> {throw exceptionThrownByPersistenceService}
+ when: 'we try to get the JSON structure'
+ objectUnderTest.getJsonById(1);
+ then: 'the same exception is thrown by CPS'
+ thrown(IllegalStateException)
+ }
+
+ def 'Delete a JSON object with a valid identifier'(){
+ given: 'that the data persistence service can delete a JSON structure for identifier 1'
+ mockDataPersistencyService.deleteJsonById(1)
+ expect: 'No exception is thrown when we delete a JSON structure with identifier 1'
+ objectUnderTest.deleteJsonById(1)
+ }
+
+ def 'Delete a JSON object with an identifier that does not exist'(){
+ given: 'that the data persistence service throws an exception'
+ mockDataPersistencyService.deleteJsonById(_) >> {throw new IllegalStateException()}
+ when: 'we try to delete a JSON structure'
+ objectUnderTest.deleteJsonById(100);
+ then: 'the same exception is thrown by CPS'
+ thrown(IllegalStateException)
+ }
+} \ No newline at end of file
diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy
new file mode 100644
index 0000000000..8aabc48448
--- /dev/null
+++ b/cps-service/src/test/groovy/org/onap/cps/utils/YangUtilsSpec.groovy
@@ -0,0 +1,80 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2020 Nordix Foundation
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.utils
+
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode
+import org.opendaylight.yangtools.yang.common.QName
+import org.opendaylight.yangtools.yang.common.Revision
+import org.opendaylight.yangtools.yang.model.parser.api.YangSyntaxErrorException
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class YangUtilsSpec extends Specification{
+ def 'Parsing a valid Yang Model'() {
+ given: 'a yang model (file)'
+ def file = new File(ClassLoader.getSystemClassLoader().getResource('bookstore.yang').getFile())
+ when: 'the file is parsed'
+ def result = YangUtils.parseYangModelFile(file)
+ then: 'the result contain 1 module of the correct name and revision'
+ result.modules.size() == 1
+ def optionalModule = result.findModule('bookstore', Revision.of('2020-09-15'))
+ optionalModule.isPresent()
+ }
+
+ @Unroll
+ def 'parsing invalid yang file (#description)'() {
+ given: 'a file with #description'
+ File file = new File(ClassLoader.getSystemClassLoader().getResource(filename).getFile());
+ when: 'the file is parsed'
+ YangUtils.parseYangModelFile(file)
+ then: 'an exception is thrown'
+ thrown(expectedException)
+ where: 'the following parameters are used'
+ filename | description || expectedException
+ 'invalid.yang' | 'no valid content' || YangSyntaxErrorException
+ 'someOtherFile.txt' | 'no .yang extension' || IllegalArgumentException
+ }
+
+ def 'Parsing a valid Json String'() {
+ given: 'a yang model (file)'
+ def jsonData = org.onap.cps.TestUtils.getResourceFileContent('bookstore.json')
+ and: 'a model for that data'
+ def file = new File(ClassLoader.getSystemClassLoader().getResource('bookstore.yang').getFile())
+ def schemaContext = YangUtils.parseYangModelFile(file)
+ when: 'the json data is parsed'
+ NormalizedNode<?, ?> result = YangUtils.parseJsonData(jsonData, schemaContext);
+ then: 'the result is a normalized node of the correct type'
+ result.nodeType == QName.create('org:onap:ccsdk:sample','2020-09-15','bookstore')
+ }
+
+ def 'Parsing an invalid Json String'() {
+ given: 'a yang model (file)'
+ def jsonData = '{incomplete json'
+ and: 'a model'
+ def file = new File(ClassLoader.getSystemClassLoader().getResource('bookstore.yang').getFile())
+ def schemaContext = YangUtils.parseYangModelFile(file)
+ when: 'the invalid json is parsed'
+ YangUtils.parseJsonData(jsonData, schemaContext);
+ then: ' an exception is thrown'
+ thrown(IllegalStateException)
+ }
+
+
+}
diff --git a/cps-service/src/test/java/org/onap/cps/TestUtils.java b/cps-service/src/test/java/org/onap/cps/TestUtils.java
new file mode 100644
index 0000000000..07647520b0
--- /dev/null
+++ b/cps-service/src/test/java/org/onap/cps/TestUtils.java
@@ -0,0 +1,41 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2020 Nordix Foundation
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+/**
+ * Common convenience methods for testing.
+ */
+public class TestUtils {
+ /**
+ * Convert a file in the test resource folder to a string.
+ *
+ * @param filename to name of the file in test/resources
+ * @return the content of the file as a String
+ * @throws IOException when there is an IO issue
+ */
+ public static String getResourceFileContent(final String filename) throws IOException {
+ File file = new File(ClassLoader.getSystemClassLoader().getResource(filename).getFile());
+ return new String(Files.readAllBytes(file.toPath()));
+ }
+}
diff --git a/cps-service/src/test/resources/bookstore.json b/cps-service/src/test/resources/bookstore.json
new file mode 100644
index 0000000000..44d5d424cd
--- /dev/null
+++ b/cps-service/src/test/resources/bookstore.json
@@ -0,0 +1,34 @@
+{
+ "test:bookstore":{
+ "categories":[
+ {
+ "name":"web",
+ "books":[
+ {
+ "authors":[
+ "Toine Siebelink","David Lang"
+ ],
+ "lang":"en",
+ "price":"123456",
+ "pub_year":"2020",
+ "title":"My first book"
+ }
+ ]
+ },
+ {
+ "name":"art",
+ "books":[
+ {
+ "authors":[
+ "Test"
+ ],
+ "lang":"en",
+ "price":"1234",
+ "pub_year":"2020",
+ "title":"My 2nd book"
+ }
+ ]
+ }
+ ]
+ }
+} \ No newline at end of file
diff --git a/cps-service/src/test/resources/bookstore.yang b/cps-service/src/test/resources/bookstore.yang
new file mode 100644
index 0000000000..01eac5f339
--- /dev/null
+++ b/cps-service/src/test/resources/bookstore.yang
@@ -0,0 +1,50 @@
+module bookstore {
+ yang-version 1.1;
+
+ namespace "org:onap:ccsdk:sample";
+
+ prefix book-store;
+
+ revision "2020-09-15" {
+ description
+ "Sample Model";
+ }
+
+ typedef year {
+ type uint16 {
+ range "1000..9999";
+ }
+ }
+
+ container bookstore {
+
+ list categories {
+
+ key name;
+
+ leaf name {
+ type string;
+ }
+
+ list books {
+ key title;
+
+ leaf title {
+ type string;
+ }
+ leaf lang {
+ type string;
+ }
+ leaf-list authors {
+ type string;
+ }
+ leaf pub_year {
+ type year;
+ }
+ leaf price {
+ type uint64;
+ }
+ }
+ }
+ }
+}
diff --git a/cps-service/src/test/resources/invalid.yang b/cps-service/src/test/resources/invalid.yang
new file mode 100644
index 0000000000..66cfd10796
--- /dev/null
+++ b/cps-service/src/test/resources/invalid.yang
@@ -0,0 +1 @@
+no yang at all!
diff --git a/cps-service/src/test/resources/someOtherFile.txt b/cps-service/src/test/resources/someOtherFile.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/cps-service/src/test/resources/someOtherFile.txt