NewFiveFour | Blog | Portfolio


Android: Upload to HockeyApp on every Travis-CI build

I assume you have setup HockeyApp, that you have setup Travis-CI and that you have a build which is signed with a constant keystore signature.

If our build is created by the above link, we will need the CI_KEYSTORE_PASSWORD environmental variable to sign the build. Go to your application settings in Travis-CI and create the environmental variable. (Ensure you have not toggled “display value in build log” for privacy)

Now go to account settings on the HockeyApp site, and then click API Tokens and create one with upload access to your application. With that, go to your application on Travis-CI and add another environmental variable, HOCKEYAPP_TOKEN.

Now we have HOCKEYAPP_TOKEN available in our Travis-CI environment, we can add a curl command to our .travis.yml script section to upload our signed build to HockeyApp:

script:
  ...
  - >
    curl
    -F "status=2"
    -F "notify=1"
    -F "notes=Some new features and fixed bugs."
    -F "notes_type=0"
    -F "ipa=@app/build/outputs/apk/YOUR_SIGNED_BUILD.apk"
    -H "X-HockeyAppToken: $HOCKEYAPP_TOKEN"
    https://rink.hockeyapp.net/api/2/apps/upload

Now on every build, we’ll upload the APK to HockeyApp, using our HOCKEYAPP_TOKEN, for distribution to our testers.

Note: We are not automatically updating the android versionCode, although putting versionCode System.getenv("TRAVIS_BUILD_NUMBER") as Integer ?: 999 will do that, and the version notes are static, which we can approach in another tutorial.

travis-ci hockeyapp android

Android: A debug build signed with a constant keystore

Each debug build of your android application will be signed with the debug keystore.

This is a problem because in continous integration environments, like travis-ci or docker, the debug keystore is regenerated on each creation of the environment.

This means if someone has a debug version of your application, they won’t be able to upgrade to a more recent version, because the keystores will be different.

To get around this, let’s create and add a keystore to our repository for continuous integration environments. Create this in the app/ directory. And ensure the key password is the same as the keystore password.

keytool -genkey -v -keystore ci-key.keystore -alias ci-key-alias -keyalg RSA -keysize 2048 -validity 10000

Now in your app/build.gradle file add this to the android block:

signingConfigs {
  ci {
    keyAlias "ci-key-alias"
    keyPassword System.getEnv("CI_KEYSTORE_PASSWORD")
    storeFile "ci-key.keystore"
    storePassword System.getEnv("CI_KEYSTORE_PASSWORD")
  }
}

We give it the alias and reference to the file we created. We’ll get the keystore key from the environment, one injected into the contiuous integration environment, for example.

In the same android block, create a new build variant, initalised as the debug variable, called debug_with_ci_keystore:

buildTypes {
    debug_with_ci_keystore.initWith(buildTypes.debug)
    debug_with_ci_keystore {
        if(System.getenv("CI_KEYSTORE_PASSWORD")) signingConfig signingConfigs.ci
        minifyEnabled false
    }
...
}

We give the variant the ci signing config above if we have the correct environment variable, CI_KEYSTORE_PASSWORD. This means, if we don’t, we only create app/build/outputs/apk/app-debug_with_ci_keystore-unsigned.apk, not the signed app-debug_with_ci_keystore.apk.

Now on ./gradlew build, if we have CI_KEYSTORE_PASSWORD set in our environment, we will create app/build/outputs/apk/app-debug_with_ci_keystore.apk, which will have a constant keystore.

The above obviously works for a release signing: just change the build type to release, although you may not want to keep your release keystore in your repository.

android android-keystore keytool

Jersey: Custom bean validation / ConstraintViolationException

With bean validation in Jersey, you get JSON errors like this:

[
  {
    "message":"may not be null",
    "messageTemplate":"{javax.validation.constraints.NotNull.message}",
    "path":"Hello.register.arg0.email",
    "invalidValue":null
  }
]

That’s great, but sometimes you have errors that appear after bean validation, such as when someone tries to register as an existing user.

You will only know about this when you talk to your data stores of existing users.

It would be nice to keep the above error format, but talk about more general errors. You do this by creating a custom ConstraintViolation and throwing a ConstraintViolationException.

The ConstraintViolationException takes a HashSet of ConstraintViolation. Let’s create a dummy ConstraintViolation in Scala (the Java version is obviously just about the same):

new ConstraintViolation[Object] {
  def getConstraintDescriptor(): ConstraintDescriptor[_] = null
  def getExecutableParameters(): Array[Object] = null
  def getExecutableReturnValue(): Object = null
  def getInvalidValue(): Object = "The invalid valid"
  def getLeafBean(): Object = null
  def getMessage(): String = "Something went wrong"
  def getMessageTemplate(): String = ""
  def getPropertyPath(): javax.validation.Path = new MyPropertyPath(argPath)
  def getRootBean(): Object = obj
  def getRootBeanClass(): Class[Object] = classOf[Object]
  def unwrap[U](t: Class[U]): U = t.newInstance
}

Most of the values above are empty, except for getInvalidValue, getMessage, getPropertyPath, getRootBean and getRootBeanClass

getInvalidValue displays the invalid value in the JSON above. getMessage displays the messages. getPropertyPath helps display path above: it’s just a dumy object with an dumy iterator and we use its toString method to fill out the rest of the "path"

The getRootBean gives us the name at the start of the "path" json reply. getRootBeanClass and unwrap need to be non-null but don’t seem to affect the JSON output.

Let’s put this altogether:

// Dumpy Path object: we only want its "toString"
class MyPropertyPath(var pathName: String) extends javax.validation.Path {
  def iterator():java.util.Iterator[javax.validation.Path.Node] = {
    return new java.util.Iterator[javax.validation.Path.Node] {
      def hasNext(): Boolean = false
      def next(): javax.validation.Path.Node = null
    }
  }
  override def toString:String = pathName
}

def customValidationErrorMessage(obj: Object, argPath: String, message: String, invalid: Object) = {
  var hs = new HashSet[ConstraintViolation[_]]
  hs.add(new ConstraintViolation[Object] {
    def getConstraintDescriptor(): ConstraintDescriptor[_] = null
    def getExecutableParameters(): Array[Object] = null
    def getExecutableReturnValue(): Object = null
    def getInvalidValue(): Object = invalid
    def getLeafBean(): Object = null
    def getMessage(): String = message
    def getMessageTemplate(): String = ""
    def getPropertyPath(): javax.validation.Path = new P(argPath)
    def getRootBean(): Object = obj
    def getRootBeanClass(): Class[Object] = classOf[Object]
    def unwrap[U](t: Class[U]): U = t.newInstance
  })
  throw new ConstraintViolationException("", hs)
}

If we call customValidationErrorMessage(register, "username", "Duplicate user", "dave") in a Jersey method, we’ll get this returned:

[
  {
    "message":"Duplicate user",
    "messageTemplate":"",
    "path":"RegisterUser.username",
    "invalidValue":"dave"
  }
]

Voila!

jersey bean-validation scala

Jersey: Bean validation with JSON return objects

Here’s how to get JSON error messages detailing validation errors in your REST input.

I’m doing this in Scala, but the Java version is obviously nearly the same.

First let’s look at our gradle dependencies, jaxrs, jetty, jackson, bean validation and our default logger.

 compile 'org.glassfish.jersey.bundles:jaxrs-ri:2.23.1'
 compile 'org.glassfish.jersey.containers:jersey-container-jetty-http:2.23.1'
 compile 'org.glassfish.jersey.media:jersey-media-json-jackson:2.16'
 compile 'org.glassfish.jersey.ext:jersey-bean-validation:2.23.2'
 compile "org.slf4j:slf4j-simple:1.6.1"

We need to turn bean validation error messages on in our ResourceConfig:

new ResourceConfig() {
  register(classOf[Hello])
  property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true)
}

We’ll define a class with @NonNull contraints, specifying Jackon will deserialise looking at fields not getters and setters for convenience.

@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
class RegisterUser() {
  @NotNull var email: String = ""
  @NotNull var username: String = ""
  @NotNull var password: String = ""
}

Now the standard Jersey resource class, but now with the @Valid annotation on the POST object.

@Path("/") class Hello {
  @Path("register") @POST @Produces(Array(MediaType.APPLICATION_JSON))
  def register(@Valid register: RegisterUser) : RegisterUser = return register
}

