Tech notes for java


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!

java-jersey java-bean-validation scala java

Docker: install Java8 automatically on ubuntu/debian

If you want to create a docker image with Java, then there's a problem: it'll ask you manually confirm that you agree to the terms and conditions.

To automatically do this, add some debconf-set-selections to your script.

So the steps are now update, install software properties utils, add the webupd8team repo, set the debconf selections, update again, install java.

apt-get update
apt-get -y install software-properties-common
add-apt-repository -y ppa:webupd8team/java
echo debconf shared/accepted-oracle-license-v1-1 select true | debconf-set-selections
echo debconf shared/accepted-oracle-license-v1-1 seen true | debconf-set-selections
apt-get update
apt-get -y install oracle-java8-installer
docker java

Gradle: Hello world Java with a fat jar

Create your build.gradle with your dependencies and a jar section that collects all your libraries into the jar and sets the Main class file.

apply plugin: 'java'

repositories {
   mavenCentral()
}

dependencies {
    compile 'org.mindrot:jbcrypt:0.3m'
}

jar {
    from {
        (configurations.runtime).collect {
            it.isDirectory() ? it : zipTree(it)
        }
    }
    manifest {
        attributes("Main-Class": "Main" )
    }
}

Now create a basic hello world, using the library we imported:

import org.mindrot.jbcrypt.BCrypt;

public class Main {
        public static void main(String[] args) {
                String password = BCrypt.hashpw("password", BCrypt.gensalt(10));
                System.out.println(password);
        }
}

Now build and run your jar:

$ gradle clean build && java -jar build/libs/THE_NAME_OF_YOUR_JAR.jar
...
$2a$10$R6q8LOed8LqXCOIhBnzhMecyebv/8v1urKjU76JMJGUctnZ8VkyZu
java gradle gradle-jar java-jar

A single page Java/Jetty/Jersey REST app with no web.xml descriptor

First create the build.gradle file in your directory:

apply plugin: 'war'
apply plugin: 'eclipse'

repositories {
   mavenCentral()
}

dependencies {
    compile 'org.glassfish.jersey.media:jersey-media-json-jackson:2.16'
    compile 'org.glassfish.jersey.bundles:jaxrs-ri:2.16'
}

compileJava {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
}

Then, in src/main/java/com/example, create App.java:

package com.example;

import java.util.ArrayList;
import java.util.List;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.glassfish.jersey.server.ResourceConfig;

@ApplicationPath("rest") 
public class App extends ResourceConfig {
  public App() { packages("com.example"); }

  @Path("example")
  public static class Hello {
    
      @GET
      @Produces(MediaType.APPLICATION_JSON)
      public List<String> example() {
        List<String> l = new ArrayList<>();
        l.add("one"); l.add("two");
        return l; 
      }

  }  
  
}

Now

  1. Create the war file with gradle
  2. Download the Jetty runner jar
  3. Run the war file with the jetty runner (takes about 15 seconds to init usually)
  4. Use curl to test the app

From the command line:

gradle build war
wget http://central.maven.org/maven2/org/eclipse/jetty/jetty-runner/9.3.0.M1/jetty-runner-9.3.0.M1.jar
java -jar jetty-runner-9.3.0.M1.jar --port 8081 build/libs/your-directory-name.war
curl localhost:8081/rest/example && echo

The above curl command should print:

["one","two"]
java-jetty java-jersey gradle java

Java Servlet: Servering static content

If you want to server static content -- like PNGs, Javascript, etc -- you need to explicitly tell your server this in its web.xml

Edit: I've changed this article due to an error and infelicitiy:

Add this file somewhere, and, voila, all the files in your res/ directory (in src/main/webapp/res in the gradle directory structure, that is) will be served.

@WebServlet("res/*")
public class ResourcesServlet extends HttpServlet {
  private static final long serialVersionUID = 1L;

  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    RequestDispatcher rd = getServletContext().getNamedDispatcher("default");
    HttpServletRequest wrapped = new HttpServletRequestWrapper(req) {
      public String getServletPath() {
        return "/res/";
      }
    };
    rd.forward(wrapped, resp);
  }
}
java java-servlet java-servlet-static-content

Page 2 of 16
prev next
Click me