Skip to content

Commit

Permalink
#1693 VersionedModelControllerV3: /{name}/{version}/validation impl…
Browse files Browse the repository at this point in the history
… added. IT mostly adjusted, but there are todos

 - DatasetServiceV3 introduced to carry difference in behavior to DatasetService. original entity validation has been divided into create-validation and regular-entity validation.
 - buildfix for VersionedModelServiceTest
  • Loading branch information
dk1844 committed Mar 30, 2022
1 parent 219360a commit 112ac1e
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,16 @@ import org.springframework.http.{HttpStatus, ResponseEntity}
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.web.bind.annotation._
import za.co.absa.enceladus.rest_api.services.DatasetService
import za.co.absa.enceladus.rest_api.services.v3.DatasetServiceV3
import za.co.absa.enceladus.rest_api.utils.implicits._

import java.net.URI
import java.util.concurrent.CompletableFuture
import javax.servlet.http.HttpServletRequest
import scala.concurrent.ExecutionContext.Implicits.global

@RestController
@RequestMapping(path = Array("/api-v3/datasets"))
class DatasetControllerV3 @Autowired()(datasetService: DatasetService)
class DatasetControllerV3 @Autowired()(datasetService: DatasetServiceV3)
extends VersionedModelControllerV3(datasetService) {

@GetMapping(Array("/{name}/{version}/properties"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import org.springframework.web.bind.annotation._
import org.springframework.web.servlet.support.ServletUriComponentsBuilder
import za.co.absa.enceladus.model.menas.audit._
import za.co.absa.enceladus.model.versionedModel._
import za.co.absa.enceladus.model.{ExportableObject, UsedIn}
import za.co.absa.enceladus.rest_api.controllers.{BaseController}
import za.co.absa.enceladus.model.{ExportableObject, UsedIn, Validation}
import za.co.absa.enceladus.rest_api.controllers.BaseController
import za.co.absa.enceladus.rest_api.controllers.v3.VersionedModelControllerV3.LatestVersionKey
import za.co.absa.enceladus.rest_api.services.VersionedModelService

Expand Down Expand Up @@ -113,6 +113,13 @@ abstract class VersionedModelControllerV3[C <: VersionedModel with Product
}
}

@GetMapping(Array("/{name}/{version}/validation"))
@ResponseStatus(HttpStatus.OK)
def validation(@PathVariable name: String,
@PathVariable version: String): CompletableFuture[Validation] = {
forVersionExpression(name, version)(versionedModelService.validate)
}

@PostMapping(Array(""))
@ResponseStatus(HttpStatus.CREATED)
def create(@AuthenticationPrincipal principal: UserDetails,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ class DatasetService @Autowired()(datasetMongoRepository: DatasetMongoRepository
}
}

// CR-related methods:
private def validateConformanceRules(conformanceRules: List[ConformanceRule],
maybeSchema: Future[Option[Schema]]): Future[Validation] = {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import za.co.absa.enceladus.model.menas.audit._

import scala.concurrent.Future
import com.mongodb.MongoWriteException
import VersionedModelService._

abstract class VersionedModelService[C <: VersionedModel with Product with Auditable[C]]
(versionedMongoRepository: VersionedMongoRepository[C]) extends ModelService(versionedMongoRepository) {
Expand Down Expand Up @@ -195,7 +196,10 @@ abstract class VersionedModelService[C <: VersionedModel with Product with Audit

private[rest_api] def create(item: C, username: String): Future[Option[C]] = {
for {
validation <- validate(item)
validation <- for {
generalValidation <- validate(item)
creationValidation <- validateForCreation(item)
} yield generalValidation.merge(creationValidation)
_ <- if (validation.isValid) {
versionedMongoRepository.create(item, username)
.recover {
Expand Down Expand Up @@ -270,30 +274,58 @@ abstract class VersionedModelService[C <: VersionedModel with Product with Audit
versionedMongoRepository.isDisabled(name)
}

/**
* Retrieves model@version and calls
* [[za.co.absa.enceladus.rest_api.services.VersionedModelService#validate(java.lang.Object)]]
*
* In order to extend this behavior, override the mentioned method instead. (that's why this is `final`)
* @param name
* @param version
* @return
*/
final def validate(name: String, version: Int): Future[Validation] = {
getVersion(name, version).flatMap({
case Some(entity) => validate(entity)
case _ => Future.failed(NotFoundException(s"Entity by name=$name, version=$version is not found!"))
})
}

/**
* Provides common validation (currently entity name validation). Override to extend for further specific validations.
* @param item
* @return
*/
def validate(item: C): Future[Validation] = {
validateName(item.name)
}

protected[services] def validateName(name: String): Future[Validation] = {
val validation = Validation()
def validateForCreation(item: C): Future[Validation] = {
isUniqueName(item.name).map { isUnique =>
if (isUnique) {
Validation.empty
} else {
Validation.empty.withError("name", s"entity with name already exists: '${item.name}'")
}
}
}

protected[services] def validateName(name: String): Future[Validation] = {
if (hasWhitespace(name)) {
Future.successful(validation.withError("name", s"name contains whitespace: '$name'"))
Future.successful(Validation.empty.withError("name", s"name contains whitespace: '$name'"))
} else {
isUniqueName(name).map { isUnique =>
if (isUnique) {
validation
} else {
validation.withError("name", s"entity with name already exists: '$name'")
}
}
Future.successful(Validation.empty)
}
}

}

object VersionedModelService {
private[services] def hasWhitespace(name: String): Boolean =
Option(name).exists(definedName => !definedName.matches("""\w+"""))

private[services] def hasValidNameChars(name: String): Boolean =
Option(name).exists(definedName => definedName.matches("""[a-zA-Z0-9._-]+"""))

private[services] def hasValidApiVersion(version: Option[String]): Boolean = version.contains(ModelVersion.toString)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2018 ABSA Group Limited
*
* 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.
*/

package za.co.absa.enceladus.rest_api.services.v3

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import za.co.absa.enceladus.model.{Dataset, Validation}
import za.co.absa.enceladus.rest_api.repositories.{DatasetMongoRepository, OozieRepository}
import za.co.absa.enceladus.rest_api.services.{DatasetService, PropertyDefinitionService}

import scala.concurrent.Future


// this DatasetService is a V3 difference wrapper - once V2 is removed, implementations can/should be merged
@Service
class DatasetServiceV3 @Autowired()(datasetMongoRepository: DatasetMongoRepository,
oozieRepository: OozieRepository,
datasetPropertyDefinitionService: PropertyDefinitionService)
extends DatasetService(datasetMongoRepository, oozieRepository, datasetPropertyDefinitionService) {

import scala.concurrent.ExecutionContext.Implicits.global

// general entity validation is extendable for V3 - here with properties validation
override def validate(item: Dataset): Future[Validation] = {
// todo check schema presence same way as for import

for {
originalValidation <- super.validate(item)
dsSpecificValidation <- validateProperties(item.propertiesAsMap)
} yield originalValidation.merge(dsSpecificValidation)
}


}


Loading

0 comments on commit 112ac1e

Please sign in to comment.