Skip to content

Commit

Permalink
Merge pull request griddynamics#874 from rsvato/databag-templates
Browse files Browse the repository at this point in the history
[RJA-1953][feature] - Modify domain objects to support template links
  • Loading branch information
bugzmanov committed Mar 11, 2013
2 parents cbb36f3 + fd246a1 commit 67959a7
Show file tree
Hide file tree
Showing 17 changed files with 205 additions and 46 deletions.
20 changes: 16 additions & 4 deletions api/src/main/scala/com/griddynamics/genesis/api/Model.scala
Expand Up @@ -28,7 +28,6 @@ import java.sql.Timestamp
import util.{TimeZone, Locale}
import java.text.DateFormat


trait Identifiable[T] {
def id: T
}
Expand All @@ -44,7 +43,10 @@ case class Configuration( id: Option[Int],
description: Option[String],
@ValidStringMap(key_min = 1, key_max = 256, value_min = 0, value_max = 256)
items: Map[String, String] = Map(),
instanceCount: Option[Int] = None) extends Identifiable[Option[Int]] with ProjectBound
instanceCount: Option[Int] = None,
override val templateId: Option[String] = None) extends Identifiable[Option[Int]] with ProjectBound with TemplateBased {
def itemsMap = items
}

case class Environment(id: Int,
name : String,
Expand Down Expand Up @@ -227,7 +229,12 @@ case class DataBag( id: Option[Int],
@Size(min = 1, max = 128) @NotBlank name: String,
tags: Seq[String],
projectId: Option[Int] = None,
@ValidSeq items: Seq[DataItem] = Seq() )
templateId: Option[String] = None,
@ValidSeq items: Seq[DataItem] = Seq() ) extends TemplateBased {
def itemsMap = {
items.map(item => (item.name, item.value)).toMap
}
}

case class Plugin(id: String, description: Option[String])

Expand Down Expand Up @@ -354,4 +361,9 @@ object CancelAction extends Action("cancel")
object ResetAction extends Action("reset")

case class DatabagTemplate(id: String, name: String, defaultName: Option[String],
tags: Seq[String], properties: Seq[DataItem])
tags: Seq[String], properties: Seq[DataItem])

