Skip to content
/ salt Public

Simple inversion of control framework written in Kotlin.

License

Notifications You must be signed in to change notification settings

kurbaniec/salt

Repository files navigation


solar-system

Salt

Salt is a simple application framework that features an inversion of control container for usage with Kotlin.

🛠️Setup

  1. Create new Gradle project with Kotlin/JVM as main language

  2. Insert the Salt framework with git submodule add https://github.com/kurbaniec-tgm/salt.git salt under src/main/kotlin/[main-package]

    Note: Do not work directly in the salt package! Create a second package for your code.

  3. Use gradle initSalt under src/main/kotlin/[main-package]/salt to initialize the framework

  4. Add the following dependencies to your build.gradle:

    implementation platform('org.jetbrains.kotlin:kotlin-bom')
    implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
    compile 'org.jetbrains.kotlin:kotlin-test'
    compile 'org.jetbrains.kotlin:kotlin-test-junit'
    compile group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: '1.3.60'
    compile group: 'org.mongodb', name: 'mongo-java-driver', version: '3.9.1'
    compile 'net.sourceforge.htmlunit:htmlunit:2.36.0'
    
  5. Lastly, define a main-method that starts the Salt application:

    class Main {
        companion object {
            @JvmStatic
            fun main(args: Array<String>) {
                val app = SaltApplication()
            }
        }
    }

🧂 Working with Salt

Dependency Injection

Just mark a class with @Scan and use @Autowired on a property with the same name/type in another class to automatically inject an instance of the scanned class to the property. Note, the class in which the object is injected also needs to be marked with @Scan or a annotation that includes it like @Controller.

@Scan
class AutowiredTest {
    val value = "Test"
}
@Scan
class SomeClass {
    @Autowired
    lateinit var autowiredTest: AutowiredTest     
}

Serve web content

Salt can serve any file you want, just drop it into the resources/web directory.

To configure request mappings for HTML-files create a new Controller class (annotated with @Controllerand define mapping functions.

A mapping functions consists of a method type annotation (@Get or @Post ) that contains the request path. The function itself returns a String, that is the filename of the page that should be returned without .html.

Following code shows how to return a HTML page on the request path / based on the file index.html-

@Controller
class Controller {
    
    @Get("/")
    fun index(): String {
        return "index"
    }
}

At the moment the mapping functions can also return simple HTTP-responses for simple data transfer through the HTTP-body.

Following code shows how to transport two parameters ("Hi" and "Salt") to an client, when it sends a post request at /api.

@Controller
class Controller {
    
    @Post("/api")
    fun api(): HTTPTransport {
        return HTTPTransport(HTTPTransport.Body("Hi", "Salt")).ok()
    }
}

HTML-Template Engine

The custom template parser is inspired by Thymeleaf and supports for now following functionality:

  • <span th:text="Hello, ${message}"></span>

    The th:text attribute will replace the value between tags eg. span. ${message} stands for an object that is added in the controller via model.addAttribute("message", "baum"). So the ouput, when parsed will be <span>Hello, baum</span>. If baum is for example not a String but an other object like User added as user with a attribute login, you can also acces it via ${user.login}.

  • <a th:href="@{/some/path/${testo}}">Link!</a>

    Creates a link that can refer to other pages or resources of the application. As seen, you can add also models like ${testo} that will be resolved to create a dynamic link.

  • <th:block th:each="user : ${users}">
        <span th:text="user ${user.id}"></span>
    </th:block>

    Creates a loop of a block marked with th:block. users is a list added via a model in the controller. For every entry of users marked as user the block will be dynamically created.
    Note: No <th:block> will be seen on the parsed site.

To enable the template engine, the html-page needs to be mapped in an controller an contain a Model object in the function call. All the information that you want to parse need to be added to the Model object.

You have a page that contains following template code:

<span th:text="Hello, ${message}"></span>

<th:block th:each="user : ${users}">
    <span th:text="user ${user.id}"></span>
</th:block>

Then you can use following code to return the page at /template with injected values.

@Controller
class Controller {
    
    @Get("/template")
    fun template(m: Model): String {
        data class User(val id: Int)
        m.addAttribute("message", "Salt")
        m.addAttribute("users", listOf(User(0), User(1)))
        return "template"
    }
}

The following result should be seen when visiting /template now.

MongoDB

Salt makes performing CRUD operations on a remote Mongo database very easy.

To enable the MongoDB plugin, add the following configuration to resources\config.toml

[mongo]
enable = true
uri = "mongodb://localhost:27017"
db = "dev"
UserRepo = "users"

This way Salt connects on startup to the remote database and binds the configuration interface UserRepo to the users collection in the dev database of MongoDB.

Now the UserRepo interface needs to be created and marked with the @MondoDB annotation. It also needs to implement the MongoRepo witch the type that we want to serialize to the database and String as second argument. With the implementation we gain a lot of basic functions like ìnsert or findAll.

But the true magic is, that you can easily define your own queries. Lets say the class User has a property username. Then you can create an abstract method fun findByUsername(username: String): List<User>? that generates the query for you and that you can call at runtime. You can also look after more parameters in a query to the inclusion of And.

Following code show some possible queries.

@MongoDB
interface UserRepo: MongoRepo<User, String> {

    fun findByUsername(username: String): List<User>?

    fun findByUsernameAndPassword(username: String, password: String): List<User>?

    fun findByMyidAndUsername(myid: String, username: String): List<User>?

    fun findByMyidAndUsernameAndPassword(myid: String, username: String, password: String): List<User>?

    fun findByMyid(myid: String): List<User>?

}

To use the MongoRepo in your code to perform CRUD-operations, just use the Salt dependency injection.

In this case that would look like that:

@Autowired
lateinit var userRepo: UserRepo

Naming conventions

Classes can for now not end with Kt or $1 because Kotlin creates [className](Kt|$1).class files, that are ignored while scanning for annotations.

More...

The Salt framework includes many more features. Visit the complete documentation here or check out the source code of fully fledged Salt-applications like KPM.

Configure Salt

Some aspects of the application can be configured in the configuration file found underresources\config.toml. Here are some of the most important ones:

[Server]:

  • ip_address: The ip-address the server is bound to.
  • https_port: The port the server listens to https-requests.
    • Note: There is also a http_port setting, but you should not use the http-protocol with sensitive data in use.
      You can disable it with http = false .

[Security]:

  • timeout: The time, in which the user is automatically logged out when no request is send in the meantime. In minutes.
  • password_timeout: The timeout, in which a user can not log in, because he used a wrong password to often. In minutes.
  • password_fails: Sets the amount of bad authentication needed to trigger the login timeout.

[Keystore]:

  • generate: Use true if you want to automatically create a certificate for the https-encryption. Be aware that this certificate is not signed by any authority and therefore not trusted by most browsers. If you have a valid certificate use false and fill the file-attribute.
  • file: If you have a TLS-certificate for your domain, convert it into a java-keystore (.jks-file) and put it into the res folder. The file-attribute should be the name of this file.
  • password: The password the generated certificate should use or the password to your own keystore.

[Mongo]:

  • uri: The connection description to your MongoDB-database in the "String URI Format". Click [here][5] for more information.
  • UserRepo: Name of your collection that will store user-accounts.
  • PasswordRepo: Name of your collection that will store password-entity data.

License

MIT


GitHub kurbaniec  ·  Mail at.kacper.urbaniec@gmail.com