SKM IT World

Just another blog about IT


Leave a comment

Strategy Pattern Revisited With Spring

This blog post wants to show another approach how to implement the Strategy Pattern with dependency injection. As DI framework, I choose Spring framework

From Wikipedia

Firstly, let’s have a look how the Strategy Pattern is implemented in the classic way.
As starting point, we have a HeroController that should add a hero in HeroRepository depends on which repository was chosen by the user.

package com.github.sparsick.springbootexample.hero.universum;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class HeroControllerClassicWay {

    @PostMapping("/hero/new")
    public String addNewHero(@ModelAttribute("newHero") NewHeroModel newHeroModel) {
        HeroRepository heroRepository = findHeroRepository(newHeroModel.getRepository());
        heroRepository.addHero(newHeroModel.getHero());
        return "redirect:/hero";
    }

    private HeroRepository findHeroRepository(String repositoryName) {
        if (repositoryName.equals("Unique")) {
            return new UniqueHeroRepository();
        }

        if(repositoryName.equals(("Duplicate")){
            return new DuplicateHeroRepository();
        }

        throw new IllegalArgumentException(String.format("Find no repository for given repository name [%s]", repositoryName));
    }
}
package com.github.sparsick.springbootexample.hero.universum;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import org.springframework.stereotype.Repository;

@Repository
public class UniqueHeroRepository implements HeroRepository {

    private Set<Hero> heroes = new HashSet<>();

    @Override
    public String getName() {
        return "Unique";
    }

    @Override
    public void addHero(Hero hero) {
        heroes.add(hero);
    }

    @Override
    public Collection<Hero> allHeros() {
        return new HashSet<>(heroes);
    }

}
package com.github.sparsick.springbootexample.hero.universum;

import org.springframework.stereotype.Repository;

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

@Repository
public class DuplicateHeroRepository implements HeroRepository {

    private List<Hero> heroes = new ArrayList<>();

    @Override
    public void addHero(Hero hero) {
        heroes.add(hero);
    }

    @Override
    public Collection<Hero> allHeros() {
        return List.copyOf(heroes);
    }

    @Override
    public String getName() {
        return "Duplicate";
    }
}

This implementation has some pitfalls. The creation of the repository implementations aren’t managed by the Spring Context (it breaks the dependency injection / inverse of control). This will be painful as soon as you want to expand the repository implementation with further feature that need to inject other classes (for example, counting the usage of this class with MeterRegistry).

package com.github.sparsick.springbootexample.hero.universum;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.stereotype.Repository;

@Repository
public class UniqueHeroRepository implements HeroRepository {

    private Set<Hero> heroes = new HashSet<>();
    private Counter addCounter;

    public UniqueHeroRepository(MeterRegistry meterRegistry) {
        addCounter = meterRegistry.counter("hero.repository.unique");
    }

    @Override
    public String getName() {
        return "Unique";
    }

    @Override
    public void addHero(Hero hero) {
        addCounter.increment();
        heroes.add(hero);
    }

    @Override
    public Collection<Hero> allHeros() {
        return new HashSet<>(heroes);
    }

}

It breaks also the separation of concern. When I want to test the controller class, I have no possibility to mock the repository interface easily. So the first idea is to put the creation of repository implementation to the Spring context. The repository implementation are annotated with @Repository annotation. So Spring’s component scan find them.
The next question how to inject them into the controller class. Here, a Spring feature can help. I define a list of HeroRepository in the controller. This list has to be filled during the creation of the controller instance.

package com.github.sparsick.springbootexample.hero.universum;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.List;

@Controller
public class HeroControllerRefactoringStep1 {

    private List<HeroRepository> heroRepositories;

    public HeroControllerRefactoringStep1(List<HeroRepository> heroRepositories) {
        this.heroRepositories = heroRepositories;
    }

    @PostMapping("/hero/new")
    public String addNewHero(@ModelAttribute("newHero") NewHeroModel newHeroModel) {
        HeroRepository heroRepository = findHeroRepository(newHeroModel.getRepository());
        heroRepository.addHero(newHeroModel.getHero());
        return "redirect:/hero";
    }

    private HeroRepository findHeroRepository(String repositoryName) {
        return heroRepositories.stream()
                .filter(heroRepository -> heroRepository.getName().equals(repositoryName))
                .findFirst()
                .orElseThrow(()-> new IllegalArgumentException(String.format("Find no repository for given repository name [%s]", repositoryName)));

    }
}

Spring searches in its context for all implementation of the interface HeroRepostiory and put them all to the list. One disadvantage has this solution, every adding a hero browses the list of HeroRepository to find the right implementation. This can be optimized by creating a map in the controller constructor that has the repository name as key and the corresponded implementation as value.

package com.github.sparsick.springbootexample.hero.universum;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Controller
public class HeroControllerRefactoringStep2 {

    private Map<String, HeroRepository> heroRepositories;

    public HeroControllerRefactoringStep2(List<HeroRepository> heroRepositories) {
        this.heroRepositories = heroRepositoryStrategies(heroRepositories);
    }

    private Map<String, HeroRepository> heroRepositoryStrategies(List<HeroRepository> heroRepositories){
        Map<String, HeroRepository> heroRepositoryStrategies = new HashMap<>();
        heroRepositories.forEach(heroRepository -> heroRepositoryStrategies.put(heroRepository.getName(), heroRepository));
        return heroRepositoryStrategies;
    }

    @PostMapping("/hero/new")
    public String addNewHero(@ModelAttribute("newHero") NewHeroModel newHeroModel) {
        HeroRepository heroRepository = findHeroRepository(newHeroModel.getRepository());
        heroRepository.addHero(newHeroModel.getHero());
        return "redirect:/hero";
    }

    private HeroRepository findHeroRepository(String repositoryName) {
        HeroRepository heroRepository = heroRepositories.get(repositoryName);
        if(heroRepository != null) {
            return  heroRepository;
        }
        throw new IllegalArgumentException(String.format("Find no repository for given repository name [%s]", repositoryName));
    }
}

The final question is what if other classes in the application need the possibility to choose a repository implementation during the runtime. I could copy and paste the private method in each class that have this need or I move the creation of the map to the Spring Context and inject the Map to each class.

package com.github.sparsick.springbootexample.hero;

import com.github.sparsick.springbootexample.hero.universum.HeroRepository;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@SpringBootApplication
public class HeroApplicationRefactoringStep3 {

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

	@Bean
    Map<String, HeroRepository> heroRepositoryStrategy(List<HeroRepository> heroRepositories){
        Map<String, HeroRepository> heroRepositoryStrategy = new HashMap<>();
        heroRepositories.forEach(heroRepository -> heroRepositoryStrategy.put(heroRepository.getName(), heroRepository));
        return heroRepositoryStrategy;
    }
}

package com.github.sparsick.springbootexample.hero.universum;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.Map;

@Controller
public class HeroControllerRefactoringStep3 {

    private Map<String, HeroRepository> heroRepositoryStrategy;

    public HeroControllerRefactoringStep3(Map<String, HeroRepository> heroRepositoryStrategy) {
        this.heroRepositoryStrategy = heroRepositoryStrategy;
    }

    @PostMapping("/hero/new")
    public String addNewHero(@ModelAttribute("newHero") NewHeroModel newHeroModel) {
        HeroRepository heroRepository = findHeroRepository(newHeroModel.getRepository());
        heroRepository.addHero(newHeroModel.getHero());
        return "redirect:/hero";
    }

    private HeroRepository findHeroRepository(String repositoryName) {
        return heroRepositoryStrategy.get(repositoryName);
    }

}

This solution is a little bit ugly, because it isn’t obvious that the Strategy Pattern is used. So the next refactoring step is moving the map of hero repositories to an own component class. Therefore, the bean definition heroRepositoryStrategy in the application configuration can be removed.

package com.github.sparsick.springbootexample.hero.universum;

import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

@Component
public class HeroRepositoryStrategy {

    private Map<String, HeroRepository> heroRepositoryStrategies;

    public HeroRepositoryStrategy(Set<HeroRepository> heroRepositories) {
        heroRepositoryStrategies = createStrategies(heroRepositories);
    }

    HeroRepository findHeroRepository(String repositoryName) {
        return heroRepositoryStrategies.get(repositoryName);
    }

    Set<String> findAllHeroRepositoryStrategyNames () {
        return heroRepositoryStrategies.keySet();
    }

    Collection<HeroRepository> findAllHeroRepositories(){
        return heroRepositoryStrategies.values();
    }


    private Map<String, HeroRepository> createStrategies(Set<HeroRepository> heroRepositories){
        Map<String, HeroRepository> heroRepositoryStrategies = new HashMap<>();
        heroRepositories.forEach(heroRepository -> heroRepositoryStrategies.put(heroRepository.getName(), heroRepository));
        return heroRepositoryStrategies;
    }

}

package com.github.sparsick.springbootexample.hero.universum;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import java.net.Inet4Address;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Controller
public class HeroController {

    private HeroRepositoryStrategy heroRepositoryStrategy;

    public HeroController(HeroRepositoryStrategy heroRepositoryStrategy) {
        this.heroRepositoryStrategy = heroRepositoryStrategy;
    }

    @PostMapping("/hero/new")
    public String addNewHero(@ModelAttribute("newHero") NewHeroModel newHeroModel) {
        HeroRepository heroRepository = heroRepositoryStrategy.findHeroRepository(newHeroModel.getRepository());
        heroRepository.addHero(newHeroModel.getHero());
        return "redirect:/hero";
    }
}

The whole sample is hosted on GitHub.


Leave a comment

Maven Project Setup for Mixing Spock 1.x and JUnit 5 Tests

I create a sample Groovy project for Maven, that mixes Spock tests and JUnit 5 tests in one project. In the next section I’ll describe how to set up such kind of Maven project.

Enable Groovy in the Project

First at all, you have to enable Groovy in your project. One possibility is to add the GMavenPlus Plugin to your project.

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.gmavenplus</groupId>
            <artifactId>gmavenplus-plugin</artifactId>
            <version>1.6.2</version>
            <executions>
                <execution>
                    <goals>
                        <goal>addSources</goal>
                        <goal>addTestSources</goal>
                        <goal>compile</goal>
                        <goal>compileTests</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

The goals addSources and addTestSources add Groovy (test) sources to Maven’s main (test) sources. The default locations are src/main/groovy (for main source) and src/test/groovy (for test source). Goals compile and compileTests compile the Groovy (test) code. If you don’t have Groovy main code, you can omit addSource and compile.

This above configuration is always using the latest released Groovy version. If you want to ensure that a specific Groovy version is used, you have to add the specific Groovy dependency to your classpath.

   <dependencies>
        <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy</artifactId>
            <version>2.5.6</version>
        </dependency>
  </dependencies>

Enable JUnit 5 in the Project

The simplest setup for using JUnit 5 in your project is to add the JUnit Jupiter dependency in your test class path and to configure the correct version of Maven Surefire Plugin (at least version 2.22.0).

    <dependencies>
<!--... maybe more dependencies -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.junit</groupId>
                <artifactId>junit-bom</artifactId>
                <version>${junit.jupiter.version}</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
        <!-- other plugins -->
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.1</version>
            </plugin>
        </plugins>
    </build>

Enable Spock in the Project

Choosing the right Spock dependency depends on which Groovy version you are using in the project. In our case, a Groovy version 2.5. So we need Spock in version 1.x-groovy-2.5 in our test class path.

    <dependencies>
        <!-- more dependencies -->
        <dependency>
            <groupId>org.spockframework</groupId>
            <artifactId>spock-core</artifactId>
            <version>1.3-groovy-2.5</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

Now the expectation is that the Spock tests and the JUnit5 tests are executed in the Maven build. But only the JUnit5 tests are executed by Maven. So what happened?

I started to change the Maven Surefire Plugin version to 2.21.0. Then the Spock tests were executed, but no JUnit5 tests. The reason is that in the version 2.22.0 of Maven Surefire Plugin JUnit4 provider is replaced by JUnit Platform Provider as default. But Spock in version 1.x is based on JUnit4. This will be changed in Spock version 2. This version will be based on the JUnit5 Platform. Thus, for Spock 1.x, we have to add JUnit Vintage dependency to our test class path.

    <dependencies>
        <!-- more dependencies -->
          <dependency>  <!--Only necessary for surefire to run spock tests during the maven build -->
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

This allows running elder JUnit (3/4) tests on the JUnit Platform. With this configuration both, Spock and JUnit 5 tests, are executed in the Maven build.

Links


Leave a comment

Using JUnit 5 In Pre-Java 8 Projects

This post demonstrates how JUnit 5 can be used in pre-Java 8 projects and explains why it could be a good idea.

JUnit 5 requires at least Java 8 as runtime environment, so you want to update your whole project to Java 8. But sometimes there exists reason why you can’t immediately update your project to Java 8. For example, the version of your application server in production only supports Java 7. But an update isn’t be taken quickly because of some issues in your production code.

Now, the question is how can you use JUnit 5 without update your production code to Java 8?

You can set up the Java version separately for production code and for test code .

<!-- in Maven -->
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>7</source>
<target>7</target>
<testSource>8</testSource>
<testTarget>8</testTarget>
</configuration>
</plugin>
</plugins>
</build>
// in Gradle
sourceCompatibility = '7'
targetCompatibility = '7'

compileTestJava {
sourceCompatibility ='8'
targetCompatibility = '8'
}

Precondition is that you use a Java 8 JDK for your build.

If you try to use Java 8 feature in your Java 7 production code, Maven and Gradle will fail the build.

// Maven
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.0:compile (default-compile) on project junit5-in-pre-java8-projects: Compilation failure
[ERROR] /home/sparsick/dev/workspace/junit5-example/junit5-in-pre-java8-projects/src/main/java/Java7Class.java:[8,58] lambda expressions are not supported in -source 7
[ERROR]   (use -source 8 or higher to enable lambda expressions)
// Gradle
> Task :compileJava FAILED
/home/sparsick/dev/workspace/junit5-example/junit5-in-pre-java8-projects/src/main/java/Java7Class.java:8: error: lambda expressions are not supported in -source 7
Function<String, String > java8Feature = (input) -> input;
^
(use -source 8 or higher to enable lambda expressions)
1 error

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':compileJava'.
> Compilation failed; see the compiler error output for details.

Now you can introduce JUnit 5 in your project and start writing test with JUnit 5.

<!-- Maven-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<!-- junit-vintage-engine is needed for running elder JUnit4 test with JUnit5-->
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
// in Gradle
dependencies {
testCompile 'org.junit.jupiter:junit-jupiter-api:5.3.2'
testCompile 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
testCompile 'org.junit.jupiter:junit-jupiter-params:5.3.2'
testCompile 'org.junit.vintage:junit-vintage-engine:5.3.2'
}

Your old JUnit 4 tests need not be migrated, because JUnit 5 has a test engine, that can run JUnit 4 tests with JUnit 5. So use JUnit 5 for new tests and only migrate JUnit 4 tests if you have to touch them anyway.

Although you can’t update your production code to a newer Java version, it has some benefit to update your test code to a newer one.

The biggest benefit is that you can start learning new language feature during your daily work when you write tests. You don’t make the beginner’s mistake in the production code. You have access to new tools that can help improve your tests. For example, in JUnit 5 it’s more comfortable to write parameterized tests than in JUnit 4. In my experience, developer writes rather parameterized test with JUnit 5 than with JUnit 4 in a situation where parameterized test make sense.

The above described technique also works for other Java version. For example, your production code is on Java 11 and you want to use Java 12 feature in your test code. Another use case for this technique could be learning another JVM language like Groovy, Kotlin or Clojure in your daily work. Then use the new language in your test code.

For Maven projects, this approach has one little pitfall. IntelliJ IDEA ignores the Java version configuration for test code. It uses the configured Java version in production code section for the whole project. An issue is already opened. A workaround is also described in this issue. So only the Maven build gives you the feedback if your production code uses correct Java version. IntelliJ hasn’t this problem for Gradle projects. Here, it uses the Java version just like it is configured in Gradle build file.

The situation in Netbeans looks better for Maven projects. Netbeans loads the Java configuration for Maven project, correctly. For Gradle projects, I couldn’t check it, because in Netbeans 10, there doesn’t yet exist a Gradle plugin (status: January 2019, but for Netbeans 9, so maybe something will come)

Links


Leave a comment

How to Format a Large Code Base Automatically

If you introduce code formatting rules retroactively, you have to solve the problem how to format existing code base according to the new formatting rules. You could checkout every code repository one by one in your IDE and click on Autoformat the whole project. But this is boring and waste of time. Fortunately, Intellij IDEA has a format CLI tool in its installation. You can locate it in the path <your Intellij IDEA installation>/bin. It’s called format.sh.

In the next section I’d like to show you how you can automate formatting big code base. First, I will show the preparation steps like exporting your code formatting rule setting from the IDE. Then, I will demonstrate how to use the CLI-Tool format.sh. At the end, I will show a small Groovy script that query all repositories (in this case they are Git repositories), formatting the code and push it back to the remote SCM.

Preparations

First at all, we need the code formatting rule setting exported from Intellij IDEA. In your Intellij IDEA follow the next step

  1. Open File -> Settings -Editor-> Code Style
  2. Click on Export…
  3. Choose a name for the XML file (for example, Default.xml) and a location where this file should be saved (for example, /home/foo ).

Then, checkout or clone your SCM repository and remember the location where you checkout/clone it (for example, /home/foo/myrepository).

Format Code Base Via format.sh  CLI Tool

Three parameters are important for format.sh:

  • -s : Set a path to Intellij IDEA code style settings .xml file (in our example: /home/foo/Default.xml).
  • -r : Set that directories should be scanned recursively.
  • path<n> : Set a path to a file or a directory that should be formatted (in our example: /home/foo/myrepository).

> ./format.sh
IntelliJ IDEA 2018.2.4, build IC-182.4505.22 Formatter
Usage: format [-h] [-r|-R] [-s|-settings settingsPath] path1 path2...
-h|-help Show a help message and exit.
-s|-settings A path to Intellij IDEA code style settings .xml file.
-r|-R Scan directories recursively.
-m|-mask A comma-separated list of file masks.
path<n> A path to a file or a directory.

> /format.sh -r -s ~/Default.xml ~/myrepository

It’s possible that the tool cancels scanning because of a java.lang.OutOfMemoryError: Java heap space. Then, you have to increase Java’s maximum memory size (-Xmx) in <your Intellij IDEA installation>/bin/idea64.vmoptions.


> nano idea64.vmoptions
-Xms128m
-Xmx750m // <- here increase the maximum memory size
-XX:ReservedCodeCacheSize=240m
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-ea
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-Djdk.http.auth.tunneling.disabledSchemes=""
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-Dawt.useSystemAAFontSettings=lcd
-Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine

Groovy Script For Formatting Many Repository In a Row

Now, we want to bring everything together. The script should do four things:

  1. Find all repository URLs whose code has to be formatted.
  2. Check out / Clone the repositories.
  3. Format the code in all branches of each repostory.
  4. Commit and push the change to the remote SCM.

I choose Git as SCM in my example. The finding of the repository URLs depends on the Git Management System (like BitBucket, Gitlab, SCM Manager etc.) that you use. But the approach is in all system the same:

  1. Call the RESTful API of your Git Management System.
  2. Parse the JSON object in the response after the URLs.

For example, in BitBucket it’s like that:



@Grab('org.codehaus.groovy.modules.http-builder:http-builder:0.7.1')
import groovyx.net.http.*

import static groovyx.net.http.ContentType.*
import static groovyx.net.http.Method.*

def cloneUrlsForProject() {

    def projectUrl = "https://scm/rest/api/1.0/projects/PROJECT_KEY/repos?limit=1000"
    def token = "BITBUCKET_TOKEN"

    def projects = []
    def cloneUrls = []

    def http = new HTTPBuilder(projectUrl)
    http.request(GET) {
        headers."Accept" = "application/json"
        headers."Authorization" = "Bearer ${token}"

        response.success = { resp -> projects = new JsonSlurper().parseText(resp.entity.content.text)}

        response.failure = { resp ->
            throw new RuntimeException("Error fetching clone urls for '${projectKey}': ${resp.statusLine}")
        }
    }

    projects.values.each { value ->
        def cloneLink = value.links.clone.find { it.name == "ssh" }
        cloneUrls.add(cloneLink.href)
    }

    return cloneUrls
}

Then, we have to clone the repositories and checkout each branch. In each branch, the format.sh has to be called. For the git operation, we use the jgit library and for the format.sh call we use a Groovy feature for process calling. In Groovy it’s possible to define the command as a String and then to call the method execute() on this String like “ls -l”.execute(). So the Groovy script for the last three tasks would be looked like that:

#!/usr/bin/env groovy
@Grab('org.eclipse.jgit:org.eclipse.jgit:5.1.2.201810061102-r')
import jgit.*
import org.eclipse.jgit.api.CreateBranchCommand
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.ListBranchCommand
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider

import java.nio.file.Files


def intellijHome = 'path to your idea home folder'
def codeFormatterSetting = 'path to your exported code formatter setting file'
def allRepositoriesUrls = ["http://scm/repo1","http://scm/repo2"] // for simplifying

allRepositoriesUrls.each { repository ->
    def repositoryName = repository.split('/').flatten().findAll { it != null }.last()
    File localPath = Files.createTempDirectory("${repositoryName}-").toFile()
    println "Clone ${repository} to ${localPath}"
    Git.cloneRepository()
       .setURI(repository)
       .setDirectory(localPath)
       .setNoCheckout(true)
       .setCredentialsProvider(new UsernamePasswordCredentialsProvider("user", "password")) // only needed when clone url is https / http
       .call()
       .withCloseable { git ->
        def remoteBranches = git.branchList().setListMode(ListBranchCommand.ListMode.REMOTE).call()
        def remoteBranchNames = remoteBranches.collect { it.name.replace('refs/remotes/origin/', '') }

        println "Found the following branches: ${remoteBranchNames}"

        remoteBranchNames.each { remoteBranch ->
            println "Checkout branch $remoteBranch"
            git.checkout()
               .setName(remoteBranch)
               .setCreateBranch(true)
               .setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK)
               .setStartPoint("origin/" + remoteBranch)
               .call()

            def formatCommand = "$intellijHome/bin/format.sh -r -s $codeFormatterSetting $localPath"

            println formatCommand.execute().text

            git.add()
               .addFilepattern('.')
               .call()
            git.commit()
               .setAuthor("Automator", "no-reply@yourcompany.com")
               .setMessage('Format code according to IntelliJ setting.')
               .call()

            println "Commit successful!"
        }

        git.push()
           .setCredentialsProvider(new UsernamePasswordCredentialsProvider("user", "password")) // only needed when clone url is https / http
           .setPushAll()
           .call()

        println "Push is done"

    }
}

