Skip to content

Commit

Permalink
Add the ability to specify default values for non-nullable properties
Browse files Browse the repository at this point in the history
  • Loading branch information
pm-dev committed Sep 10, 2018
1 parent ba6a178 commit e50d664
Show file tree
Hide file tree
Showing 24 changed files with 409 additions and 43 deletions.
36 changes: 12 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,19 @@
[![Kotlin Version](https://img.shields.io/badge/kotlin-1.2.60-blue.svg)](http://kotlinlang.org/)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0)

An interactive example can be run using the [starwars example project](https://github.com/pm-dev/kotlin-gremlin-ogm/tree/master/example).
From the repo root directory, run:

./gradlew :example:run

Then load GraphiQL at `http://localhost:5000/graphiql.html` to explore the data mapped with this library.



Gremlin is the graph traversal language for the Apache TinkerPop graph framework and is
supported by most graph database implementations.
Check out [kotlin-janusgraph-ogm](https://github.com/pm-dev/kotlin-gremlin-ogm/tree/master/kotlin-janusgraph-ogm)
for additional JanusGraph specific features.
for additional JanusGraph specific features and [kotlin-gremlin-graphql](https://github.com/pm-dev/kotlin-gremlin-ogm/tree/master/kotlin-gremlin-graphql)


#### Basic Usage:
Expand Down Expand Up @@ -42,26 +51,19 @@ Traverse an Edge
graphMapper.traverse(friends from michael).fetch() // returns list: [ dwight ]
graphMapper.traverse(friends from dwight).fetch() // returns list: [ michael ]

More complex examples can be seen in the [starwars example project](https://github.com/pm-dev/kotlin-gremlin-ogm/tree/master/example), which
can be explored interactively using GraphiQL. From this directory, run:

./gradlew :example:run

Then load `http://localhost:5000/graphiql.html`


#### Installation:

- Gradle

compile 'com.github.pm-dev:kotlin-gremlin-ogm:0.17.0'
compile 'com.github.pm-dev:kotlin-gremlin-ogm:0.18.0'

- Maven

<dependency>
<groupId>com.github.pm-dev</groupId>
<artifactId>kotlin-gremlin-ogm</artifactId>
<version>0.17.0</version>
<version>0.18.0</version>
</dependency>
Expand Down Expand Up @@ -178,17 +180,3 @@ a special `UUID` token. For example if the names `Set` was empty:

Licensed under the Apache Software License 2.0.
This code is in no way affiliated with, authorized, maintained, sponsored or endorsed by the Apache Software Foundation.


#### Future improvements to consider:

- Deleting edges & vertices:
Gremlin supports removing edges and vertices, however, I haven't built this yet because removing data
from the graph is not typically a good idea. It's a better pattern to introduce properties that can be used
in filtering. This could still be useful however, when doing a migration.

- Default property values:
If you wanted to add a non-nullable property to a vertex, you would first have to add the property as nullable,
migrate all vertices in the graph to have a value for that property, then change the property to non-nullable.
However, if migrating all vertices is too costly in terms of time or cpu, this could potentially be avoided by
specifying a value (or function producing a value) to use when null is loaded from the graph for a non-nullable property.
3 changes: 2 additions & 1 deletion example/src/main/kotlin/starwars/models/Droid.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package starwars.models
import org.apache.tinkerpop.gremlin.ogm.annotations.Element
import org.apache.tinkerpop.gremlin.ogm.annotations.ID
import org.apache.tinkerpop.gremlin.ogm.annotations.Property
import org.janusgraph.ogm.annotations.Indexed
import org.apache.tinkerpop.gremlin.ogm.annotations.defaults.DefaultString
import java.time.Instant

@Element(label = "Droid")
Expand All @@ -22,6 +22,7 @@ internal class Droid(
appearsIn: Set<Episode>,

@Property(key = "primaryFunction")
@DefaultString("Unknown Function")
val primaryFunction: String
) : Character(
id = id,
Expand Down
10 changes: 9 additions & 1 deletion example/src/main/kotlin/starwars/models/Human.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package starwars.models

import org.apache.tinkerpop.gremlin.ogm.annotations.defaults.DefaultValue
import org.apache.tinkerpop.gremlin.ogm.annotations.Element
import org.apache.tinkerpop.gremlin.ogm.annotations.ID
import org.apache.tinkerpop.gremlin.ogm.annotations.Property
import java.time.Instant
import java.util.function.Supplier

@Element(label = "Human")
internal class Human(
Expand All @@ -15,6 +17,7 @@ internal class Human(
createdAt: Instant,

@Property(key = "name")
@DefaultValue(DefaultName::class)
name: Name,

@Property(key = "appearsIn")
Expand All @@ -28,6 +31,11 @@ internal class Human(
name = name,
appearsIn = appearsIn
) {
companion object
companion object {

class DefaultName: Supplier<Name> {
override fun get() = Name(first = "Unknown", last = "Name")
}
}
}

4 changes: 2 additions & 2 deletions kotlin-gremlin-graphql/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ apply plugin: 'maven-publish'
apply plugin: 'signing'
apply plugin: 'com.github.ben-manes.versions'

version = '0.17.0'
version = '0.18.0'
sourceCompatibility = 1.8
compileKotlin {
kotlinOptions {
Expand Down Expand Up @@ -83,7 +83,7 @@ publishing {
customizePom(pom)
groupId 'com.github.pm-dev'
artifactId 'kotlin-gremlin-graphql'
version '0.17.0'
version '0.18.0'

from components.java

Expand Down
4 changes: 2 additions & 2 deletions kotlin-gremlin-ogm/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ apply plugin: 'signing'
apply plugin: 'com.github.ben-manes.versions'
apply plugin: 'jacoco'

version = '0.17.0'
version = '0.18.0'
sourceCompatibility = 1.8
compileKotlin {
kotlinOptions {
Expand Down Expand Up @@ -87,7 +87,7 @@ publishing {
customizePom(pom)
groupId 'com.github.pm-dev'
artifactId 'kotlin-gremlin-ogm'
version '0.17.0'
version '0.18.0'

from components.java

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.apache.tinkerpop.gremlin.ogm.annotations.defaults

import java.lang.annotation.Inherited

/**
* Annotation that, when used with the @Property annotation, specifies a value to use when
* the graph does not have a value for the given parameter. This can be useful as an alternative to a migration
* when adding a non-nullable property to an Element.
*/
@Retention(value = AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
@Inherited
annotation class DefaultBoolean(

/**
* The value to use when deserializing this property from the graph, but the graph has no value for this
* property.
*/
val value: Boolean)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.apache.tinkerpop.gremlin.ogm.annotations.defaults

import java.lang.annotation.Inherited

/**
* Annotation that, when used with the @Property annotation, specifies a value to use when
* the graph does not have a value for the given parameter. This can be useful as an alternative to a migration
* when adding a non-nullable property to an Element.
*/
@Retention(value = AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
@Inherited
annotation class DefaultByte(

/**
* The value to use when deserializing this property from the graph, but the graph has no value for this
* property.
*/
val value: Byte)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.apache.tinkerpop.gremlin.ogm.annotations.defaults

import java.lang.annotation.Inherited

/**
* Annotation that, when used with the @Property annotation, specifies a value to use when
* the graph does not have a value for the given parameter. This can be useful as an alternative to a migration
* when adding a non-nullable property to an Element.
*/
@Retention(value = AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
@Inherited
annotation class DefaultChar(

/**
* The value to use when deserializing this property from the graph, but the graph has no value for this
* property.
*/
val value: Char)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.apache.tinkerpop.gremlin.ogm.annotations.defaults

import java.lang.annotation.Inherited

/**
* Annotation that, when used with the @Property annotation, specifies a value to use when
* the graph does not have a value for the given parameter. This can be useful as an alternative to a migration
* when adding a non-nullable property to an Element.
*/
@Retention(value = AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
@Inherited
annotation class DefaultDouble(

/**
* The value to use when deserializing this property from the graph, but the graph has no value for this
* property.
*/
val value: Double)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.apache.tinkerpop.gremlin.ogm.annotations.defaults

import java.lang.annotation.Inherited

/**
* Annotation that, when used with the @Property annotation, specifies a value to use when
* the graph does not have a value for the given parameter. This can be useful as an alternative to a migration
* when adding a non-nullable property to an Element.
*/
@Retention(value = AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
@Inherited
annotation class DefaultFloat(

/**
* The value to use when deserializing this property from the graph, but the graph has no value for this
* property.
*/
val value: Float)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.apache.tinkerpop.gremlin.ogm.annotations.defaults

import java.lang.annotation.Inherited

/**
* Annotation that, when used with the @Property annotation, specifies a value to use when
* the graph does not have a value for the given parameter. This can be useful as an alternative to a migration
* when adding a non-nullable property to an Element.
*/
@Retention(value = AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
@Inherited
annotation class DefaultInt(

/**
* The value to use when deserializing this property from the graph, but the graph has no value for this
* property.
*/
val value: Int)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.apache.tinkerpop.gremlin.ogm.annotations.defaults

import java.lang.annotation.Inherited

/**
* Annotation that, when used with the @Property annotation, specifies a value to use when
* the graph does not have a value for the given parameter. This can be useful as an alternative to a migration
* when adding a non-nullable property to an Element.
*/
@Retention(value = AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
@Inherited
annotation class DefaultLong(

/**
* The value to use when deserializing this property from the graph, but the graph has no value for this
* property.
*/
val value: Long)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.apache.tinkerpop.gremlin.ogm.annotations.defaults

import java.lang.annotation.Inherited

/**
* Annotation that, when used with the @Property annotation, specifies a value to use when
* the graph does not have a value for the given parameter. This can be useful as an alternative to a migration
* when adding a non-nullable property to an Element.
*/
@Retention(value = AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
@Inherited
annotation class DefaultShort(

/**
* The value to use when deserializing this property from the graph, but the graph has no value for this
* property.
*/
val value: Short)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.apache.tinkerpop.gremlin.ogm.annotations.defaults

import java.lang.annotation.Inherited

/**
* Annotation that, when used with the @Property annotation, specifies a value to use when
* the graph does not have a value for the given parameter. This can be useful as an alternative to a migration
* when adding a non-nullable property to an Element.
*/
@Retention(value = AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
@Inherited
annotation class DefaultString(

/**
* The value to use when deserializing this property from the graph, but the graph has no value for this
* property.
*/
val value: String)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.apache.tinkerpop.gremlin.ogm.annotations.defaults

import java.lang.annotation.Inherited
import java.util.function.Supplier
import kotlin.reflect.KClass

/**
* Annotation that, when used with the @Property annotation, specifies a value to use when
* the graph does not have a value for the given parameter. This can be useful as an alternative to a migration
* when adding a non-nullable property to an Element.
*/
@Retention(value = AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
@Inherited
annotation class DefaultValue(

/**
* A class that can supply the default value for the parameter this annotation is placed on.
* The supplied value must never be null.
*/
val supplier: KClass<out Supplier<out Any>>)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.apache.tinkerpop.gremlin.ogm.exceptions

import kotlin.reflect.KParameter

internal class NullablePropertyWithDefault(
parameter: KParameter
) : AnnotationException(
"Parameter with @DefaultVaule annotation marked nullable. It's pretty odd to have an @Property that allows null " +
"when the Element is created and serialized to the graph, but is given a default value when there's " +
"no value for the @Property when deserializing from the graph. Most likely this parameter should be " +
"non-nullable." +
"Parameter: ${parameter.name}."
)
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ internal class PropertyDeserializer<out T>(
) : Mapper<SerializedProperty?, Any?> {

override fun invoke(from: SerializedProperty?): Any? {
if (from == null && propertyDescription.default != null) {
return propertyDescription.default.get()
}
if (propertyDescription.mapper != null && from != null) {
return propertyDescription.mapper.inverseMap(from)
}
Expand Down
Loading

0 comments on commit e50d664

Please sign in to comment.