trait TemplateBased {
def templateId: Option[String]
def itemsMap: Map[String, String]
}
Expand Up @@ -12,6 +12,7 @@ class DatabagTemplateServiceContextImpl extends DatabagTemplateServiceContext {
@Value("${genesis.databag.template.repository.path:templates}") var templatePath: String = _
@Value("${genesis.databag.template.repository.wildcard:*.dbtemplate}") var wildCard: String = _

@Bean
def databagTemplateRepository: DatabagTemplateRepository = new DatabagTemplateRepositoryImpl(templatePath, wildCard)
@Bean
def databagTemplateService: DatabagTemplateService = new DatabagTemplateServiceImpl(databagTemplateRepository)
Expand Down
Expand Up @@ -31,7 +31,7 @@ import org.springframework.jdbc.datasource.{DataSourceUtils, DataSourceTransacti
import org.squeryl.adapters.{PostgreSqlAdapter, MySQLAdapter, H2Adapter}
import com.griddynamics.genesis.repository
import com.griddynamics.genesis.service
import repository.{GenesisVersionRepository, SchemaCreator}
import repository.{DatabagTemplateRepository, GenesisVersionRepository, SchemaCreator}
import org.springframework.transaction.support.TransactionTemplate
import org.springframework.transaction.PlatformTransactionManager
import com.griddynamics.genesis.adapters.MSSQLServerWithPagination
Expand All @@ -41,6 +41,9 @@ import org.springframework.beans.factory.InitializingBean
import com.griddynamics.genesis.util.Logging
import org.springframework.beans.factory.annotation.Autowired
import java.sql.SQLException
import com.griddynamics.genesis.validation.ConfigValueValidator
import collection.JavaConversions._
import scala.Some

@Configuration
class JdbcStoreServiceContext extends StoreServiceContext {
Expand All @@ -49,6 +52,10 @@ class JdbcStoreServiceContext extends StoreServiceContext {

@Autowired var permissionService: PermissionService = _

@Autowired var databagTemplateRepository: DatabagTemplateRepository = _

@Autowired(required = false) private var validators: java.util.Map[String, ConfigValueValidator] = mapAsJavaMap(Map())

@Bean def environmentSecurity: service.EnvironmentAccessService = new impl.EnvironmentAccessService(storeService, permissionService)

@Bean def storeService: service.StoreService = new impl.StoreService
Expand All @@ -59,19 +66,14 @@ class JdbcStoreServiceContext extends StoreServiceContext {

@Bean def credentialsRepository: repository.CredentialsRepository = new repository.impl.CredentialsRepository
@Bean def configurationRepository: repository.ConfigurationRepository = new repository.impl.ConfigurationRepositoryImpl
@Bean def environmentService: EnvironmentConfigurationService = new EnvironmentConfigurationServiceImpl(configurationRepository, environmentSecurity)

@Bean def environmentService: EnvironmentConfigurationService = new EnvironmentConfigurationServiceImpl(configurationRepository, environmentSecurity, databagTemplateRepository, validators.toMap)
@Bean def credentialsStoreService: service.CredentialsStoreService = new impl.CredentialsStoreService(credentialsRepository, projectRepository)

@Bean def serversArrayRepository: repository.ServerArrayRepository = new repository.impl.ServerArrayRepository()
@Bean def serversRepository: repository.ServerRepository = new repository.impl.ServerRepository()

@Bean def serversService: service.ServersService = new ServersServiceImpl(serversArrayRepository, serversRepository)
@Bean def serversLoanService: service.ServersLoanService = new ServersLoanServiceImpl(storeService, credentialsRepository)

@Bean def databagRepository: repository.DatabagRepository = new repository.impl.DatabagRepository

@Bean def databagService: service.DataBagService = new impl.DataBagServiceImpl(databagRepository)
@Bean def databagService: service.DataBagService = new impl.DataBagServiceImpl(databagRepository, databagTemplateRepository, validators.toMap)

}

Expand Down
Expand Up @@ -40,7 +40,7 @@ class DatabagRepository extends AbstractGenericRepository[model.DataBag, api.Dat

implicit def convert(entity: model.DataBag) = {
val tags: Seq[String] = if (entity.tags.trim.isEmpty) List() else List(entity.tags.trim.toLowerCase.split(" "): _*)
new api.DataBag(fromModelId(entity.id), entity.name, tags, entity.projectId)
new api.DataBag(fromModelId(entity.id), entity.name, tags, entity.projectId, entity.templateId)
}

implicit def convert(dto: DataBag) = {
Expand Down
@@ -1,6 +1,6 @@
package com.griddynamics.genesis.repository.impl

import com.griddynamics.genesis.model.{ValueMetadata, DatabagTemplate}
import com.griddynamics.genesis.model.{TemplateProperty, ValueMetadata, DatabagTemplate}
import com.typesafe.config.{ConfigFactory, ConfigException, Config}
import scala.util.control.Exception._
import com.griddynamics.genesis.repository.DatabagTemplateRepository
Expand Down Expand Up @@ -42,11 +42,11 @@ object DatabagTemplateReader {
Option(getString(defaultKey)), getString(scopeKey, ""), tags, readProperties(c.getConfig(databagKey)))
}

private def readProperties(config: Config): Map[String, ValueMetadata] = {
private def readProperties(config: Config): Map[String, TemplateProperty] = {
import collection.JavaConversions._
config.root().keysIterator.map(key => (key -> {
val value: Config = config.getConfig(key)
new ValueMetadata(value)
new TemplateProperty(value)
})).toMap
}
}
Expand Up @@ -2,12 +2,21 @@ package com.griddynamics.genesis.service.impl

import com.griddynamics.genesis.service.DataBagService
import org.springframework.transaction.annotation.Transactional
import com.griddynamics.genesis.repository.DatabagRepository
import com.griddynamics.genesis.validation.Validation
import com.griddynamics.genesis.repository.{DatabagTemplateRepository, DatabagRepository}
import com.griddynamics.genesis.validation.{ConfigValueValidator, Validation}
import com.griddynamics.genesis.validation.Validation._
import com.griddynamics.genesis.api.{ExtendedResult, DataBag, Failure, Success}
import com.griddynamics.genesis.api._
import scala.Some
import com.griddynamics.genesis.model.{TemplateProperty, DatabagTemplate}
import com.griddynamics.genesis.api.DataBag
import com.griddynamics.genesis.api.Success
import com.griddynamics.genesis.api.Failure
import scala.Some

class DataBagServiceImpl(repository: DatabagRepository) extends DataBagService with Validation[DataBag] {
class DataBagServiceImpl(repository: DatabagRepository,
override val templates: DatabagTemplateRepository,
override val validations: Map[String, ConfigValueValidator]) extends DataBagService with
Validation[DataBag] with TemplateValidator {

@Transactional(readOnly = true)
def list = repository.list
Expand Down Expand Up @@ -48,7 +57,11 @@ class DataBagServiceImpl(repository: DatabagRepository) extends DataBagService w

def commonValidate (bag: DataBag): ExtendedResult[DataBag] = {
mustNotHaveDuplicateItems(bag) ++
mustSatisfyLengthConstraints(bag, bag.tags.mkString(" "), "tags")(0, 510)
mustSatisfyLengthConstraints(bag, bag.tags.mkString(" "), "tags")(0, 510) ++
validAccordingToTemplate(bag) match {
case Success(_) => Success(bag)
case a: Failure => a
}
}

protected def validateCreation(bag: DataBag) = {
Expand All @@ -66,4 +79,5 @@ class DataBagServiceImpl(repository: DatabagRepository) extends DataBagService w
Success(bag)
}
}

}
Expand Up @@ -24,12 +24,19 @@
package com.griddynamics.genesis.service.impl

import com.griddynamics.genesis.service
import com.griddynamics.genesis.api.Configuration
import com.griddynamics.genesis.repository.ConfigurationRepository
import com.griddynamics.genesis.api._
import com.griddynamics.genesis.repository.{DatabagTemplateRepository, ConfigurationRepository}
import org.springframework.security.core.context.SecurityContextHolder
import java.security.Principal
import com.griddynamics.genesis.validation.{Validation, ConfigValueValidator}
import com.griddynamics.genesis.api.Configuration
import com.griddynamics.genesis.api.Success
import com.griddynamics.genesis.api.Failure
import scala.Some

class EnvironmentConfigurationServiceImpl(repository: ConfigurationRepository, accessService: service.EnvironmentAccessService) extends service.EnvironmentConfigurationService {
class EnvironmentConfigurationServiceImpl(repository: ConfigurationRepository, accessService: service.EnvironmentAccessService,
override val templates: DatabagTemplateRepository, override val validations: Map[String, ConfigValueValidator])
extends service.EnvironmentConfigurationService with TemplateValidator with Validation[Configuration] {

private def getAuth = SecurityContextHolder.getContext.getAuthentication

Expand Down Expand Up @@ -71,4 +78,32 @@ class EnvironmentConfigurationServiceImpl(repository: ConfigurationRepository, a
def get(projectId: Int, configId: Int) = repository.get(projectId, configId).map(checkAccess(projectId, _))
.getOrElse(None)

protected def validateUpdate(c: Configuration) = {
commonValidation(c)
}

protected def validateCreation(c: Configuration) = {
commonValidation(c)
}

protected def commonValidation(c: Configuration) = {
valid(c) ++ validAccordingToTemplate(c).flatMap({case s: TemplateBased => Success(c)})
}

private def valid(config: Configuration): ExtendedResult[Configuration] = {
val exist = repository.findByName(config.projectId, config.name)
exist match {
case None => Success(config)
case Some(c) if c.id.isDefined && config.id == c.id => Success(config)
case _ => Failure(compoundServiceErrors = Seq("Environment configuration with name %s already exists in project".format(config.name)))
}
}

def save(c: Configuration) = {
validCreate(c, c => repository.save(c))
}

def update(c: Configuration) = {
validUpdate(c, c => repository.save(c))
}
}
@@ -0,0 +1,42 @@
package com.griddynamics.genesis.service.impl

import com.griddynamics.genesis.model.{DatabagTemplate, TemplateProperty}
import com.griddynamics.genesis.api.{Failure, TemplateBased, Success, ExtendedResult}
import com.griddynamics.genesis.validation.ConfigValueValidator
import com.griddynamics.genesis.repository.DatabagTemplateRepository

trait TemplateValidator {
def validations: Map[String, ConfigValueValidator]
def templates: DatabagTemplateRepository

private def validateValue(propName: String, value: String, defaults: Map[String, TemplateProperty]): ExtendedResult[Any] = {
val results: Seq[ExtendedResult[Any]] = for {prop <- defaults.get(propName).toSeq
(msg, validatorName) <- prop.getValidation
validator = validations.get(validatorName)} yield
validator.map(v => v.validate(propName, value, msg, Map("name" -> validatorName))).getOrElse(Success(propName))
if (results.isEmpty)
Success(propName)
else
results.reduce(_ ++ _)
}

def validAccordingToTemplate(bag: TemplateBased): ExtendedResult[TemplateBased] = {
bag.templateId.flatMap(templateId => {
templates.get(templateId).map(template => {
val requiredValidation = validateRequiredKeys(template, bag)
val rulesValidation = bag.itemsMap.foldLeft(Success(bag).asInstanceOf[ExtendedResult[TemplateBased]])((acc, item) => acc ++
validateValue(item._1, item._2, template.values).flatMap(v => Success(bag)))
requiredValidation ++ rulesValidation
})
}).getOrElse(Success(bag))
}

def validateRequiredKeys(template: DatabagTemplate, bag: TemplateBased): ExtendedResult[TemplateBased] = {
val requiredKeys: Iterable[String] = template.values.filter({
case (_, value) => value.required
}).keys.filterNot(name => bag.itemsMap.find({case (k,v) => k == name}).isDefined)
val left = requiredKeys.foldLeft(Success(bag).asInstanceOf[ExtendedResult[TemplateBased]])((acc, item) => acc ++
Failure(serviceErrors = Map(item -> s"Required key $item not found in databag")))
left
}
}
2 changes: 1 addition & 1 deletion backend/src/test/resources/databags/test.dbtemplate
Expand Up @@ -6,6 +6,6 @@
"default-tags": ["foo", "bar", "baz"],
"properties": {
"some-long-key": {"description": "This is a long key", "default": 1234, "validation": {"Must be integer value >= 0": "int_nonnegative"}},
"other-long-key": {"description": "This is an other key", "default": "aaa", "required": true}
"other-long-key": {"description": "This is an other key", "default": "aaa", "required" : true}
}
}
@@ -0,0 +1,58 @@
package com.griddynamics.genesis.service.impl

import org.scalatest.junit.AssertionsForJUnit
import org.scalatest.mock.MockitoSugar
import org.scalatest.matchers.ShouldMatchers
import org.scalatest.FunSpec
import com.griddynamics.genesis.repository.impl.DatabagTemplateRepositoryImpl
import com.griddynamics.genesis.api.{Failure, DataItem, ExtendedResult, DataBag}
import com.griddynamics.genesis.validation.IntValidator

class DatabagValidationAgainstTemplateTest extends AssertionsForJUnit with MockitoSugar with ShouldMatchers with FunSpec with DSLTestUniverse {
val repository = new DatabagTemplateRepositoryImpl("../resources/databags", "*.dbtemplate")
val service = new DataBagServiceImpl(null, repository, Map("int_nonnegative" -> new IntValidator(0, Int.MaxValue)))
val emptyDatabag = new DataBag(None, "", Seq(), None, Some("1"))

val databagWithRequiredKey = emptyDatabag.copy(items = Seq(
new DataItem(None, "other-long-key", "value", None)
))

val databagWithErrorInValues = emptyDatabag.copy(items = Seq(
new DataItem(None, "other-long-key", "value", None),
new DataItem(None, "some-long-key", "aaa", None)
))

val validDatabag = emptyDatabag.copy(items = Seq(
new DataItem(None, "other-long-key", "value", None),
new DataItem(None, "some-long-key", "123", None)
))

describe("Databag validation against template") {
it ("Should report missing keys") {
val missingErrors = service.validateRequiredKeys(repository.get("1").get, emptyDatabag)
missingErrors.isSuccess should equal(false)
missingErrors.asInstanceOf[Failure].serviceErrors.get("other-long-key") should not be None
}

it ("Should not report missing keys when no required key is specified in template") {
val missingErrors = service.validateRequiredKeys(repository.get("2").get, emptyDatabag)
missingErrors.isSuccess should equal(true)
}

it ("Should not report missing keys when databag contains them") {
val missingErrors = service.validateRequiredKeys(repository.get("1").get, databagWithRequiredKey)
missingErrors.isSuccess should equal(true)
}

it ("Should report validation errors when databag value is invalid") {
val missingErrors = service.validAccordingToTemplate(databagWithErrorInValues)
missingErrors.isSuccess should equal(false)
}

it ("Should not report validation errors when databag value is valid") {
val missingErrors = service.validAccordingToTemplate(databagWithErrorInValues)
missingErrors.isSuccess should equal(false)
}

}
}
Expand Up @@ -62,7 +62,7 @@ class GroovyTemplateProjectContextTest extends AssertionsForJUnit with MockitoSu
val createWorkflow = templateService.findTemplate(0, "Projects", "0.1", 1).get.createWorkflow

def testDatabag : DataBag = {
val db = DataBag(Some(0), "foo", Seq("foo"), Some(0), Seq(
val db = DataBag(Some(0), "foo", Seq("foo"), Some(0), None, Seq(
DataItem(Some(0), "key1", "fred", None),
DataItem(Some(0), "key2", "barney", None),
DataItem(Some(0), "key3", "wilma", None),
Expand All @@ -73,7 +73,7 @@ class GroovyTemplateProjectContextTest extends AssertionsForJUnit with MockitoSu
}

def systemDatabag : DataBag = {
val db = DataBag(Some(0), "bar", Seq("bar"), None, Seq(
val db = DataBag(Some(0), "bar", Seq("bar"), None, None, Seq(
DataItem(Some(0), "key1", "barney", None),
DataItem(Some(0), "key2", "wilma", None),
DataItem(Some(0), "key3", "fred", None)
Expand All @@ -82,7 +82,7 @@ class GroovyTemplateProjectContextTest extends AssertionsForJUnit with MockitoSu
}

def altDatabag : DataBag = {
val db = DataBag(Some(0), "foo", Seq("foo"), None, Seq(
val db = DataBag(Some(0), "foo", Seq("foo"), None, None, Seq(
DataItem(Some(0), "key1", "barney", None),
DataItem(Some(0), "key2", "wilma", None),
DataItem(Some(0), "key3", "fred", None),
Expand Down

0 comments on commit 67959a7

Please sign in to comment.