Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit 276d06a94043c9c4de8b3adf765425083b152500 0 parents
@aloiscochard authored
4 .gitignore
@@ -0,0 +1,4 @@
+target
+boot
+lib_managed
+_build
28 README.md
@@ -0,0 +1,28 @@
+# Siona
+Siona is a layered application framework for the [Scala](http://www.scala-lang.org) programming language.
+
+## Contribution Policy
+
+Contributions via GitHub pull requests are gladly accepted from their original author.
+Along with any pull requests, please state that the contribution is your original work and
+that you license the work to the project under the project's open source license.
+Whether or not you state this explicitly, by submitting any copyrighted material via pull request,
+email, or other means you agree to license the material under the project's open source license and
+warrant that you have the legal authority to do so.
+
+## License
+
+ This software is licensed under the Apache 2 license, quoted below.
+
+ Copyright 2009-2012 Alois Cochard
+
+ 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.
+
49 project/Publishing.scala
@@ -0,0 +1,49 @@
+import sbt._
+import Keys._
+
+abstract class PublishToSonatype(build: Build) {
+ import build._
+
+ val ossSnapshots = "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots/"
+ val ossStaging = "Sonatype OSS Staging" at "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
+
+ def projectUrl: String
+ def developerId: String
+ def developerName: String
+
+ def licenseName: String
+ def licenseUrl: String
+ def licenseDistribution = "repo"
+ def scmUrl = projectUrl
+ def scmConnection = "scm:git:" + scmUrl
+
+ def generatePomExtra(scalaVersion: String): xml.NodeSeq = {
+ <url>{ projectUrl }</url>
+ <licenses>
+ <license>
+ <name>{ licenseName }</name>
+ <url>{ licenseUrl }</url>
+ <distribution>{ licenseDistribution }</distribution>
+ </license>
+ </licenses>
+ <scm>
+ <url>{ scmUrl }</url>
+ <connection>{ scmConnection }</connection>
+ </scm>
+ <developers>
+ <developer>
+ <id>{ developerId }</id>
+ <name>{ developerName }</name>
+ </developer>
+ </developers>
+ }
+
+ def settings: Seq[Setting[_]] = Seq(
+ credentials += Credentials(Path.userHome / ".ivy2" / ".credentials"),
+ publishMavenStyle := true,
+ publishTo <<= version((v: String) => Some( if (v.trim endsWith "SNAPSHOT") ossSnapshots else ossStaging)),
+ publishArtifact in Test := false,
+ pomIncludeRepository := (_ => false),
+ pomExtra <<= (scalaVersion)(generatePomExtra)
+ )
+}
67 project/Siona.scala
@@ -0,0 +1,67 @@
+// ____,__, ____, _, _, ____,
+// (-(__(-| (-/ \(-|\ | (-/_|
+// ____)_|_,_\__/,_| \|,_/ |,
+//
+
+import sbt._
+import Keys._
+
+object BuildSettings {
+ val buildSettings = Defaults.defaultSettings ++ Sonatype.settings ++ Seq(
+ organization := "com.github.aloiscochard.siona",
+ version := "0.1-SNAPSHOT",
+ scalaVersion := "2.9.1",
+ scalacOptions := Seq("-unchecked", "-deprecation", "-Ydependent-method-types"),
+ crossScalaVersions := Seq("2.9.1", "2.9.1-1", "2.9.2"),
+ resolvers ++= Seq(
+ "Sonatype OSS Releases" at "http://oss.sonatype.org/content/repositories/releases/",
+ "Sonatype OSS Snapshots" at "http://oss.sonatype.org/content/repositories/snapshots/"
+ )
+ )
+}
+
+object Dependencies {
+ val testDependencies = Seq(libraryDependencies += "org.specs2" %% "specs2" % "1.8.2" % "test")
+}
+
+object SionaBuild extends Build {
+ import Dependencies._
+ import BuildSettings._
+
+ lazy val sindi = Project (
+ "siona",
+ file ("."),
+ settings = buildSettings
+ ) aggregate (core, logging)
+
+ lazy val core = Project(
+ "siona-core",
+ file("siona-core"),
+ settings = buildSettings ++ testDependencies ++ Seq(
+ libraryDependencies ++= Seq(
+ "org.scalaz" %% "scalaz-core" % "7.0-SNAPSHOT",
+ "com.chuusai" %% "shapeless" % "1.2.0-SNAPSHOT"
+ )
+ )
+ )
+
+ lazy val logging = Project(
+ "siona-logging",
+ file("siona-logging"),
+ settings = buildSettings ++ testDependencies
+ ) dependsOn(core)
+
+ lazy val demo_petstore = Project(
+ "siona-demo-petstore",
+ file("siona-demo/petstore"),
+ settings = buildSettings ++ testDependencies
+ ) dependsOn(core, logging)
+}
+
+object Sonatype extends PublishToSonatype(SionaBuild) {
+ def projectUrl = "https://github.com/aloiscochard/siona"
+ def developerId = "alois.cochard"
+ def developerName = "Alois Cochard"
+ def licenseName = "Apache 2 License"
+ def licenseUrl = "http://www.apache.org/licenses/LICENSE-2.0.html"
+}
15 siona-core/src/main/scala/Siona.scala
@@ -0,0 +1,15 @@
+// ____,__, ____, _, _, ____,
+// (-(__(-| (-/ \(-|\ | (-/_|
+// ____)_|_,_\__/,_| \|,_/ |,
+//
+package object siona {
+ import siona.core._
+ // core.uuid
+ type UUID = uuid.UUID
+ val UUID = uuid.UUID
+ implicit val uuidEqual = uuid.uuidEqual
+ // core.entity
+ val Key = entity.Key
+ type Entity[K, T] = entity.Entity[K, T]
+ implicit def entityOps[K, T](e: entity.Entity[K, T]) = entity.entityOps(e)
+}
46 siona-core/src/main/scala/core.scala
@@ -0,0 +1,46 @@
+// ____,__, ____, _, _, ____,
+// (-(__(-| (-/ \(-|\ | (-/_|
+// ____)_|_,_\__/,_| \|,_/ |,
+//
+package siona.core
+
+import scalaz._
+import Scalaz._
+
+package object uuid {
+ type UUID = java.util.UUID
+
+ object UUID {
+ def random = java.util.UUID.randomUUID
+ }
+
+ implicit val uuidEqual = new Equal[UUID] {
+ def equal(id1: UUID, id2: UUID): Boolean = id1 == id2
+ }
+}
+
+package object entity {
+ object Key {
+ def apply[K, T](k: K): Entity[K, T]#Key = k.asInstanceOf[Entity[K, T]#Key]
+ }
+
+ object Entity {
+ implicit def equal[K, T](implicit eqK: Equal[K]): Equal[Entity[K, T]] = new Equal[Entity[K, T]] {
+ def equal(e1: Entity[K, T], e2: Entity[K, T]): Boolean = e1.id === e2.id
+ }
+ }
+
+ trait Entity[K, T] {
+ type Key = K @@ T
+ val key: Key
+ def id: K = key.asInstanceOf[K]
+ }
+
+ implicit def entityOps[K, T](e: Entity[K, T]) = EntityOps(e)
+
+ case class EntityOps[K, T](val entity: Entity[K,T]) {
+ def ===(other: Entity[K, T])(implicit e: Equal[Entity[K, T]]) = e.equal(entity, other)
+ }
+
+}
+
76 siona-core/src/main/scala/data.mapping.scala
@@ -0,0 +1,76 @@
+// ____,__, ____, _, _, ____,
+// (-(__(-| (-/ \(-|\ | (-/_|
+// ____)_|_,_\__/,_| \|,_/ |,
+//
+package siona.data
+package mapping
+
+import siona.core.entity._
+
+import repository._
+import query._
+import model._
+
+trait Input[E <: Entity[_, _]] extends model.Input {
+ def key: E#Key
+}
+trait Output[E <: Entity[_, _]] extends model.Output
+
+trait Mapped[E <: Entity[_,_], T] extends model.Field[T] {
+ val lens: scalaz.Lens[E, T]
+ object Lens {
+ def apply(a: E => T, b: E => T => E) = scalaz.Lens.lensG[E, T](a, b)
+ }
+}
+
+trait Mapper[E <: Entity[_, _]] extends model.Model {
+ override type In = Input[E]
+ override type Out = Output[E]
+
+ type Mapped[T] = mapping.Mapped[E, T]
+
+ protected[this] def key[K, T](implicit in: In): Entity[K, T]#Key = in.key.asInstanceOf[Entity[K, T]#Key]
+
+ protected def in(implicit in: In): E
+ protected def out: List[Mapped[_]] // FIXME ... with HList? + Add key automatically
+
+ def read(i: In) = in(i)
+ def write(o: Out, e: E) = { // FIXME RETURN IO
+ //out.foreach(f => f.apply(f.lens.get(e), o)) // FIX WITH HLIST
+ e
+ }
+
+ implicit def fieldDefault2apply[T](f: model.Default[T])(implicit in: In) = f(in)
+ implicit def fieldOptional2apply[T](f: model.Optional[T])(implicit in: In) = f(in)
+}
+
+trait Mapping[M <: Mapper[E], E <: Entity[K, T], K, T] extends Repository[M] {
+ override type Key = E#Key
+
+ implicit def e2key(e: E): Key = e.key
+
+ implicit def e2ops(e: E) = EntityOps(e)
+
+ case class EntityOps(e: E) {
+ def save() = entity.set(e, e)
+ def update[T](m: Mapper[E]#Mapped[T])(f: T => T) = entity.update(e, m)(f)
+ }
+
+ object entity {
+ def get(k: Key) = Mapping.this.get(k, i => model.read(i))
+ def set(k: Key, e: E) = Mapping.this.set(k, o => model.write(o, e))
+ def update(k: Key, f: E => E) = Mapping.this.get(k, i => model.read(i)).flatMap(e => Mapping.this.set(k, o => model.write(o, e)))
+ def update[T](e: E, m: Mapper[E]#Mapped[T])(f: T => T) = None
+ def update[T0, T1](e: E)(ms: (Mapper[E]#Mapped[T0], Mapper[E]#Mapped[T1]))(ts: (T0, T1) => (T0, T1)) = None
+ def query[List[E]](xs: Predicate[_, Indexed]) = None
+ }
+}
+
+case class Entities[M <: Mapper[E], E <: Entity[K, T], K, T](override val model: M)
+ extends Repository[M] with Mapping[M, E, K, T] {
+ val key = new Field[K] {
+ val name = "key"
+ override type V = T
+ def apply(implicit in: siona.data.model.Input): V = throw new Error("Key field cannot be read.")
+ }
+}
66 siona-core/src/main/scala/data.model.scala
@@ -0,0 +1,66 @@
+// ____,__, ____, _, _, ____,
+// (-(__(-| (-/ \(-|\ | (-/_|
+// ____)_|_,_\__/,_| \|,_/ |,
+//
+package siona.data
+
+import scalaz._
+
+import mapping._
+
+package object model {
+ type Validation[T] = T => ValidationNEL[String, T]
+}
+
+package model {
+ trait Input {
+ def apply[T](f: Field[T]) = f.apply(this)
+ def read[T](n: String): Option[T]
+ }
+ trait Output {
+ def write[T](n: String, x: T): T
+ }
+
+ trait Model {
+ type F[T] <: model.Field[T]
+ type In <: Input
+ type Out <: Output
+ }
+
+ trait Document extends Model {
+ override type F[T] = Indexed[T] with Validated[T]
+
+ abstract class Field[T](override val name: String)(implicit m: Monoid[T])
+ extends Default[T] with Indexed[T] with Validated[T] {
+ val default = m.zero
+ }
+ }
+
+ trait Field[T] {
+ type V
+ type Z[T] = Field[T]
+ val name: String
+ def apply(implicit in: Input): V
+ def apply(x: T)(out: Output) = out.write(name, x)
+ }
+
+ trait Optional[T] extends Field[T] {
+ override type V = Option[T]
+ def apply(implicit in: Input): V = in.read[T](name)
+ }
+
+ trait Default[T] extends Field[T] {
+ override type V = T
+ val default: T
+ def apply(implicit in: Input): V = in.read[T](name).getOrElse(default)
+ }
+
+ trait Indexed[T] extends Field[T]
+
+ trait Validated[T] extends Field[T] {
+ val validation: model.Validation[T]
+ object Validation {
+ def apply(v: model.Validation[T]) = v
+ }
+ }
+}
27 siona-core/src/main/scala/data.query.scala
@@ -0,0 +1,27 @@
+// ____,__, ____, _, _, ____,
+// (-(__(-| (-/ \(-|\ | (-/_|
+// ____)_|_,_\__/,_| \|,_/ |,
+//
+package siona.data
+package query
+
+sealed trait Predicate[T, A[T]] {
+ val source: A[T]
+}
+case class Equal[T, A[T]](override val source: A[T], value: T) extends Predicate[T, A]
+case class Greater[T, A[T]](override val source: A[T], value: T) extends Predicate[T, A]
+
+case class Predictable[T, A[T]](source: A[T]) {
+ // > greaterThan
+ // < lessThan
+ //
+ // %== startsWith
+ // ==% endsWith
+ // =%= contains
+ //
+ // === equalTo
+ // =!= notEqualTo
+ def >(x: T) = Greater(source, x)
+
+ def ===(x: T) = Equal(source, x)
+}
61 siona-core/src/main/scala/data.repository.scala
@@ -0,0 +1,61 @@
+// ____,__, ____, _, _, ____,
+// (-(__(-| (-/ \(-|\ | (-/_|
+// ____)_|_,_\__/,_| \|,_/ |,
+//
+package siona.data
+
+import mapping._
+import model._
+import query._
+
+package object repository {
+ implicit def field2input[T](f: Field[T]) = f(_: model.Input)
+ implicit def indexed2predictable[T](field: Indexed[T]) = query.Predictable[T, Indexed](field)
+}
+
+package repository {
+ trait Repository[M <: Model] {
+ type Key
+
+ val model: M
+
+ implicit def field2ops[T](f: Field[T]) = FieldOps(f)
+
+ case class FieldOps[T](field: Field[T]) {
+ def get(key: Key) = Repository.this.get(key, field)
+ def set(key: Key, x: T) = Repository.this.set(key, field)(x)
+ def update(key: Key, x: T => T) = Repository.this.update(key, field)(x)
+ }
+
+ def get[T](k: Key, x: M#In => T) = None.asInstanceOf[Operation[T]]
+ def get[T](k: Key, f: Field[T]) = None.asInstanceOf[Operation[T]]
+ def get[T0, T1](k: Key)(fs: (Field[T0], Field[T1])) = None.asInstanceOf[Operation[(T0, T1)]]
+
+ def set[T](k: Key, x: M#Out => T) = None.asInstanceOf[Operation[T]]
+ def set[T](k: Key, f: Field[T])(x: T) = None.asInstanceOf[Operation[T]]
+ def set[T0, T1](k: Key)(fs: (Field[T0], Field[T1]))(xs: (T0, T1)) = None.asInstanceOf[Operation[(T0, T1)]]
+
+ def update[T](k: Key, i: M#In => T)(o: T=> M#Out => T) =
+ get(k, i).flatMap(x => set(k, o(x)))
+ def update[T](k: Key, f: Field[T])(x: T => T) =
+ get(k, f).flatMap(set(k, f))
+ def update[T0, T1](k: Key)(fs: (Field[T0], Field[T1]))(ts: (T0, T1) => (T0, T1)) =
+ get(k)(fs).flatMap(x => set(k)(fs)(ts(x._1, x._2)))
+
+ def query[T](xs: Predicate[_, Indexed])(x: M#In => T) = None.asInstanceOf[Operation[List[T]]]
+ def query[T](xs: Predicate[_, Indexed], f: Field[T]) = None.asInstanceOf[Operation[T]]
+ def query[T0, T1](xs: Predicate[_, Indexed], fs: (Field[T0], Field[T1])) = None.asInstanceOf[Operation[(T0, T1)]]
+ }
+
+
+ object Operation {
+ def apply[T](x: T) = new Operation[T] { override lazy val value = x }
+ //def bind[A](op: Operation[A])(
+ }
+
+ trait Operation[V] {
+ def value: V
+ def map[T](f: V => T): T = f(value)
+ def flatMap[T](f: V => Operation[T]) = Operation(f(value))
+ }
+}
10 siona-core/src/main/scala/data.scala
@@ -0,0 +1,10 @@
+// ____,__, ____, _, _, ____,
+// (-(__(-| (-/ \(-|\ | (-/_|
+// ____)_|_,_\__/,_| \|,_/ |,
+//
+package siona
+
+package object data {
+ // TODO Add Predef
+}
+
121 siona-demo/petstore/src/main/scala/core/model.scala
@@ -0,0 +1,121 @@
+package siona.demo.petstore
+package core
+
+import scalaz._
+import Scalaz._
+
+import siona._
+//import siona.data._ will replace separated sub-module import
+
+import siona.data.mapping._
+import siona.data.model._
+import siona.data.repository._
+
+// - Refactor querying support (per impl configuration)
+// - Better matcher support with automatic add SizeConstrain
+// - Schema support
+// - Use shapeless HList instead of TupleX
+
+package object model {
+
+ case class Category(
+ key: Category#Key,
+ name: String
+ ) extends Entity[UUID, Category]
+
+ case class Item(
+ key: Item#Key,
+ name: String,
+ category: Category#Key
+ ) extends Entity[UUID, Item]
+
+ object Category extends Document with Mapper[Category] {
+ val name = new Field[String]("name") with Mapped[String] {
+ val lens = Lens(_.name, e => n => e.copy(name = n))
+
+ val validation = Validation(name => {
+ if (name.isEmpty) "Name can't be empty".fail
+ if (name.length > 20) "Name can't be longer than 20 character".fail
+ if (name.length < 5) "Name can't be smaller than 5 character".fail
+ name.success
+ })
+ }
+
+ // TODO Add Optional Field support
+
+ def apply(name: String): Category = Category(
+ Key(UUID.random),
+ name
+ )
+
+ def in(implicit in: In) = Category(key, name)
+ def out = name :: Nil
+ }
+
+
+
+ object Item {
+ def apply(name: String, category: Category#Key): Item = Item(
+ Key(java.util.UUID.randomUUID),
+ name,
+ category
+ )
+ }
+}
+
+package object store {
+ object Categories extends Entities[model.Category.type, model.Category, UUID, model.Category](model.Category)
+}
+
+object test {
+ val c = model.Category("food")
+ val i = model.Item("HappyCat", c.key)
+
+ // Type-safe equality on entity ID
+ c === c
+
+ ////////////////////////
+ // Repository support //
+ ////////////////////////
+ import model.Category._
+ import store.Categories._
+
+ // Per Entities (Mapped Only)
+ entity.get(c.key)
+ entity.get(c) // implicit convertion Entity => Entity#Key
+
+ entity.set(c.key, c)
+ entity.update(c.key, x => x.copy(name = x.name + "*"))
+ entity.update(c, name)(_ + "*") // == update(c.key)(_)(_) => Then apply lens and return c
+ entity.update(c)(name -> name) { (a, b) => (a, b) }
+
+ // Per Entity (Mapped Only)
+ c.save // == entity.set(c.key)(c)
+ c.update(name)(_ + "*") // == entity.update(c)(_)(_)
+
+ // Per Column
+ name.get(c.key) // == get(c.key)(name)
+ name.set(c.key, "yo") // == get(c.key)(name)
+ name.update(c.key, _ + "*") // == update(c.key)(name)(_)
+
+ // Per Document
+ get(c.key, name)
+ get(c.key)(key, name)
+
+ set(c.key, name)("a")
+ set(c.key)(key, name)(UUID.random, "b")
+
+ update(c.key, name)(_ + "*")
+ update(c.key)(key, name) { (k, n) => (UUID.random, n + "-" + k) }
+
+ // Monadic Operations
+ entity.get(c.key).flatMap { x => entity.set(c.key, x.copy(name = x.name + "*")) }
+ for { x <- entity.get(c.key) } yield entity.set(c.key, x.copy(name = x.name + "*"))
+
+
+ get(c.key, name).flatMap { x => set(c.key, name)(x + "*") }
+ for { x <- get(c.key, name) } yield set(c.key, name)(x + "*")
+
+ // Query DSL
+ entity.query(name === "food")
+}
Please sign in to comment.
Something went wrong with that request. Please try again.