JVM Advent 2017

The JVM Programming Advent Calendar

Get any image metadata with Spring Boot and Docker

This is the second part of a series of three articles, but I decided to condensate in two to give space to another article. The first part is available on this website at https://www.javaadvent.com/2017/12/microservices-architecture.html.

In this part we’re going to develop a Microservice that reads metadata from an image as JSON or XML. I’m using Docker and Java (check part 1 if you need a Java image for Docker), Spring Boot, Gradle, Groovy and the metadata-extractor by drewnoakes.

Gradle

Gradle is a build tool as Maven, and I decided to switch to Gradle after using Maven for many years because I found it richer and more practical, but at the end of the day which build tool you use is up to you – as long as you get what you want and you’re happy. I have to say that moving to Gradle wasn’t that quick as many told me, but I reckon that once I understood the fundamentals it was a joy so in my opinion it totally worths to spend some time to learn this “new” tool.

I’ll get back to Gradle at the end of this article with the full build script.

Groovy

The Microservice is written with Groovy but don’t worry: you’ll get everything even with no knowledge of Groovy!

In Groovy any item is public by default, so the classes and method that you’ll see with a modifier are public.

Metadata-extractor

I’m not digging into this library because there is enough documentation at https://github.com/drewnoakes/metadata-extractor. The important point about this library is that allows to read metadata from several types of image.

To use in our project we need to add the following line to our Gradle build script:

compile group: 'com.drewnoakes', name: 'metadata-extractor', version: '2.10.0'

We’ll use ImageMetadataReader to get the Metadata using a Controller responding /metadata.

Spring Boot

Almost everybody working with Java knows Spring, despite not everyone use it. Spring is also known to the lengthy of setting a new project and I know many people bringing almost the same configuration for any new project. It might work in some situations, but the risk is to bring things that are not necessary. ** Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”. We take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. Most Spring Boot applications need very little Spring configuration. **(From https://projects.spring.io/spring-boot/) I couldn’t describe it better, and it actually does what is says on the tin.

To use Spring boot in out project we need to add the following lines to our Gradle build script:

buildscript {

    ext {
        springBootVersion = '1.5.1.RELEASE'
    }

    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }

}

apply plugin: 'org.springframework.boot'

and define some dependencies:

dependencies {

compile 'org.springframework.boot:spring-boot-starter-groovy-templates'
compile 'org.springframework.boot:spring-boot-starter-web'
compile 'org.springframework.boot:spring-boot-starter-actuator'
compile 'org.springframework.boot:spring-boot-starter-logging'

}

All that we need to run our Microservice is this class:

package com.daftano.metadata

import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication

@SpringBootApplication
class MetadataApplication {

    static void main(String[] args) {
        SpringApplication.run(MetadataApplication.class, args)
    }

}

And Spring Boot will configure all the dependencies needed. How Spring Boot knows what has to load and configure depends on the libraries loaded in the application (in our case those configured in the Gradle dependencies block).

Spring Controller

Our Microservice is a REST service with a single controller named MetadataController. All that we need to do to instruct Spring to listen for requests to /metadata is using these two annotations:

@Controller
@RequestMapping("/metadata")
class MetadataController

That’s it.

Another configuration that we want to do is to define the parameters we want to get from a request and we do that with three more annotations:

@RequestMapping(method= RequestMethod.GET)
@ResponseBody Metadata getMetadata(@RequestParam(value="url") String urlAddress)

Whit those we inform Spring that getMetadata only allows GET requests and requires a parameter named url, which is the URL of the image that we want to use to extract Metadata. It actually is an URI, but as a Microservice you’re using it to read images from the Internet.

What we really need to do in this method is to call

ImageMetadataReader.readMetadata(url.openStream())

Where url is a polished urlAddress.

Microservice In action

What else? Well, we are actually done!

All you need to do to extract metadata from my Gravatar is run the service and go to

http://hostname:8080/metadata?url=https://secure.gravatar.com/avatar/20f152f6504953476654270f77f1a0fe?s=160

To get the following output:

{
    "directories": [
        {
            "name": "JPEG",
            "imageWidth": 160,
            "imageHeight": 160,
            "numberOfComponents": 3,
            "empty": false,
            "parent": null,
            "errors": [
            ],
            "errorCount": 0,
            "tags": [
                {
                    "tagType": -3,
                    "tagTypeHex": "0xfffffffd",
                    "directoryName": "JPEG",
                    "tagName": "Compression Type",
                    "description": "Baseline"
                },
                {
                    "tagType": 0,
                    "tagTypeHex": "0x0000",
                    "directoryName": "JPEG",
                    "tagName": "Data Precision",
                    "description": "8 bits"
                },
                {
                    "tagType": 1,
                    "tagTypeHex": "0x0001",
                    "directoryName": "JPEG",
                    "tagName": "Image Height",
                    "description": "160 pixels"
                },
                {
                    "tagType": 3,
                    "tagTypeHex": "0x0003",
                    "directoryName": "JPEG",
                    "tagName": "Image Width",
                    "description": "160 pixels"
                },
                {
                    "tagType": 5,
                    "tagTypeHex": "0x0005",
                    "directoryName": "JPEG",
                    "tagName": "Number of Components",
                    "description": "3"
                },
                {
                    "tagType": 6,
                    "tagTypeHex": "0x0006",
                    "directoryName": "JPEG",
                    "tagName": "Component 1",
                    "description": "Y component: Quantization table 0, Sampling factors 2 horiz/2 vert"
                },
                {
                    "tagType": 7,
                    "tagTypeHex": "0x0007",
                    "directoryName": "JPEG",
                    "tagName": "Component 2",
                    "description": "Cb component: Quantization table 1, Sampling factors 1 horiz/1 vert"
                },
                {
                    "tagType": 8,
                    "tagTypeHex": "0x0008",
                    "directoryName": "JPEG",
                    "tagName": "Component 3",
                    "description": "Cr component: Quantization table 1, Sampling factors 1 horiz/1 vert"
                }
            ],
            "tagCount": 8
        },
        {
            "name": "JpegComment",
            "empty": false,
            "parent": null,
            "errors": [
            ],
            "errorCount": 0,
            "tags": [
                {
                    "tagType": 0,
                    "tagTypeHex": "0x0000",
                    "directoryName": "JpegComment",
                    "tagName": "JPEG Comment",
                    "description": "CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), quality = 90\n"
                }
            ],
            "tagCount": 1
        },
        {
            "name": "JFIF",
            "imageWidth": 1,
            "imageHeight": 1,
            "resUnits": 0,
            "resY": 1,
            "resX": 1,
            "version": 257,
            "empty": false,
            "parent": null,
            "errors": [
            ],
            "errorCount": 0,
            "tags": [
                {
                    "tagType": 5,
                    "tagTypeHex": "0x0005",
                    "directoryName": "JFIF",
                    "tagName": "Version",
                    "description": "1.1"
                },
                {
                    "tagType": 7,
                    "tagTypeHex": "0x0007",
                    "directoryName": "JFIF",
                    "tagName": "Resolution Units",
                    "description": "none"
                },
                {
                    "tagType": 8,
                    "tagTypeHex": "0x0008",
                    "directoryName": "JFIF",
                    "tagName": "X Resolution",
                    "description": "1 dot"
                },
                {
                    "tagType": 10,
                    "tagTypeHex": "0x000a",
                    "directoryName": "JFIF",
                    "tagName": "Y Resolution",
                    "description": "1 dot"
                },
                {
                    "tagType": 12,
                    "tagTypeHex": "0x000c",
                    "directoryName": "JFIF",
                    "tagName": "Thumbnail Width Pixels",
                    "description": "0"
                },
                {
                    "tagType": 13,
                    "tagTypeHex": "0x000d",
                    "directoryName": "JFIF",
                    "tagName": "Thumbnail Height Pixels",
                    "description": "0"
                }
            ],
            "tagCount": 6
        },
        {
            "name": "Huffman",
            "numberOfTables": 4,
            "typical": true,
            "optimized": false,
            "empty": false,
            "parent": null,
            "errors": [
            ],
            "errorCount": 0,
            "tags": [
                {
                    "tagType": 1,
                    "tagTypeHex": "0x0001",
                    "directoryName": "Huffman",
                    "tagName": "Number of Tables",
                    "description": "4 Huffman tables"
                }
            ],
            "tagCount": 1
        }
    ],
    "directoryCount": 4
}

So far it was very easy, but if you remember, I said the I prefer to support both JSON and XML in my Microservices, and to get the XML reply all we need to do is to request /metadata.xml. The only thing that we needed to add to our project is the following dependency and Spring Boot did the rest of the job:

compile group: 'com.fasterxml.jackson.dataformat', name:'jackson-dataformat-xml', version: '2.6.3'

The build script

This is the full build script that we need to run this Service

buildscript {
    ext {
        springBootVersion = '1.5.1.RELEASE'
    }

    repositories {
        mavenCentral()
    }

    dependencies {
       classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }

}

apply plugin: 'groovy'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'

jar {
    baseName = ‘image-metadata'
    version = '1.0.0'
    manifest {
        attributes("Implementation-Title": "Gradle",
                   "Implementation-Version": version,
                   "Main-Class": "com.daftano.metadata.MetadataApplication”
        )
    }
}

repositories {
    mavenCentral()
}

dependencies {
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-groovy-templates'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-web'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-actuator'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-logging'
    compile group: 'org.slf4j', name: 'jcl-over-slf4j', version: '1.7.22'
    compile group: 'com.drewnoakes', name: 'metadata-extractor', version: '2.10.0'
    compile group: 'com.fasterxml.jackson.dataformat', name:'jackson-dataformat-xml', version: '2.6.3'

    testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test'

}

Docker

Now that we have a working application, lets build our docker image.

FROM daftano/java:8

LABEL maintainer-twitter="@daftano"

EXPOSE 8080

ADD build/image-metadata-1.0.0.jar /opt/image-metadata.jar

ENTRYPOINT java -jar /opt/image-metadata.jar

I’ve already talked of this image when I introduced the HelloWorld service, but this time I added the EXPOSE.

Build the Docker image and run it, and you have the Microservice to extract metadata that’s 63K.

Author: Davide Fiorentino lo Regio

Known to investigate, prototype and deliver new and innovative solutions previously considered impossible, his life philosophy is “Make yourself better to make the best of everything and achieve the impossible.”
Engaged in multiple projects for large organizations and consultanting for the United Nations, he won the “Premio Colosseo” in Rome, Italy

Next Post

Previous Post

Leave a Reply

© 2018 JVM Advent 2017

Theme by Anders Norén

%d bloggers like this: