Simple alternative to nvm using Docker

After Node v6.6.0 is out, I wanted to try it, but I don't want to replace my local Node v4 environment. I can use nvm, but that's too much for some simple tests. So I switched to Docker.

Just adding following alias to my .zshrc file, I can now run JavaScript file using Node 6.

alias node6="docker run -it --rm -v $(pwd):/code -w /code node:6.6 node"  
node6 hello.js  

I also added Node 4.

alias node4="docker run -it --rm -v $(pwd):/code -w /code node:4.5 node"  

Java - Sanitize file and folder name

A quick way to sanitize file name in Java:

name.replaceAll("\\\\|/|\\||:|\\?|\\*|\"|<|>|\\p{Cntrl}", "_")  

And also for folder name:

name.replaceAll("\\.|\\\\|/|\\||:|\\?|\\*|\"|<|>|\\p{Cntrl}", "_")  

Taken from here.

Docker build apt-get install error

I was trying to build a docker image and use apt-get to install packages and kept getting this error Error reading from server. Remote end closed connection for different packages. The reason is because httpredir.debian.org server tries to return different server mirrors for different packages and some servers may have temporary load or connection issues. I tried a couple of solutions, e.g. run apt-get clean doesn't work, add --no-cache to docker build doesn't work. The only workable solution is to add following line to Dockerfile before run apt-get install.

RUN sed -i "s/httpredir.debian.org/`curl -s -D - http://httpredir.debian.org/demo/debian/ | awk '/^Link:/ { print $2 }' | sed -e 's@<http://\(.*\)/debian/>;@\[email protected]'`/" /etc/apt/sources.list  

Thanks to this SO answer.

Git - Create and apply patches

I got two Git repositories which diverged too much, so when I fixed a bug in one repository, the same fix should be applied to the other repository.

The workflow is:

1. Create the patch.

Use git format-patch -1 <sha> to create the patch from git commit. <sha> is the SHA1 of the commit.
For the latest commit, we can use git format-patch -1 HEAD.

2. Check the patch

Use git apply --stat <patch file> to get the stats after applying the patch. Use git apply --check <patch file> to check any errors.

3. Apply the patch

Use git am < <patch file> to apply the patch and commit.

4. When conflicts

When the patch cannot be applied due to conflicts, use git apply --reject --whitespace=fix <patch file> to apply the patch partially. Unapplied changes will be put into *.rej files. Resolve the conflicts yourself and commit the changes.

Docker - Clean up disk space

When using Docker Toolbox on Mac OS X, it complains no space left on device after a while. So we need to clean up the disk space.

Delete exited containers

docker rm -v $(docker ps -a -q -f status=exited)  

Remove dangling images

docker rmi $(docker images -f "dangling=true" -q)  

Remove dangling volumes

docker volume rm $(docker volume ls -qf dangling=true)  

References: 1, 2

AngularJS - Insert Text at Caret Position

In AngularJS, if you want to insert text at current caret position, you can use following service. The code is written in CoffeeScript.

angular  
  .module('text-insert', [])
  .service('TextInsert', () -> 
    {       
      insert: (input, text) ->
        return if !input
        scrollPos = input.scrollTop
        pos = 0
        browser = if (input.selectionStart || input.selectionStart == '0') then 'ff' else (if document.selection then 'ie' else false)
        if browser == 'ie'
          input.focus()
          range = document.selection.createRange()
          range.moveStart('character', -input.value.length)
          pos = range.text.length
        else if browser == 'ff'
          pos = input.selectionStart
        front = (input.value).substring(0, pos)
        back = (input.value).substring(pos, input.value.length)
        input.value = front + text + back
        pos = pos + text.length
        if browser == 'ie'
          input.focus()
          range = document.selection.createRange()
          range.moveStart('character', -input.value.length)
          range.moveStart('character', pos)
          range.moveEnd('character', 0)
          range.select()
        else if browser == 'ff'
          input.selectionStart = pos
          input.selectionEnd = pos
          input.focus()
        input.scrollTop = scrollPos
        angular.element(input).trigger('input')
        ''
    }
  )

The first argument of insert method is the raw DOM node, can be input or textarea. The second argument is the text to insert. See following code about how to use it.

TextInsert.insert(angular.element('#input1')[0], 'hello')  

This AngularJS service is based on another CodePen project.

A complete example can be find in this CodePen project. If you want to use JavaScript, you can click 'View Compiled' in CodePen to see the compiled JavaScript code.

See the Pen Angular Text Insert at Caret Position by Fu Cheng (@alexcheng) on CodePen.

Spring Async Task Executor with Event Bus

I have a operation which talks to database, so it may be slow. So I was looking for a way to make it asynchronous, then I discovered Spring 4 has async task executor and ListenableFuture and it works well with current Google Guava EventBus.

We created a new AsyncListenableTaskExecutor first. SimpleAsyncTaskExecutor does not reuse any threads, rather it starts up a new thread for each invocation. But it's good enough.

private final AsyncListenableTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor("my task");  

Then we submit a task to the executor.

this.taskExecutor.submitListenable(() -> {  
    saveToDB();
    return null;
}).addCallback(
    (result) -> this.eventBus.post(new SaveOKEvent()),
    (ex)     -> this.eventBus.post(new SaveFailedEvent(ex))
);

Then we adds listeners to both SaveOKEvent and SaveFailedEvent to handle the success and failure case. Done!

JGit Flow Maven plugin integration with Bamboo

JGit Flow is a good plugin to apply git-flow practice with Maven projects. Since it's a pure Java implementation, it's very easy to integrate with most CI servers.

However, if you are using Atlassian Bamboo, there are some workarounds for particular issues.

Git repository url

Bamboo use a fake Git repository after checkout. The repository's url is something like file:///nothing. So JGit Flow cannot perform actual Git operations on this repository. You can:

1) Set repository url in plugin configuration

<configuration>  
    <defaultOriginUrl>[repository url]</defaultOriginUrl>
    <alwaysUpdateOrigin>true</alwaysUpdateOrigin>
</configuration>  

2) Use Git command to update repository url

${bamboo.capability.system.git.executable} remote set-url origin ${bamboo.repository.git.repositoryUrl}

Git repository authentication

You can use -Dusername and -Dpassword in JGit Flow plugin to set the repository's username and password. To execute Bamboo shell script with Git commands, a .netrc file with authentication details needs to be created. This can be done via agent start script or using echo in inline script.

machine bitbucket.org  
login <username>  
password <password>  

Clean old release branches

After finishing a release using release-finish, the remote release branch is deleted by default. But the branch may still exist in local. These old release branches should be removed, otherwise next release-start goal will fail.

${bamboo.capability.system.git.executable} fetch --prune --verbose

${bamboo.capability.system.git.executable} branch -vv | awk '/: gone]/{print $1}' | xargs ${bamboo.capability.system.git.executable} branch -d 2> /dev/null

echo 'stale branches deleted'  

Elasticsearch - Delete documents by type

If you want to delete documents in Elasticsearch by type using Java API, below are some options:

  • For Elasticsearch 1.x, use the deprecated prepareDeleteByQuery method of Client. 2.x has removed this method.
  • For Elasticsearch 2.x, use delete-by-query plugin.

Or use scroll/scan API as below.

SearchResponse scrollResponse = this.client.prepareSearch(INDEX_NAME)  
        .setTypes(type)
        .setSearchType(SearchType.SCAN)
        .setScroll(new TimeValue(60000))
        .setQuery(QueryBuilders.matchAllQuery())
        .setSize(100)
        .get();
final BulkRequestBuilder bulkRequestBuilder = this.client.prepareBulk().setRefresh(true);  
while (true) {  
    if (scrollResponse.getHits().getHits().length == 0) {
        break;
    }

    scrollResponse.getHits().forEach(hit -> bulkRequestBuilder.add(
        this.client.prepareDelete(INDEX_NAME, type, hit.getId()))
    );
    scrollResponse = this.client.prepareSearchScroll(scrollResponse.getScrollId())
            .setScroll(new TimeValue(60000))
            .get();
}
if (bulkRequestBuilder.numberOfActions() > 0) {  
    bulkRequestBuilder.get();
}