Thursday, April 26, 2012

Resolve JIRA issue after Jenkins build using API

Jenkins and JIRA integration is quite poor - there are few plugins, but they are quite far from being practical. Even such simple action like issue transition after build is missing. It is generally possible by using smart comments, but they trigger on commit, not after successful build.
Fortunately, there is a lot of customization options for Jenkins, and Jira has great API.
So, to implement issue transition it is good to have Groovy plugin and Jira Issue Updater plugin (for Jira libs). Then it is needed to add new Build step - Execute system Groovy script; and add this simple Groovy script there:


import hudson.model.*
import com.atlassian.jira.rpc.soap.client.*

def thr = Thread.currentThread()
def build = thr?.executable

def rss = new XmlParser().parseText(  new File("${build.getEnvVars()['WORKSPACE']}/../builds/${build.getEnvVars()['BUILD_ID']}/changelog.xml").getText()  )
def ids = []
rss.logentry.each {
   def match =  it.msg.text() =~ /(\w+-\d+)/
   match.each {
       ids << it[0]
   }
}

JiraSoapServiceServiceLocator jiraSoapServiceLocator = new JiraSoapServiceServiceLocator();
def jiraSoapService = jiraSoapServiceLocator.getJirasoapserviceV2(new URL('http://jira.xxxxx.com/rpc/soap/jirasoapservice-v2'));
String token = jiraSoapService.login('login', 'password');
def issues = jiraSoapService.getIssuesFromJqlSearch(token, "project=PROJECT1 and issuekey in (${ids.join(', ')})", 20)
String[] resolutions = ["23"]
for (RemoteIssue issue : issues) {
    def actions = jiraSoapService.getAvailableActions(token, issue.getKey() );
    for (RemoteNamedObject action : actions) {
         if (action.getName().equalsIgnoreCase('Resolve')) {
               RemoteFieldValue[] vals = [  new RemoteFieldValue("resolution", resolutions  )  ]
               jiraSoapService.progressWorkflowAction(token, issue.getKey(), action.getId(), vals );
         }
    }
}



This basically looks for all Jira IDs in commit comments, then queries specific project for all issues with these IDs and sets status to Resolved if it is applicable.
It is easy to customize this script to add special keys, like to ignore some commits or something.

Thursday, April 19, 2012

Groovy Eval slow performance

Groovy Eval is nice feature, unfortunately it does not have great performance. Recently, I had to calculate values on a bunch of objects and got quite dramatic performance issue. After quick investigation, I pinpointed problem to be with Eval, which can be summarized to:


def t = System.currentTimeMillis()
1000.times {
  Eval.x(44, 'x>3')
}
println (System.currentTimeMillis() - t)
7281
I tried to check if there is other solution that could do the same and I found that there is similar native Java function. In similar test I got:


import javax.script.ScriptEngine
import javax.script.ScriptEngineManager
import javax.script.SimpleBindings

def t = System.currentTimeMillis()
ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
1000.times {
  engine.eval('x>3', new SimpleBindings([x:44]))
}
println (System.currentTimeMillis() - t)
250

Obviously, it will not be absolutely the same in general and I assume Eval provides much more (and looks less ugly), but ScriptEngine is good enough for my case and it performs almost 30 times faster.

Tuesday, April 3, 2012

Accessing request parameters from UserDetailsService in Spring Security

If you need to use several properties to authenticate user with Spring Security, for example, by login and domain, there is no built-in way to do it. Even when you override UserDetailsService it only calls method with one username parameter.
Fortunately, there is easy way to access request context with Grails, so it is possible to extract any parameter you need, like:


import org.springframework.web.context.request.RequestContextHolder

class UserDetailsService implements GrailsUserDetailsService  {

  UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    def otherParameter = RequestContextHolder.requestAttributes.params.other
       ...
  }
}


Monday, April 2, 2012

Redis exception: Cannot use Jedis when in Multi.

Jedis newbie problem when adding transactions to Redis client application:


Cannot use Jedis when in Multi. Please use JedisTransaction instead.
redis.clients.jedis.exceptions.JedisDataException: Cannot use Jedis when in Multi. Please use JedisTransaction instead.
 at redis.clients.jedis.BinaryJedis.checkIsInMulti(BinaryJedis.java:1651)
 at redis.clients.jedis.Jedis.hmset(Jedis.java:724)
 at test.TestService.updateSomething(TestService.groovy:39)
 at test.TestService$_processBet_closure1.doCall(TestService.groovy:16)
 at grails.plugin.redis.RedisService.withRedis(RedisService.groovy:67)

Description of exception is absolutely correct though a little confusing. Problem is that you are trying to call methods on Redis connection object instead of Transaction object. So most probably you have something like:


  Jedis jedis = pool.getResource()
  jedis.watch('foo')
  ...
  def transaction = jedis.multi()
  jedis.set("foo", value.toString())
  def result = transaction.exec()


But instead, you should have something like:


  Jedis jedis = pool.getResource()
  jedis.watch('foo')
  ...
  def transaction = jedis.multi()
  transaction.set("foo", value.toString())
  def result = transaction.exec()