Skip to content
This repository has been archived by the owner on Jul 13, 2020. It is now read-only.

Commit

Permalink
Merge pull request #10 from kohesive/develop
Browse files Browse the repository at this point in the history
Develop to mater for 1.3.0 release
  • Loading branch information
apatrida committed Aug 21, 2015
2 parents 62936c1 + 097c055 commit b95db5e
Show file tree
Hide file tree
Showing 17 changed files with 404 additions and 20 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
2015-??-?? IN DEVELOPMENT

...

====

2015-08-21 v1.3.0

* Added Configuration injection from Typesafe Config (see [README in config-typesafe](config-typesafe/))
* Added storage in Injekt scope for addons to work within a scope

====

2015-08-11 v1.2.0

* [BREAKING CHANGE] Fixing #8 - Moved api classes to `uy.kohesive.injekt.api` package so that separate module jars do not have classes in the same package, breaking use in Android
Expand Down
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,30 @@ Injekt is a crazyily easy **Dependency Injection** for Kotlin. Although you can

Injekt is NOT inversion of control. It is NOT some mystical class file manipulator. It is NOT complex. But it IS powerful.

Injekt can also load, bind to objects, and inject configuration using Typesafe Config. Read more in the [injekt-config-typesafe module](config-typesafe/).

## Maven Dependnecy

First, include the dependency in your Gradle / Maven projects, ones that have Kotlin configured for Kotlin M12 versions `0.12.1218` or `0.12.1230`

**Gradle:**
```
compile "uy.kohesive.injekt:injekt-core:1.2.+"
compile "uy.kohesive.injekt:injekt-core:1.3.+"
```

**Maven:**
```
<dependency>
<groupId>uy.kohesive.injekt</groupId>
<artifactId>injekt-core</artifactId>
<version>[1.2.0,1.3.0)</version>
<version>[1.3.0,1.4.0)</version>
</dependency>
```


*(deploy note: Maven repo publishing is in progress, should appear soon)*

## Injektor "Main"
## Injekt "Main"

At the earliest point in your application startup, you register singletons, factories and your logging factories. For the simplest version of this process, you can use the `InjektModule` on an object or companion object (from [Injekt Examples](https://github.com/kohesive/injekt/blob/master/core/src/example/kotlin/uy/kohesive/injekt/example/MyApp.kt))

Expand Down Expand Up @@ -144,13 +146,16 @@ Note: if you extend `InjektMain` you are also a module that can be imported. B

## One Instance Per-Thread Factories -- a tip

When using a factory that is per-thread (one instance of each object is generated per thread), it is important that you consider how the instance is used. If you generate it near the start of processing on a thread avoid passing the object to be used on a different thread if you truly want to isolate the instances by thread. Currently, the default registry has lock contention across threads for these per-thread factories, so asking too often will cause possible thread contention. But, when [issue #2](https://github.com/kohesive/injekt/issues/2) is resolved, thread local storage will be used making it very fast to grab the instance any time you need it rather than holding onto the instance for a long duration. Until then, watch how you uses these.
When using a factory that is per-thread, be sure not to pass the object to other threads if you really intend for them to be isolated. Lookup of such objects is from ThreadLocal storage and fast, so it is better to keep these objects for shorter durations or in situations guaranteed to stay on the same thread as that which retrieved the object.

## Injecting Configuration with Typesafe Config

Injekt can also load, bind to objects, and inject configuration using Typesafe Config. Read more in the [injekt-config-typesafe module](config-typesafe/).

## Coming soon... (RoadMap)

* More about scopes
* Materializing object graphs without explicit calls to Injekt
* Configuration loading, binding and injektion as a separate module.
* Tell me what you would like to see, add Issues here in Github with requests.

## Recommended libraries:
Expand Down
2 changes: 1 addition & 1 deletion api/src/main/kotlin/uy/kohesive/injekt/api/Modules.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public abstract class InjektScopedMain(public val scope: InjektScope) : InjektMo
* A package of injectable items that can be included into a scope of someone else
*/
public interface InjektModule {
internal fun registerWith(intoModule: InjektRegistrar) {
final internal fun registerWith(intoModule: InjektRegistrar) {
intoModule.registerInjectables()
}

Expand Down
2 changes: 2 additions & 0 deletions api/src/main/kotlin/uy/kohesive/injekt/api/Registry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public interface InjektRegistry {
public fun <T> alias(existingRegisteredClass: Class<T>, otherClassesThatAreSame: List<Class<*>>)
public fun <T> hasFactory(forClass: Class<T>): Boolean

public fun <T> getAddonMetadata(addon: String): T
public fun <T> setAddonMetadata(addon: String, metadata: T): T

public final inline fun <reified T : Any> T.registerAsSingleton() {
addSingleton(this)
Expand Down
2 changes: 1 addition & 1 deletion api/src/main/kotlin/uy/kohesive/injekt/api/Scope.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import kotlin.properties.ReadOnlyProperty
/**
* Not much difference than a InjektRegistrar for now...
*/
public open class InjektScope(val registrar: InjektRegistrar) : InjektRegistrar by registrar{
public open class InjektScope(val registrar: InjektRegistrar) : InjektRegistrar by registrar {
override fun <T> alias(existingRegisteredClass: Class<T>, otherClassesThatAreSame: List<Class<*>>) {
if (!hasFactory(existingRegisteredClass)) {
throw InjektionException("Cannot alias anything to ${existingRegisteredClass.getName()}, it does not have a registered factory")
Expand Down
36 changes: 32 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,23 @@ subprojects {
apply plugin: 'kotlin'
apply plugin: 'pride'

sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
if (name.endsWith("jdk8")) {
println("Project ${name} set to JDK 8")
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
} else if (name.endsWith("jdk7")) {
println("Project ${name} set to JDK 7")
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
} else if (name.endsWith("jdk6")) {
println("Project ${name} set to JDK 6")
sourceCompatibility = JavaVersion.VERSION_1_6
targetCompatibility = JavaVersion.VERSION_1_6
} else {
println("Project ${name} set to JDK 6 (Default)")
sourceCompatibility = JavaVersion.VERSION_1_6
targetCompatibility = JavaVersion.VERSION_1_6
}

tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
Expand Down Expand Up @@ -94,12 +109,25 @@ if (rootProject != project) {
subprojects {
apply plugin: 'com.bmuschko.nexus'

def pomOnlyModule = !(new File("${projectDir.toString()}/src/main/kotlin").exists())
if (pomOnlyModule) {
println("Project $name is POM ONLY (has no code of its own)")
}

task jars() {
dependsOn build
}

task subRelease() {
dependsOn jars, uploadArchives
if (pomOnlyModule) {
artifacts {}
task subRelease() {
dependsOn uploadArchives
}
}
else {
task subRelease() {
dependsOn jars, uploadArchives
}
}

modifyPom {
Expand Down
6 changes: 6 additions & 0 deletions config-typesafe-jdk6/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
dependencies {
compile relativeProject(":injekt-api")
runtime relativeProject(":injekt-core")
compile "uy.klutter:klutter-config-typesafe-jdk6:$version_klutter"
compile "uy.klutter:klutter-json-jackson-jdk6:$version_klutter"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package uy.kohesive.injekt.config.typesafe

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigRenderOptions
import com.typesafe.config.ConfigResolveOptions
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.InjektModule
import uy.kohesive.injekt.api.InjektRegistrar
import uy.kohesive.injekt.api.InjektScope
import java.net.URI

/**
* A class that startups up an system using Injekt + TypesafeConfig, using the default global scope, and default object binder
*/
public abstract class KonfigAndInjektMain(): KonfigAndInjektScopedMain(Injekt)

/**
* A startup module that registers and uses singletons/object factories from a specific scope,
* and an ObjectMapper to bind configuration properties into class instances.
*/
public abstract class KonfigAndInjektScopedMain(public val scope: InjektScope, public val mapper: ObjectMapper = jacksonObjectMapper()) : InjektModule, KonfigModule {
private val ADDON_ID = "Konfigure"

abstract fun configFactory(): Config

private data class KonfigureClassAtPath(val path: String, val klass: Class<*>)

private inner class ScopedKonfigRegistrar(val path: List<String>, val scope: InjektScope, val itemsToConfigure: MutableList<KonfigureClassAtPath>): KonfigRegistrar {
override fun importModule(atPath: String, module: KonfigModule) {
module.registerWith(ScopedKonfigRegistrar(path + atPath.split('.'), scope, itemsToConfigure))
}

override fun bindClassAtConfigRoot(klass: Class<*>) {
val fullpath = path.filter { it.isNotBlank() }.map { it.removePrefix(".").removeSuffix(".") }.joinToString(".")
itemsToConfigure.add(KonfigureClassAtPath(fullpath, klass))
}

override fun bindClassAtConfigPath(configPath: String, klass: Class<*>) {
val fullpath = (path + configPath.split('.')).filter { it.isNotBlank() }.map { it.removePrefix(".").removeSuffix(".") }.joinToString(".")
itemsToConfigure.add(KonfigureClassAtPath(fullpath, klass))
}

@suppress("UNCHECKED_CAST")
fun loadAndInject(config: Config) {
itemsToConfigure.forEach {
val configAtPath = config.getConfig(it.path)
// TODO: handle a class that wants to be constructed with a configuration object instead of binding
val asJson = configAtPath.root().render(ConfigRenderOptions.concise().setJson(true))
val instance: Any = mapper.readValue(asJson, it.klass)!!
scope.registrar.addSingleton(it.klass as Class<Any>, instance)
}
}
}

init {
val itemsToConfigure: MutableList<KonfigureClassAtPath> = scope.getAddonMetadata(ADDON_ID) ?: scope.setAddonMetadata(ADDON_ID, linkedListOf<KonfigureClassAtPath>())
val registrar = ScopedKonfigRegistrar(emptyList(), scope, itemsToConfigure)
registrar.registerConfigurables()
val config = configFactory()
registrar.loadAndInject(config)
scope.registrar.registerInjectables()
}
}

public interface KonfigRegistrar {
fun importModule(atPath: String, module: KonfigModule)

final inline fun <reified T> bindClassAtConfigPath(configPath: String) {
bindClassAtConfigPath(configPath, javaClass<T>())
}

fun bindClassAtConfigPath(configPath: String, klass: Class<*>)

final inline fun <reified T> bindClassAtConfigRoot() {
bindClassAtConfigRoot(javaClass<T>())
}

fun bindClassAtConfigRoot(klass: Class<*>)
}

/**
* A package of configuration bound items that can be included into a scope of someone else
*/
public interface KonfigModule {
final internal fun registerWith(intoModule: KonfigRegistrar) {
intoModule.registerConfigurables()
}

fun KonfigRegistrar.registerConfigurables()
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package uy.kohesive.injekt.config.typesafe

import com.typesafe.config.Config
import org.junit.Test
import uy.klutter.config.typesafe.MapAsConfig
import uy.klutter.config.typesafe.loadConfig
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.InjektModule
import uy.kohesive.injekt.api.InjektRegistrar
import kotlin.test.assertEquals

class TestTypesafeConfigInjection {
companion object : KonfigAndInjektMain() {
override fun configFactory(): Config {
return loadConfig(MapAsConfig(kotlin.mapOf(
"http" to kotlin.mapOf("httpPort" to 8080, "workerThreads" to 16),
"data" to kotlin.mapOf("bucket" to "com.test.bucket", "region" to "us-east"),
"other" to kotlin.mapOf("name" to "frisbee"))))
}

override fun KonfigRegistrar.registerConfigurables() {
bindClassAtConfigPath<HttpConfig>("http")
bindClassAtConfigPath<DataConfig>("data")
importModule("other", OtherModule)
}

override fun InjektRegistrar.registerInjectables() {
addFactory { ConfiguredThing() }
importModule(OtherModule)
}

}

@Test public fun testConfigSingletonsExist() {
val matchHttp = HttpConfig(8080,16)
val matchData = DataConfig("com.test.bucket", "us-east")

assertEquals(matchHttp, Injekt.get<HttpConfig>())
assertEquals(matchData, Injekt.get<DataConfig>())
}

@Test public fun testFactoryUsingConfigWorks() {
val matchHttp = HttpConfig(8080,16)
val matchData = DataConfig("com.test.bucket", "us-east")

val thing = Injekt.get<ConfiguredThing>()
assertEquals(matchHttp, thing.httpCfg)
assertEquals(matchData, thing.dataCfg)
}

@Test public fun testWithModules() {
val thing = Injekt.get<OtherThingWantingConfig>()
assertEquals("frisbee", thing.cfg.name)
}


data class HttpConfig(val httpPort: Int, val workerThreads: Int)
data class DataConfig(val bucket: String, val region: String)
data class ConfiguredThing(val httpCfg: HttpConfig = Injekt.get(), val dataCfg: DataConfig = Injekt.get())
}


data class OtherConfig(val name: String)
data class OtherThingWantingConfig(val cfg: OtherConfig = Injekt.get())

public object OtherModule : KonfigModule, InjektModule {
override fun KonfigRegistrar.registerConfigurables() {
bindClassAtConfigRoot<OtherConfig>()
}

override fun InjektRegistrar.registerInjectables() {
addFactory { OtherThingWantingConfig() }
}
}




7 changes: 7 additions & 0 deletions config-typesafe-jdk7/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
dependencies {
compile relativeProject(":injekt-api")
runtime relativeProject(":injekt-core")
compile relativeProject(":injekt-config-typesafe-jdk6")
compile "uy.klutter:klutter-config-typesafe-jdk7:$version_klutter"
compile "uy.klutter:klutter-json-jackson-jdk6:$version_klutter"
}
8 changes: 8 additions & 0 deletions config-typesafe-jdk8/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
dependencies {
compile relativeProject(":injekt-api")
runtime relativeProject(":injekt-core")
compile relativeProject(":injekt-config-typesafe-jdk6") // TODO: exclude uy.klutter:klutter-json-jackson-jdk6
compile relativeProject(":injekt-config-typesafe-jdk7") // TODO: exclude uy.klutter:klutter-json-jackson-jdk6
compile "uy.klutter:klutter-config-typesafe-jdk8:$version_klutter"
compile "uy.klutter:klutter-json-jackson-jdk8:$version_klutter"
}
Loading

0 comments on commit b95db5e

Please sign in to comment.