Do you have another approach? Let me know and write a comment below.


Leave a comment

Running Ansible on a Windows System

On my last conference talk (it was about Ansible and Docker at DevOpsCon in Berlin), I was asked what is the best way to run Ansible on a Windows system. Ansible itself requires a Linux-based system as the control machine. When I have to develop on a Windows machine, I install a Linux-based virtual machine to run the Ansible’s playbooks inside the virtual machine. I set up the virtual machine with Virtualbox and Vagrant. This tools allow me to share the playbooks easily between host and the virtual machine. so I can develop the playbook on the windows system and the virtual machine can have a headless setup. The next section shows you how to set up this tool chain.

 Tool Chain Setup

 At first, install VirtualBox and Vagrant on your machine. I additionally use Babun, a windows shell based on Cygwin and oh-my-zsh, for a better shell experience on Windows, but this isn’t necessary. Then, go to the directory (let’s called it ansible-workspace), where your Ansible’s playbooks are located. Create there a Vagrant configuration file with the command vagrant init:
ansible-workspace
├── inventories
│   ├── production
│   └── test
├── README.md
├── roles
│   ├── deploy-on-tomcat
│   │   ├── defaults
│   │   │   └── main.yml
│   │   └── tasks
│   │       ├── cleanup-webapp.yml
│   │       ├── deploy-webapp.yml
│   │       ├── main.yml
│   │       ├── start-tomcat.yml
│   │       └── stop-tomcat.yml
│   ├── jdk
│   │   └── tasks
│   │       └── main.yml
│   └── tomcat8
│       ├── defaults
│       │   └── main.yml
│       ├── files
│       │   └── init.d
│       │       └── tomcat
│       ├── tasks
│       │   └── main.yml
│       └── templates
│           └── setenv.sh.j2
├── demo-app-ansible-deploy-1.0-SNAPSHOT.war
├── deploy-demo.yml
├── inventories
│   ├── production
│   └── test
├── roles
│   ├── deploy-on-tomcat
│   │   ├── defaults
│   │   │   └── main.yml
│   │   └── tasks
│   │       ├── cleanup-webapp.yml
│   │       ├── deploy-webapp.yml
│   │       ├── main.yml
│   │       ├── start-tomcat.yml
│   │       └── stop-tomcat.yml
│   ├── jdk
│   │   └── tasks
│   │       └── main.yml
│   └── tomcat8
│       ├── defaults
│       │   └── main.yml
│       ├── files
│       │   └── init.d
│       │       └── tomcat
│       ├── tasks
│       │   └── main.yml
│       └── templates
│           └── setenv.sh.j2
├── setup-app-roles.yml
├── setup-app.yml
└── Vagrantfile