Let’s put it altogether:

import javax.validation.constraints.{NotNull};
import javax.validation.{Valid};
import javax.ws.rs.core.{MediaType, UriBuilder}
import javax.ws.rs.{ApplicationPath, Path, Produces, POST}
import org.glassfish.jersey.server.{ResourceConfig, ServerProperties}
import org.glassfish.jersey.jetty.JettyHttpContainerFactory
import com.fasterxml.jackson.annotation.{JsonAutoDetect}

object rest {

  @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
  class RegisterUser() {
    @NotNull var email: String = ""
    @NotNull var username: String = ""
    @NotNull var password: String = ""
  }

  @Path("/") class Hello {
    @Path("register") @POST @Produces(Array(MediaType.APPLICATION_JSON))
    def register(@Valid register: RegisterUser) : RegisterUser = return register
  }

  def main(args: Array[String]) : Unit = {
    JettyHttpContainerFactory.createServer(
      UriBuilder.fromUri("http://localhost/").port(8901).build(),
      new ResourceConfig() {
        register(classOf[Hello])
        property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true)
      }
    )
  }

}

Let’s run the application using scala -cp "dependencies/*" valid.scala (dependencies is where my dependencies are. See this.

Here’s the curl command detailing the error in JSON:

curl -H "accept: application/json" -H "content-type: application/json" -X POST -d '{"username": "me", "email": null, "password":"p"}' http://localhost:8901/register
[{"message":"may not be null","messageTemplate":"{javax.validation.constraints.NotNull.message}","path":"Hello.register.arg0.email","invalidValue":null}]
jersey bean-validation jackson scala

Gradle: Bintray tutorial

We can use gradle to upload our project as a maven repository to bintray, and by extension jcenter and mavenCentral.

Sign up to at https://bintray.com. Create a repository, let’s say we call it wonderful.

Now let’s create the build.gradle file giving a buildscript repository, adding the bintray plugin, and applying the java, maven and maven-publish plugins.

buildscript {
  repositories {
    jcenter()
  }
}

plugins {
  id "com.jfrog.bintray" version "1.7"
}

apply plugin: 'java'
apply plugin: 'maven'
apply plugin: 'maven-publish'

Now let’s add the standard version, archivesBaseName, group and description. We’ll reference this as project.version etc later on.

archivesBaseName = 'jersey-json-wadl'
group = 'com.newfivefour'
version = '0.0.1'
description = 'Something, innit'

The maven-publish plugin allows us to define a publishing.publications maven block. This defines what we’re publishing (java components), the groupId, the artifactId and the version. The publication is called MyPublication.

publishing {
  publications {
      MyPublication(MavenPublication) {
          from components.java
          groupId = project.group
          artifactId = project.archivesBaseName
          version = project.version
      }
  }
}

Now let’s create the bintray block. We get our bintray user and api key from the system environment, and we reference the name of the publication created above, and we tell bintray to publish the artifact, instead of holding it on bintray waiting for us to press ‘publish’.

bintray {
    user = System.getenv('BINTRAY_USER')
    key = System.getenv('BINTRAY_KEY')
    publications = ['MyPublication']
    publish = true
    pkg ...
}

The pkg part is where things get interesting:

    pkg {
        repo = 'wondeful'
        name = 'somerandomname'
        licenses = ['Apache-2.0']
        vcsUrl = 'https://github.com/newfivefour/jerseyjsonwadl.git'
        version {
            name = project.version
            desc = project.description
            released  = new Date()
        }
    }

We define the repo name we created on bintray. We give it a name, or a package as it will appear on the bintray UI. We also specify the licence and VCS url. The version block reuses the version, desc from our project and we set the release date to now.

Running ./gradlew --no-daemon bintrayUpload (we’re using --no-daemon since the daemon is buggy and keeps old values) will upload our repository. Wait a few seconds, and our maven repository should start to appear at https://YOURUSERNAME.bintray.com/wonderful/. You should see in the bintray web interface, too.

Now, in another gradle project, if you define a repository via maven { url "http://dl.bintray.com/YOURUSERNAME/wonderful" } you can call a dependency called compile 'com.newfivefour:jersey-json-wadl:0.0.1'.

gradle bintray

Page 1 of 64
Next