├── setup-app-roles.yml
├── setup-app.yml
└── Vagrantfile


Now, we have to choose a so-called Vagrant Box on Vagrant Cloud. A box is the package format for a Vagrant environment. It depends on the provider and the operation system that you choose to use. In our case, it is a Virtualbox VM image based on a minimal Ubuntu 18.04 system (box name is bento/ubuntu-18.04 ). This box will be configured in our Vagrantfile:

Vagrant.configure("2") do |config|
  config.vm.box = "bento/ubuntu-18.04"
end

The next step is to ensure that Ansible will be installed in the box. Thus, we use the shell provisioner of Vagrant. The Vagranfile will be extended by the provisioning code:

Vagrant.configure("2") do |config|
  # ... other Vagrant configuration
  config.vm.provision "shell", inline: &lt;&lt;-SHELL
    sudo apt-get update -y
    sudo apt-get install -y software-properties-common
    sudo apt-add-repository ppa:ansible/ansible
    sudo apt-get update -y
    sudo apt-get install -y ansible
    # ... other Vagrant provision steps
  SHELL
end
end

The last step is to copy the SSH credential into the Vagrant box. Thus, we mark the SSH credential folder of the host system as a Shared folder, so that we can copy them to the SSH config folder inside the box.
Vagrant.configure("2") do |config|
 
  # ... other Vagrant configuration
  config.vm.synced_folder ".", "/vagrant"
  config.vm.synced_folder "path to your ssh config", "/home/vagrant/ssh-host"
  # ... other Vagrant configuration

  config.vm.provision "shell", inline: &lt;&lt;-SHELL
    # ... other Vagrant provision steps
    cp /home/vagrant/ssh-host/* /home/vagrant/.ssh/.
  SHELL
end

On Github’s Gist you can found the whole Vagrantfile.

Workflow

After setting up the tool chain let’s have a look how to work with it. I write my Ansible playbook on the Windows system and run them from the Linux guest system against the remote hosts. For running the Ansible playbooks we have to start the Vagrant box.
> cd ansible-workspace
> vagrant up

When the Vagrant box is ready to use, we can jump into the box with:
 
> vagrant ssh 

You can find the Ansible playbooks inside the box in the folder /vagrant .  In this folder run Ansible:
 
> cd" /vagrant
> ansible-playbook -i inventories/test -u tekkie setup-db.yml

Outlook

Maybe on Windows 10 it’s possible to use Ansible natively, because of the Linux subsystem. But I don’t try it out. Some Docker fans would prefer a container instead of a virtual machine. But remember, before Windows 10 Docker runs on Windows in a virtual machine, so therefore, I don’t see a benefit for using Docker instead of a virtual machine. But of course with Windows 10 native container support a setup with Docker is a good alternative if Ansible doesn’t run on the Linux subsystem.
Do you another idea or approach? Let me know and write a comment.

Links

  1. VirtualBox
  2. Vagrant
  3. Whole Vagrantfile on Github.

 


Leave a comment

Apache2 as Reverse Proxy for NPM Registry Proxies in Sonatype Nexus 3

I use a NPM registry proxy in Sonatype Nexus 3 behind an Apache2 as reverse proxy. With the “standard” Apache2 VirtualHost configuration


<VirtualHost:80>

  ProxyRequests Off
  <Proxy *>
    Order deny,allow
    Allow from all
  </Proxy>
  ProxyPass / http://localhost:8081/
  ProxyPassReverse / http://localhost:8081/

</VirtualHost:80>

I got following failure when I tried to install the dependency @sinonjs/formatio.

$ yarn add @sinonjs/formatio --verbose
yarn add v1.3.2
warning package.json: No license field
verbose 0.337 Checking for configuration file "/home/sparsick/dev/workspace/yarn-test-module/.npmrc".
verbose 0.337 Checking for configuration file "/home/sparsick/.npmrc".
verbose 0.337 Found configuration file "/home/sparsick/.npmrc".
verbose 0.337 Checking for configuration file "/usr/etc/npmrc".
verbose 0.338 Found configuration file "/usr/etc/npmrc".
verbose 0.338 Checking for configuration file "/home/sparsick/dev/workspace/yarn-test-module/.npmrc".
verbose 0.338 Checking for configuration file "/home/sparsick/dev/workspace/.npmrc".
verbose 0.338 Checking for configuration file "/home/sparsick/dev/.npmrc".
verbose 0.338 Checking for configuration file "/home/sparsick/.npmrc".
verbose 0.338 Found configuration file "/home/sparsick/.npmrc".
verbose 0.338 Checking for configuration file "/home/.npmrc".
verbose 0.341 Checking for configuration file "/home/sparsick/dev/workspace/yarn-test-module/.yarnrc".
verbose 0.342 Found configuration file "/home/sparsick/dev/workspace/yarn-test-module/.yarnrc".
verbose 0.343 Checking for configuration file "/home/sparsick/.yarnrc".
verbose 0.344 Found configuration file "/home/sparsick/.yarnrc".
verbose 0.344 Checking for configuration file "/usr/etc/yarnrc".
verbose 0.344 Checking for configuration file "/home/sparsick/dev/workspace/yarn-test-module/.yarnrc".
verbose 0.345 Found configuration file "/home/sparsick/dev/workspace/yarn-test-module/.yarnrc".
verbose 0.345 Checking for configuration file "/home/sparsick/dev/workspace/.yarnrc".
verbose 0.345 Checking for configuration file "/home/sparsick/dev/.yarnrc".
verbose 0.345 Checking for configuration file "/home/sparsick/.yarnrc".
verbose 0.345 Found configuration file "/home/sparsick/.yarnrc".
verbose 0.345 Checking for configuration file "/home/.yarnrc".
verbose 0.347 current time: 2018-02-27T08:04:43.357Z
warning yarn-test-module: No license field
[1/4] Resolving packages...
verbose 0.45 Performing "GET" request to "http://mycompany/repository/npm-public/@sinonjs%2fformatio".
verbose 0.55 Request "http://mycompany/repository/npm-public/@sinonjs%2fformatio" finished with status code 404.
verbose 0.551 Error: Couldn't find package "@sinonjs/formatio" on the "npm" registry.
at /usr/lib/node_modules/yarn/lib/cli.js:49061:15
at Generator.next (<anonymous>)
at step (/usr/lib/node_modules/yarn/lib/cli.js:92:30)
at /usr/lib/node_modules/yarn/lib/cli.js:103:13
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
error Couldn't find package "@sinonjs/formatio" on the "npm" registry.
info Visit https://yarnpkg.com/en/docs/cli/add for documentation about this command.</span>

The problem is that Apache2 canonicalizes URLs as default. So I have to configure Apache2 to not canonicalize URLs and additionally, I have to allow encoded slashes:


<VirtualHost:80>

  ProxyRequests Off
  <Proxy *>
    Order deny,allow
    Allow from all
  </Proxy>
  ProxyPass / http://localhost:8081/ nocanon
  ProxyPassReverse / http://localhost:8081/

  AllowEncodedSlashes NoDecode

</VirtualHost:80>

With the above Apache2 Virtualhost configuration I could install my dependency via the NPM registry proxy.


$ yarn add @sinonjs/formatio

yarn add v1.3.2
warning package.json: No license field
info No lockfile found.
warning yarn-test-module: No license field
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 2 new dependencies.
├─ @sinonjs/formatio@2.0.0
└─ samsam@1.3.0
warning yarn-test-module: No license field
Done in 0.60s.

Big thanks to Sonatype support team, that gave me this advice.


Leave a comment

Mocking SecurityContext in Jersey Tests

Jersey has a great possibility to write integration test for REST-APIs, written with Jersey. Just extend the class JerseyTest and go for it.

I ran in an issue, where I had to mock a SecurityContext, so that the SecurityContext includes a special UserPrincipal. The challenge is that Jersey wraps the SecurityContext in an own class SecurityContextInjectee in tests. So I have to add my SecurityContext Mock to this Jersey’s wrapper class. Let me demonstrate it in an example.

Let say I have the following Jersey Resource:


import javax.ws.rs.*;
import javax.ws.rs.core.*;

@Path("hello/world")
public class MyJerseyResource {

    @GET
    public Response helloWorld(@Context final SecurityContext context) {
        String name = context.getUserPrincipal().getName();
        return Response.ok("Hello " + name, MediaType.TEXT_PLAIN).build();
    }

}

In my test, I have to mock the SecurityContext, so that a predefined user principal can be used during the tests. I use Mockito as mocking framework. My mock looks like the following one

import java.security.Principal;
import javax.ws.rs.core.SecurityContext;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

final SecurityContext securityContextMock = mock(SecurityContext.class);
when(securityContextMock.getUserPrincipal()).thenReturn(new Principal() {
    @Override
    public String getName() {
        return "Alice";
    }
});

For adding this mocked SecurityContext to the wrapper class SecurityContextInjectee, I have to configure a ResourceConfig with a modified ContainerRequestContext in my Jersey Test. The mocked SecurityContext can be set in this modified ContainerRequestContext and then it will be used in the wrapper class:


import java.security.Principal;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.SecurityContext;

import org.glassfish.jersey.server.ResourceConfig;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@Override
public Application configure() {
    final SecurityContext securityContextMock = mock(SecurityContext.class);
    when(securityContextMock.getUserPrincipal()).thenReturn(new Principal() {
        @Override
        public String getName() {
            return "Alice";
        }
    });

    ResourceConfig config = new ResourceConfig();
    config.register(new ContainerRequestFilter(){
        @Override
        public void filter(final ContainerRequestContext containerRequestContext) throws IOException {
            containerRequestContext.setSecurityContext(securityContextMock);
        }
    });
    return config;
}

Then, the whole test for my resource looks like the following one:

import java.security.Principal;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;

import org.apache.commons.httpclient.HttpStatus;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class MyJerseyResourceTest extends JerseyTest {

    @Test
    public void helloWorld() throws Exception {
        Response response = target("hello/world").request().get();

        assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_OK);
        assertThat(response.getEntity()).isEqualTo("Hello Alice");
    }

    @Override
    public Application configure() {
        final SecurityContext securityContextMock = mock(SecurityContext.class);
        when(securityContextMock.getUserPrincipal()).thenReturn(new Principal() {
            @Override
            public String getName() {
                return "Alice";
            }
        });

        ResourceConfig config = new ResourceConfig();
        config.register(new ContainerRequestFilter() {
            @Override
            public void filter(final ContainerRequestContext containerRequestContext) throws IOException {
                containerRequestContext.setSecurityContext(securityContextMock);
            }
        });
        return config;
    }
}

Do you have a smarter solution for this problem? Let me know it and write a comment below.