Syntax highlighter header

Friday 24 September 2021

Httpd mod_proxy throwing 503 error

Recently I was debugging an issue of Apache server throwing 503 when we are trying to connect to a nodeJS server running on port 5000. We had proxy setting correctly added into out httpd.conf

<IfModule mod_proxy.c>
    ProxyRequests Off
    SSLProxyEngine On

    <Proxy *>
            Order deny,allow
            Allow from 127.0.0.1
    </Proxy>

    <Location /nodeserver>
        ProxyPass http://localhost:5000
        ProxyPassReverse http://localhost:5000
    </Location>

</IfModule>

The server was throwing 503 error and we were not able to figure out the reason for it because node server was up and was responding to all requests at port 5000. But when we were trying to access it via Apache server it was throwing 503 error. 

After a lot of debugging we found that it was selinux which was preventing Apache server from connecting to node server at port 5000. Although Apache server was able to connect tomcat server running at port 8009. 

The reason of this selective behavior was that selinux defines some ports as http ports and httpd server is allowed to connect to those port. You can get a list of these ports by running following command.

$semanage port -l |grep http_port_t
http_port_t                    tcp      80, 81, 443, 488, 8008, 8009, 8443, 9000

After changing port from 5000 to 9000 for our node server and changing mod_proxy setting in httpd.conf our application started working.


 

Wednesday 8 September 2021

Tomcat 9 creates different sessions for different directories

Recently we were testing one application with tomcat 9.0.43 where one application path was starting with sso/ and one was starting with normal/. We were redirecting to normal/ path from sso/ path. We found that the values we set in HttpSession were missing in normal/ path. 

After a lot of debugging we found out that Tomcat 9 was creating two sessions for same client one for sso/ path and one for normal/ path. That is why values set in HttpSession were missing. I wrote following sample jsp file to demonstrate the behavior.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<body>
<%
	String sessionStr = request.getSession().toString();
%>
Session:<%=sessionStr%>

</body>
</html>

I placed the file at two locations. One under sso/index.jsp and one under normal/index.html and hit both of them from same browser and output was following.

SSO:    Session:org.apache.catalina.session.StandardSessionFacade@2ca7ac8e
Normal: Session:org.apache.catalina.session.StandardSessionFacade@4b992f7e

This problem looks like to be fixed in tomcat 9.0.54



Tuesday 17 August 2021

Unable to make private java.lang.reflect.Proxy() accessible with JDK-17 in JUnit test using Ant

Recently I encountered the following error while executing Junit test case using Ant. My testcase uses EJBs from a wildfly 24 server. I am using jboss-client.jar file from same server in my testcase.

2021-08-17 16:39:30,450 2428  ERROR [com.tk20.ejb.api.util.EJBUtils] (main:[]) Unable to make private java.lang.reflect.Proxy() accessible: module java.base does not "opens java.lang.reflect" to unnamed module @
394df057
java.lang.reflect.InaccessibleObjectException: Unable to make private java.lang.reflect.Proxy() accessible: module java.base does not "opens java.lang.reflect" to unnamed module @394df057
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354) ~[?:?]
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297) ~[?:?]
        at java.base/java.lang.reflect.Constructor.checkCanSetAccessible(Constructor.java:188) ~[?:?]
        at java.base/java.lang.reflect.Constructor.setAccessible(Constructor.java:181) ~[?:?]
        at org.jboss.marshalling.reflect.JDKSpecific$SerMethods.<init>(JDKSpecific.java:147) ~[jboss-client.jar:24.0.0.Final]
        at org.jboss.marshalling.reflect.SerializableClass.<init>(SerializableClass.java:84) ~[jboss-client.jar:24.0.0.Final]
        at org.jboss.marshalling.reflect.SerializableClassRegistry$1.computeValue(SerializableClassRegistry.java:62) ~[jboss-client.jar:24.0.0.Final]
        at org.jboss.marshalling.reflect.SerializableClassRegistry$1.computeValue(SerializableClassRegistry.java:59) ~[jboss-client.jar:24.0.0.Final]
        at java.base/java.lang.ClassValue.getFromHashMap(ClassValue.java:228) ~[?:?]
        at java.base/java.lang.ClassValue.getFromBackup(ClassValue.java:210) ~[?:?]
        at java.base/java.lang.ClassValue.get(ClassValue.java:116) ~[?:?]
        at org.jboss.marshalling.reflect.SerializableClassRegistry.lookup(SerializableClassRegistry.java:83) ~[jboss-client.jar:24.0.0.Final]
        at org.jboss.marshalling.reflect.SerializableClass.<init>(SerializableClass.java:76) ~[jboss-client.jar:24.0.0.Final]
        at org.jboss.marshalling.reflect.SerializableClassRegistry$1.computeValue(SerializableClassRegistry.java:62) ~[jboss-client.jar:24.0.0.Final]
        at org.jboss.marshalling.reflect.SerializableClassRegistry$1.computeValue(SerializableClassRegistry.java:59) ~[jboss-client.jar:24.0.0.Final]
        at java.base/java.lang.ClassValue.getFromHashMap(ClassValue.java:228) ~[?:?]
        at java.base/java.lang.ClassValue.getFromBackup(ClassValue.java:210) ~[?:?]
        at java.base/java.lang.ClassValue.get(ClassValue.java:116) ~[?:?]
        at org.jboss.marshalling.reflect.SerializableClassRegistry.lookup(SerializableClassRegistry.java:83) ~[jboss-client.jar:24.0.0.Final]
        at org.jboss.marshalling.river.RiverUnmarshaller.doReadNewObject(RiverUnmarshaller.java:1394) ~[jboss-client.jar:24.0.0.Final]
        at org.jboss.marshalling.river.RiverUnmarshaller.doReadObject(RiverUnmarshaller.java:298) ~[jboss-client.jar:24.0.0.Final]
        at org.jboss.marshalling.river.RiverUnmarshaller.doReadObject(RiverUnmarshaller.java:231) ~[jboss-client.jar:24.0.0.Final]
        at org.jboss.marshalling.AbstractObjectInput.readObject(AbstractObjectInput.java:41) ~[jboss-client.jar:24.0.0.Final]
        at org.wildfly.naming.client.remote.RemoteClientTransport.lookup(RemoteClientTransport.java:271) ~[jboss-client.jar:24.0.0.Final]
        at org.wildfly.naming.client.remote.RemoteContext.lambda$lookupNative$0(RemoteContext.java:190) ~[jboss-client.jar:24.0.0.Final]
        at org.wildfly.naming.client.NamingProvider.performExceptionAction(NamingProvider.java:222) ~[jboss-client.jar:24.0.0.Final]
        at org.wildfly.naming.client.remote.RemoteContext.performWithRetry(RemoteContext.java:100) ~[jboss-client.jar:24.0.0.Final]
        at org.wildfly.naming.client.remote.RemoteContext.lookupNative(RemoteContext.java:188) ~[jboss-client.jar:24.0.0.Final]
        at org.wildfly.naming.client.AbstractFederatingContext.lookup(AbstractFederatingContext.java:74) ~[jboss-client.jar:24.0.0.Final]
        at org.wildfly.naming.client.store.RelativeFederatingContext.lookupNative(RelativeFederatingContext.java:58) ~[jboss-client.jar:24.0.0.Final]
        at org.wildfly.naming.client.AbstractFederatingContext.lookup(AbstractFederatingContext.java:74) ~[jboss-client.jar:24.0.0.Final]
        at org.wildfly.naming.client.AbstractFederatingContext.lookup(AbstractFederatingContext.java:60) ~[jboss-client.jar:24.0.0.Final]
        at org.wildfly.naming.client.AbstractFederatingContext.lookup(AbstractFederatingContext.java:66) ~[jboss-client.jar:24.0.0.Final]
        at org.wildfly.naming.client.AbstractFederatingContext.lookup(AbstractFederatingContext.java:66) ~[jboss-client.jar:24.0.0.Final]
        at org.wildfly.naming.client.AbstractFederatingContext.lookup(AbstractFederatingContext.java:66) ~[jboss-client.jar:24.0.0.Final]
        at org.wildfly.naming.client.WildFlyRootContext.lookup(WildFlyRootContext.java:144) ~[jboss-client.jar:24.0.0.Final]

The reason of failure was traced to version of Ant which I was using version 1.9.15 of Ant. After upgrading Ant to version 1.10.11 the error was removed. Due to some reason with older version of Ant JDKSpecific class which was being used was compatible with JDK8 and not the one which is compatible with JDK9 or higher. After upgrading Ant to 1.10.11 it started picking right version of class and problem was fixed.

Please refer to my earlier post regarding similar  error faced while accessing EJB in wildfly 24 without involvement of Ant and JUnit.  https://blog.bigdatawithjasvant.com/2021/07/javalangreflectinaccessibleobjectexcept.html

Friday 13 August 2021

Implementing rate limit on an API

Here I am going to discuss an interesting programming problem. How to implement rate limit for an API per customer. You need to implement a rate limit of 10 requests per second with allowing a bust of 50 API calls. It means if user was silent for some time then tokens will accumulate up to 50 tokens and after that they will get expired. Every call will consume one token. One token will get accumulated every 100 milliseconds. 

Following is the implementation of code with limit of 10 API calls per second and allowing a bust of 50 API calls.


import java.util.HashMap;

public class RateLimitting {
	
	HashMap<String, Bucket> rateLimits = new HashMap<>();
	
	public static class Bucket {
		int tokens;
		long lastAccessTime;
		
		public Bucket() {
			tokens = 50;
			lastAccessTime = System.currentTimeMillis();
		}
	}
	
	public String api(String user, String req) {
		Bucket r = rateLimits.get(user);
		if(r==null) {
			r = new Bucket();
			rateLimits.put(user, r);
		} else {
			long current = System.currentTimeMillis();
			if((current - r.lastAccessTime)/100 >=1) {
				r.tokens = (int) (r.tokens + (current - r.lastAccessTime)/100);
				if(r.tokens>50) {
					r.tokens = 50;
				}
				r.lastAccessTime = current;
			}
		}
		
		if(r.tokens<0) {
			// deny the request
			return null;
		}
		
		r.tokens--;
		
		// Process the request
		return "Success";
		
	}
} 

Implement power function without using multiplication

In this post I am going to discuss one interesting  programming question. You are supposed to write power function without using multiply operator. You are allowed to use division operator but not multiply.

This is a good question to show your skill at optimizing code and showing that you don't miss the corner cases. You can use multiple additions to simulate multiplication. You need to make sure that you use lest number of additions. For example if you are multiplying 5 with 5 then you don't add 5 four time, you can do it by adding three times. You add 5 with 5 to get 10 in one addtion, You add 10 with 10 in second addition and after that you add 5 in third addition to get answer as 25.

You can implement one function for multiplying two numbers and use that function in your power function. Here again you need to make least number of calls to multiply function. For example if you are calculating 5^5 then we can do it with three calls to multiply functions instead of 4 calls. You multiply 5 with 5 in first call to get 25. You multiply 25 with 25 in second call and multiply 625 with 5 in third call.

Following is the java code for this question:


public class PowerWithoutMul {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		int m = 4;
		int n = 4;
		
		System.out.println(pow(m, n));
		

	}
	
	static int pow(int m, int n) {
		int res = 1;
		if(n/2>0) {
			int temp = pow(m, n/2);
			res = mul(temp, temp);
		}
		if(n%2==1) {
			res = mul(res, m);
		}
		return res;		
	}
	
	static int mul (int m, int k) {
		int res = 0;
		if(k/2>0) {
			int temp = mul(m, k/2);
			res =  temp + temp;
		}
		
		if(k%2 == 1) {
			res = res + m;
		}
		return res;
	}

}

Tuesday 10 August 2021

Minimum number of swaps required to sort an array

I was reading one interesting post on GeeksForGeeks at https://www.geeksforgeeks.org/minimum-number-swaps-required-sort-array/

I found it very informative. I thought of explaining this problem in my words.

Problem statement:

Given an array of n distinct elements, find the minimum number of swaps required to sort the array.

Example:


Input: {4, 3, 2, 1}
Output: 2
Explanation: Swap index 0 with 3 and 1 with 2 to 
              form the sorted array {1, 2, 3, 4}.

Input: {1, 5, 4, 3, 2}
Output: 2

Solution to this problem lies in sorting this array and observing how many elements have changed their position. For the elements who has not changed their position no swap is needed. For the elements who have changed their positions there will be cycles like following image(from GeeksForGeeks.com)

In the above image there are two cycles of two elements each. To sort this array we need two swaps, one for each cycle. Now look at following image (from GeeksForGeeks.com) The above array contains two cycles one with 2 elements and one with 3 elements. We need one swap for correcting two element cycle and two swaps for correcting 3 element cycle. 
We need N-1 swaps for correcting a N element cycle. If we have a cycle a->b->c->d->e->a then this 5 element cycle can be corrected by 4 swaps:

  1. (a, b)
  2. (b, c) note b will be at a's original place
  3. (c, d) note c will be at a's original place
  4. (d, e) note d will be at a's original place

After 4th swap you notice e is at a's original place already so no swap is needed.

For counting number of swap we can calculate cycle size either forward or backward. If we have a cycle a->b->c->d->e->a .We can start at a's original place in sorted array and see that element e is present there, now we see which element it present at e's original place ,we find d there then we look at d's original place then we find c there. If we continue this way we find a at b's original place when we look for a's original place we find that it was already visited so we break out of the loop. This gives us size of the cycle. We mark all elements of this cycle visited so we don't get into this cycle one again.

Following is the code from GeeksForGeeks:

import java.util.*;
import java.io.*;

class GfG
{
	// Function returns the
	// minimum number of swaps
	// required to sort the array
	public static int minSwaps(int[] nums)
	{
		int len = nums.length;
		HashMap<Integer, Integer> map = new HashMap<>();
		for(int i=0;i<len;i++)
			map.put(nums[i], i);
			
		Arrays.sort(nums);
		
		// To keep track of visited elements. Initialize
		// all elements as not visited or false.
		boolean[] visited = new boolean[len];
		Arrays.fill(visited, false);
		
		// Initialize result
		int ans = 0;
		for(int i=0;i<len;i++) {
		
			// already swapped and corrected or
			// already present at correct pos
			if(visited[i] || map.get(nums[i]) == i)
				continue;
				
			int j = i, cycle_size = 0;
			while(!visited[j]) {
				visited[j] = true;
				
				// move to next node
				j = map.get(nums[j]);
				cycle_size++;
			}
			
			// Update answer by adding current cycle.
			if(cycle_size > 0) {
				ans += (cycle_size - 1);
			}
		}
		return ans;
	}
}

// Driver class
class MinSwaps
{
	// Driver program to test the above function
	public static void main(String[] args)
	{
		int []a = {1, 5, 4, 3, 2};
		GfG g = new GfG();
		System.out.println(g.minSwaps(a));
	}
}
// This code is contributed by Saurabh Johari

Monday 9 August 2021

replacement of com.sun.jersey.spi.container.ContainerRequestFilters in jersey 2

Recently I was upgrading my application from Jersey 1 to Jersey 2. Following is my web.xml file in Jercey 1.


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" 
            xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>myapplication</display-name>
  <servlet>
    <servlet-name>jersey-serlvet</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    <init-param>
      <param-name>com.sun.jersey.config.property.packages</param-name>
      <param-value>myapplication</param-value>
    </init-param>
    <init-param>
	  <param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name>
	  <param-value>common.SecurityFilter</param-value>
	</init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>jersey-serlvet</servlet-name>
    <url-pattern>/rest/*</url-pattern>
  </servlet-mapping> 
</web-app>

I found that in Jersey 2 replacement for com.sun.jersey.spi.container.servlet.ServletContainer is org.glassfish.jersey.servlet.ServletContainer . From documentation of org.glassfish.jersey.servlet.ServletContainer I found that replacement of com.sun.jersey.config.property.packages is  jersey.config.server.provider.packages . But I was unable to find replacement for init-param parameter com.sun.jersey.spi.container.ContainerRequestFilters .  It was big struggle to find a replacement for this.

After struggling for a long time I found that we don't need to put filter in web.xml file. It will be picked by package scanning. Following is my filter code. I changed package names according to jersey 2.


package common;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;

import java.util.Base64;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerRequestContext;

@Provider
public class SecurityFilter implements ContainerRequestFilter {

	private static final String X_API_KEY = "x-api-key";
	private static final String X_API_SECRET = "1234567890";
	
	@Override
	public void filter(ContainerRequestContext requestContext) {
		String authapikey = requestContext.getHeaderString(X_API_KEY);
		if(authapikey!=null && !authapikey.isEmpty()) {
			String decodedKey = new String(Base64.getDecoder().decode(authapikey)) ;
			if(X_API_SECRET.equals(decodedKey)) {
				return ;
			}
			
		}
		
		Response unauthorizedResp = Response
						.status(Response.Status.UNAUTHORIZED)
						.entity("User cannot access the resource")
						.build();
		throw new WebApplicationException(unauthorizedResp);
	}
}

The annotation @Provider is required. Without this annotation this filter will not be invoked. Following is my web.xml with Jersey 2. 


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
    id="WebApp_ID" version="3.0">
  <display-name>myapplication</display-name>
  <servlet>
    <servlet-name>jersey-serlvet</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    <init-param>
      <param-name>jersey.config.server.provider.packages</param-name>
      <param-value>myapplication;common</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>jersey-serlvet</servlet-name>
    <url-pattern>/rest/*</url-pattern>
  </servlet-mapping>  
</web-app>

Now my common.SecurityFilter get invoked with Jersey 2. Now my application is working in Jersey 2 as it was working with Jersey 1. Jersey 1 does not work with JDK-17 so you need Jersey 2 for working with JDK-17.

Tuesday 3 August 2021

Using CompletableFuture in Java

 Recently I was exploring CompletableFuture in Java and trying to find correct use case for it. This is what I found.

We have a service which need to combine data from multiple http calls. We submit http requests to a ExecutorService for doing that. We have a deadline that our service need to return in 2 seconds. So we set a deadline or 1 second for all http calls to return the response. If some http call does not return by this time we return timeout for that resource. Following is the code:


import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class HttpClientTest4 {
	private static String apiKey="your_key_here";
	
	public static class RunnableRequest implements Runnable {
		String url;
		private CompletableFuture<String> cf;
				
		public RunnableRequest(String url, CompletableFuture<String> cf) {
			this.url = url;
			this.cf = cf;
		}
		
		@Override
		public void run() {
			try {
				//System.out.println("calling the task for movieId "+ url);
				CloseableHttpClient client = HttpClients.createDefault();
				HttpGet request = new HttpGet(url);
				CloseableHttpResponse response = client.execute(request);
				if(response.getStatusLine().getStatusCode()!=200) {
					cf.completeExceptionally(new RuntimeException("Issue getting response"));
					return;
				}
				String respString = EntityUtils.toString(response.getEntity());
				client.close();
				cf.complete(respString);
			} catch(IOException exp) {
				cf.completeExceptionally(exp);
			}
		}
		
	}
	
	public static class MyCompletebaleFuture<K,V> extends CompletableFuture<V> {
		K k;
		public MyCompletebaleFuture(K k) {
			super();
			this.k = k;
		}
		K getKey() {
			return k;
		}
	}

	public static void main(String[] args)  {

		ExecutorService executorSerivce = Executors.newFixedThreadPool(5);
		
		List<String> movieIds  = Arrays.asList("100","125","133","134","156","101","107","108","120","220","221","225","234","254","256");
		List<MyCompletebaleFuture<String,String>> futures = new ArrayList<>();
		for (String movieId : movieIds) {
			String url = "https://api.themoviedb.org/3/movie/"+movieId+"?api_key="+apiKey;
			MyCompletebaleFuture<String,String> cf = new MyCompletebaleFuture<>(movieId);
 			RunnableRequest req = new RunnableRequest(url, cf);			
			//System.out.println("submitting the task for movieId "+ movieId);			
			executorSerivce.submit(req);
			futures.add(cf);
		}
		
		ScheduledExecutorService ses = Executors.newScheduledThreadPool(1);
		Runnable timeoutTask = new Runnable() {
			@Override
			public void run() {
				RuntimeException re = new RuntimeException("Timeout");
				for (CompletableFuture<String> future : futures) {
					future.completeExceptionally(re);
				}				
			}			
		};
		ses.schedule(timeoutTask, 1500, TimeUnit.MILLISECONDS);
		
		for (MyCompletebaleFuture<String,String> future : futures) {
			String resp=null;
			Throwable failureCause=null;
			try {
				resp = future.get();
			} catch (InterruptedException e) {
				failureCause = e;
			} catch (ExecutionException e) {
				failureCause = e.getCause();
			}
			if(failureCause==null) {
				System.out.println("MovieID="+future.getKey() +" res="+resp);
			} else {
				System.out.println("MovieID="+future.getKey() + " exception="+failureCause.toString());
			}
		}
		executorSerivce.shutdown();
		ses.shutdown();
	}

}

For me the output was:


MovieID=100 res={"adult":false,"backdrop_path":"/tY6zVyt0OubPgCapbXFJLKhQqSu.jpg","belongs_to_collection":null,"budget":1350000,"genres":[{"id":35,"name":"Comedy"},{"id":80,"name":"Crime"}],"homepage":"http://www.universalstudiosentertainment.com/lock-stock-and-two-smoking-barrels/","id":100,"imdb_id":"tt0120735","original_language":"en","original_title":"Lock, Stock and Two Smoking Barrels","overview":"A card shark and his unwillingly-enlisted friends need to make a lot of cash quick after losing a sketchy poker match. To do this they decide to pull a heist on a small-time gang who happen to be operating out of the flat next door.","popularity":8.102,"poster_path":"/8kSerJrhrJWKLk1LViesGcnrUPE.jpg","production_companies":[{"id":491,"logo_path":"/rUp0lLKa1pr4UsPm8fgzmnNGxtq.png","name":"Summit Entertainment","origin_country":"US"},{"id":21920,"logo_path":null,"name":"The Steve Tisch Company","origin_country":""},{"id":13419,"logo_path":null,"name":"SKA Films","origin_country":""},{"id":1382,"logo_path":"/sOg7LGESPH5vCTOIdbMhLuypoLL.png","name":"PolyGram Filmed Entertainment","origin_country":"US"},{"id":20076,"logo_path":"/i9qXGJIP9fGN22PP5jXUVENbyHi.png","name":"HandMade Films","origin_country":"GB"}],"production_countries":[{"iso_3166_1":"GB","name":"United Kingdom"}],"release_date":"1998-03-05","revenue":28356188,"runtime":105,"spoken_languages":[{"english_name":"English","iso_639_1":"en","name":"English"}],"status":"Released","tagline":"A Disgrace to Criminals Everywhere.","title":"Lock, Stock and Two Smoking Barrels","video":false,"vote_average":8.2,"vote_count":4932}
MovieID=125 res={"adult":false,"backdrop_path":null,"belongs_to_collection":null,"budget":0,"genres":[{"id":99,"name":"Documentary"}],"homepage":"","id":125,"imdb_id":"tt0080668","original_language":"pl","original_title":"Dworzec","overview":"Kieslowski’s later film Dworzec (Station, 1980) portrays the atmosphere at Central Station in Warsaw after the rush hour.","popularity":1.94,"poster_path":"/c4xG9QoCx2zXc8iP3jQVLYDHiMi.jpg","production_companies":[{"id":2466,"logo_path":null,"name":"WFD","origin_country":""}],"production_countries":[{"iso_3166_1":"PL","name":"Poland"}],"release_date":"1980-01-01","revenue":0,"runtime":13,"spoken_languages":[{"english_name":"Polish","iso_639_1":"pl","name":"Polski"}],"status":"Released","tagline":"","title":"Railway Station","video":false,"vote_average":5.9,"vote_count":16}
MovieID=133 res={"adult":false,"backdrop_path":"/e6IPjjDzZDkgdkvxQtCjaVCMYJF.jpg","belongs_to_collection":null,"budget":0,"genres":[{"id":99,"name":"Documentary"}],"homepage":"","id":133,"imdb_id":"tt0054205","original_language":"en","original_title":"Primary","overview":"Primary is a documentary film about the primary elections between John F. Kennedy and Hubert Humphrey in 1960. Primary is the first documentary to use light equipment in order to follow their subjects in a more intimate filmmaking style. This unconventional way of filming created a new look for documentary films where the camera’s lens was right in the middle of what ever drama was occuring.","popularity":3.616,"poster_path":"/gVloWsthHRlw719vzRWRpBqDM20.jpg","production_companies":[{"id":2401,"logo_path":null,"name":"Drew Associates","origin_country":""},{"id":2402,"logo_path":null,"name":"Time","origin_country":""}],"production_countries":[{"iso_3166_1":"US","name":"United States of America"}],"release_date":"1960-01-01","revenue":0,"runtime":60,"spoken_languages":[{"english_name":"English","iso_639_1":"en","name":"English"}],"status":"Released","tagline":"","title":"Primary","video":false,"vote_average":6.5,"vote_count":24}
MovieID=134 res={"adult":false,"backdrop_path":"/rbsm633lnt3hKHEpk7FOt3dy82a.jpg","belongs_to_collection":null,"budget":26000000,"genres":[{"id":12,"name":"Adventure"},{"id":35,"name":"Comedy"},{"id":80,"name":"Crime"}],"homepage":"","id":134,"imdb_id":"tt0190590","original_language":"en","original_title":"O Brother, Where Art Thou?","overview":"In the deep south during the 1930s, three escaped convicts search for hidden treasure while a relentless lawman pursues them. On their journey they come across many comical characters and incredible situations. Based upon Homer's 'Odyssey'.","popularity":12.979,"poster_path":"/2YztYilviFCYcEtDAnrOstUWGie.jpg","production_companies":[{"id":2092,"logo_path":null,"name":"Mike Zoss Productions","origin_country":"US"},{"id":33,"logo_path":"/8lvHyhjr8oUKOOy2dKXoALWKdp0.png","name":"Universal Pictures","origin_country":"US"},{"id":9195,"logo_path":"/ou5BUbtulr6tIt699q6xJiEQTR9.png","name":"Touchstone Pictures","origin_country":"US"},{"id":10163,"logo_path":"/16KWBMmfPX0aJzDExDrPxSLj0Pg.png","name":"Working Title Films","origin_country":"GB"},{"id":694,"logo_path":"/5LEHONGkZBIoWvp1ygHOF8iyi1M.png","name":"StudioCanal","origin_country":"FR"}],"production_countries":[{"iso_3166_1":"FR","name":"France"},{"iso_3166_1":"GB","name":"United Kingdom"},{"iso_3166_1":"US","name":"United States of America"}],"release_date":"2000-08-30","revenue":71868327,"runtime":107,"spoken_languages":[{"english_name":"English","iso_639_1":"en","name":"English"}],"status":"Released","tagline":"They have a plan... but not a clue.","title":"O Brother, Where Art Thou?","video":false,"vote_average":7.3,"vote_count":3022}
MovieID=156 res={"adult":false,"backdrop_path":null,"belongs_to_collection":null,"budget":0,"genres":[{"id":35,"name":"Comedy"},{"id":18,"name":"Drama"},{"id":10749,"name":"Romance"}],"homepage":"","id":156,"imdb_id":"tt0329767","original_language":"da","original_title":"Wilbur begår selvmord","overview":"The strange comedy film of two close brothers; one, Wilbur, who wants to kill himself, and the other, Harbour, who tries to prevent this. When their father dies leaving them his bookstore they meet a woman who makes their lives a bit better yet with a bit more trouble as well.","popularity":3.117,"poster_path":"/f2nq2ZLqKPRaWb4HeOwxULKkSNk.jpg","production_companies":[{"id":223,"logo_path":"/b9Icqi8qGm7gJ6jVs8bauUo5N5Q.png","name":"Les Films du Losange","origin_country":"FR"},{"id":235,"logo_path":"/obOynoztBEhDNfHsDXBqOuQLAZk.png","name":"Nordisk Film","origin_country":"DK"},{"id":698,"logo_path":null,"name":"Scottish Screen","origin_country":""},{"id":758,"logo_path":"/fft3jrctrZNcgAxgN1w5Givtzip.png","name":"TV 2","origin_country":"DK"},{"id":980,"logo_path":null,"name":"The Glasgow Film Fund","origin_country":""}],"production_countries":[{"iso_3166_1":"DK","name":"Denmark"},{"iso_3166_1":"FR","name":"France"},{"iso_3166_1":"GB","name":"United Kingdom"},{"iso_3166_1":"SE","name":"Sweden"}],"release_date":"2002-11-08","revenue":0,"runtime":111,"spoken_languages":[{"english_name":"English","iso_639_1":"en","name":"English"}],"status":"Released","tagline":"Meet a man dying to live","title":"Wilbur Wants to Kill Himself","video":false,"vote_average":6.5,"vote_count":61}
MovieID=101 res={"adult":false,"backdrop_path":"/jRJrQ72VLyEnVsvwfep8Xjlvu8c.jpg","belongs_to_collection":null,"budget":16000000,"genres":[{"id":80,"name":"Crime"},{"id":18,"name":"Drama"},{"id":28,"name":"Action"}],"homepage":"","id":101,"imdb_id":"tt0110413","original_language":"en","original_title":"Léon: The Professional","overview":"Léon, the top hit man in New York, has earned a rep as an effective \"cleaner\". But when his next-door neighbors are wiped out by a loose-cannon DEA agent, he becomes the unwilling custodian of 12-year-old Mathilda. Before long, Mathilda's thoughts turn to revenge, and she considers following in Léon's footsteps.","popularity":37.287,"poster_path":"/wHqGb8J6tXBVwjqWooGMtNEjs2A.jpg","production_companies":[{"id":9,"logo_path":"/nda3dTUYdDrJ6rZqBpYvY865aDv.png","name":"Gaumont","origin_country":"FR"},{"id":66743,"logo_path":null,"name":"Les Films du Dauphin","origin_country":"FR"},{"id":5,"logo_path":"/71BqEFAF4V3qjjMPCpLuyJFB9A.png","name":"Columbia Pictures","origin_country":"US"}],"production_countries":[{"iso_3166_1":"US","name":"United States of America"},{"iso_3166_1":"FR","name":"France"}],"release_date":"1994-09-14","revenue":45284974,"runtime":111,"spoken_languages":[{"english_name":"English","iso_639_1":"en","name":"English"},{"english_name":"French","iso_639_1":"fr","name":"Français"},{"english_name":"Italian","iso_639_1":"it","name":"Italiano"}],"status":"Released","tagline":"If you want a job done well, hire a professional.","title":"Léon: The Professional","video":false,"vote_average":8.3,"vote_count":11096}
MovieID=107 res={"adult":false,"backdrop_path":"/32V4uHVRU2fbDsQl1dsYsZm3uba.jpg","belongs_to_collection":null,"budget":10000000,"genres":[{"id":80,"name":"Crime"},{"id":35,"name":"Comedy"}],"homepage":"","id":107,"imdb_id":"tt0208092","original_language":"en","original_title":"Snatch","overview":"Unscrupulous boxing promoters, violent bookmakers, a Russian gangster, incompetent amateur robbers and supposedly Jewish jewelers fight to track down a priceless stolen diamond.","popularity":17.959,"poster_path":"/56mOJth6DJ6JhgoE2jtpilVqJO.jpg","production_companies":[{"id":3287,"logo_path":"/bz6GbCQQXGNE56LTW9dwgksW0Iw.png","name":"Screen Gems","origin_country":"US"},{"id":13419,"logo_path":null,"name":"SKA Films","origin_country":""},{"id":5,"logo_path":"/71BqEFAF4V3qjjMPCpLuyJFB9A.png","name":"Columbia Pictures","origin_country":"US"}],"production_countries":[{"iso_3166_1":"GB","name":"United Kingdom"},{"iso_3166_1":"US","name":"United States of America"}],"release_date":"2000-09-01","revenue":83557872,"runtime":104,"spoken_languages":[{"english_name":"English","iso_639_1":"en","name":"English"},{"english_name":"Russian","iso_639_1":"ru","name":"Pусский"}],"status":"Released","tagline":"Stealin' stones and breakin' bones.","title":"Snatch","video":false,"vote_average":7.8,"vote_count":6747}
MovieID=108 res={"adult":false,"backdrop_path":"/s9rWmLANsVlSV4XmuC0IUcseGzO.jpg","belongs_to_collection":{"id":131,"name":"Three Colors Collection","poster_path":"/ph9rRSTG4xL8nubxNrK00VUMn5l.jpg","backdrop_path":"/AeHExfHIl70SZCea907KfEoSkfJ.jpg"},"budget":0,"genres":[{"id":18,"name":"Drama"}],"homepage":"","id":108,"imdb_id":"tt0108394","original_language":"fr","original_title":"Trois couleurs : Bleu","overview":"Julie is haunted by her grief after living through a tragic auto wreck that claimed the life of her composer husband and young daughter. Her initial reaction is to withdraw from her relationships, lock herself in her apartment and suppress her pain. But avoiding human interactions on the bustling streets of Paris proves impossible, and she eventually meets up with Olivier, an old friend who harbors a secret love for her, and who could draw her back to reality.","popularity":7.918,"poster_path":"/teDQRqy5STEhSvLERfGERBX25Br.jpg","production_companies":[{"id":591,"logo_path":"/q5I5RDwMEiqoNmfaJgd2LraEOJY.png","name":"France 3 Cinéma","origin_country":"FR"},{"id":1610,"logo_path":null,"name":"CED Productions","origin_country":""}],"production_countries":[{"iso_3166_1":"FR","name":"France"},{"iso_3166_1":"PL","name":"Poland"},{"iso_3166_1":"CH","name":"Switzerland"}],"release_date":"1993-09-08","revenue":0,"runtime":98,"spoken_languages":[{"english_name":"French","iso_639_1":"fr","name":"Français"},{"english_name":"Polish","iso_639_1":"pl","name":"Polski"}],"status":"Released","tagline":"","title":"Three Colors: Blue","video":false,"vote_average":7.7,"vote_count":1093}
MovieID=120 res={"adult":false,"backdrop_path":"/vRQnzOn4HjIMX4LBq9nHhFXbsSu.jpg","belongs_to_collection":{"id":119,"name":"The Lord of the Rings Collection","poster_path":"/nSNle6UJNNuEbglNvXt67m1a1Yn.jpg","backdrop_path":"/bccR2CGTWVVSZAG0yqmy3DIvhTX.jpg"},"budget":93000000,"genres":[{"id":12,"name":"Adventure"},{"id":14,"name":"Fantasy"},{"id":28,"name":"Action"}],"homepage":"http://www.lordoftherings.net/","id":120,"imdb_id":"tt0120737","original_language":"en","original_title":"The Lord of the Rings: The Fellowship of the Ring","overview":"Young hobbit Frodo Baggins, after inheriting a mysterious ring from his uncle Bilbo, must leave his home in order to keep it from falling into the hands of its evil creator. Along the way, a fellowship is formed to protect the ringbearer and make sure that the ring arrives at its final destination: Mt. Doom, the only place where it can be destroyed.","popularity":78.395,"poster_path":"/6oom5QYQ2yQTMJIbnvbkBL9cHo6.jpg","production_companies":[{"id":12,"logo_path":"/iaYpEp3LQmb8AfAtmTvpqd4149c.png","name":"New Line Cinema","origin_country":"US"},{"id":11,"logo_path":"/6FAuASQHybRkZUk08p9PzSs9ezM.png","name":"WingNut Films","origin_country":"NZ"},{"id":5237,"logo_path":null,"name":"The Saul Zaentz Company","origin_country":"US"},{"id":174,"logo_path":"/IuAlhI9eVC9Z8UQWOIDdWRKSEJ.png","name":"Warner Bros. Pictures","origin_country":"US"}],"production_countries":[{"iso_3166_1":"NZ","name":"New Zealand"},{"iso_3166_1":"US","name":"United States of America"}],"release_date":"2001-12-18","revenue":871368364,"runtime":179,"spoken_languages":[{"english_name":"English","iso_639_1":"en","name":"English"}],"status":"Released","tagline":"One ring to rule them all","title":"The Lord of the Rings: The Fellowship of the Ring","video":false,"vote_average":8.4,"vote_count":19453}
MovieID=220 res={"adult":false,"backdrop_path":"/u7i1XtbOubq0xwRy2Zbgmox6g9W.jpg","belongs_to_collection":null,"budget":1,"genres":[{"id":18,"name":"Drama"},{"id":10749,"name":"Romance"}],"homepage":"","id":220,"imdb_id":"tt0048028","original_language":"en","original_title":"East of Eden","overview":"In the Salinas Valley in and around World War I, Cal Trask feels he must compete against overwhelming odds with his brother for the love of their father. Cal is frustrated at every turn, from his reaction to the war, how to get ahead in business and in life, and how to relate to his estranged mother.","popularity":8.989,"poster_path":"/xv1MZVIop0SQqwLUymgE5eb2LFl.jpg","production_companies":[{"id":174,"logo_path":"/IuAlhI9eVC9Z8UQWOIDdWRKSEJ.png","name":"Warner Bros. Pictures","origin_country":"US"}],"production_countries":[{"iso_3166_1":"US","name":"United States of America"}],"release_date":"1955-03-09","revenue":5,"runtime":115,"spoken_languages":[{"english_name":"English","iso_639_1":"en","name":"English"}],"status":"Released","tagline":"The searing classic of paradise lost!","title":"East of Eden","video":false,"vote_average":7.6,"vote_count":496}
MovieID=221 res={"adult":false,"backdrop_path":"/jPXsPciwlPTG756CHkZt2jYpnNM.jpg","belongs_to_collection":null,"budget":1500000,"genres":[{"id":18,"name":"Drama"}],"homepage":"","id":221,"imdb_id":"tt0048545","original_language":"en","original_title":"Rebel Without a Cause","overview":"After moving to a new town, troublemaking teen Jim Stark is supposed to have a clean slate, although being the new kid in town brings its own problems. While searching for some stability, Stark forms a bond with a disturbed classmate, Plato, and falls for local girl Judy. However, Judy is the girlfriend of neighborhood tough, Buzz. When Buzz violently confronts Jim and challenges him to a drag race, the new kid's real troubles begin.","popularity":7.809,"poster_path":"/qNmYLRadv106IhG6T2wSFH3Bc7W.jpg","production_companies":[{"id":174,"logo_path":"/IuAlhI9eVC9Z8UQWOIDdWRKSEJ.png","name":"Warner Bros. Pictures","origin_country":"US"}],"production_countries":[{"iso_3166_1":"US","name":"United States of America"}],"release_date":"1955-10-29","revenue":199963,"runtime":111,"spoken_languages":[{"english_name":"English","iso_639_1":"en","name":"English"}],"status":"Released","tagline":"The bad boy from a good family.","title":"Rebel Without a Cause","video":false,"vote_average":7.6,"vote_count":1126}
MovieID=225 res={"adult":false,"backdrop_path":"/row4sMw5mVzKCWRbyrAQ39zvyLX.jpg","belongs_to_collection":{"id":457305,"name":"Solidarity Trilogy","poster_path":null,"backdrop_path":null},"budget":0,"genres":[{"id":18,"name":"Drama"},{"id":36,"name":"History"}],"homepage":"","id":225,"imdb_id":"tt0082222","original_language":"pl","original_title":"Człowiek z żelaza","overview":"In Warsaw in 1980, the Communist Party sends Winkel, a weak, alcoholic TV hack, to Gdansk to dig up dirt on the shipyard strikers, particularly on Maciek Tomczyk, an articulate worker whose father was killed in the December 1970 protests. Posing as sympathetic, Winkel interviews the people surrounding Tomczyk, including his detained wife, Agnieszka.","popularity":3.552,"poster_path":"/22wNUqKyz2m6wzAt31f26H8Y433.jpg","production_companies":[{"id":12838,"logo_path":null,"name":"Film Polski","origin_country":"PL"},{"id":6804,"logo_path":null,"name":"Zespól Filmowy \"X\"","origin_country":""}],"production_countries":[{"iso_3166_1":"PL","name":"Poland"}],"release_date":"1981-07-27","revenue":0,"runtime":147,"spoken_languages":[{"english_name":"Polish","iso_639_1":"pl","name":"Polski"},{"english_name":"Hungarian","iso_639_1":"hu","name":"Magyar"}],"status":"Released","tagline":"","title":"Man of Iron","video":false,"vote_average":6.9,"vote_count":37}
MovieID=234 exception=java.lang.RuntimeException: Timeout
MovieID=254 exception=java.lang.RuntimeException: Timeout
MovieID=256 exception=java.lang.RuntimeException: Timeout

You can see some requests are completed and some have timed out. We can combine result and send it as response.

Please comment if you need any clarification.

Thursday 29 July 2021

Moving from Nashorn to GraalVM

Recently we migrated from JDK 8 to JDK 16 and found that JavaScript engine is removed from Java. We tried including GraalVM jars into classpath but it did not work. The following code was returning null.


ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName( "JavaScript" );

After struggling for a long time I figured out that following list of jars works.


graal-sdk-21.2.0.jar
icu4j-69.1.jar
js-21.2.0.jar
js-launcher-21.2.0.jar
js-scriptengine-21.2.0.jar
regex-21.2.0.jar
truffle-api-21.2.0.jar
truffle-nfi-21.2.0.jar

Please comment if you have any question.

Wednesday 21 July 2021

Is struts 1.3 compatible with java 16?

Recently we were upgrading our application from Java 8 to Java 16. Our application was using struts 1.3 which is deprecated now and we did not know if it will work with Java 16.

For finding out if struts 1.3 is compatible with Java 16 I compiled struts 1.3 code with JDK 16. Surprisingly it compiled without a single error or warning. It gave me confidence that struts 1.3 is compatible with Java 16.

We migrated our struts 1.3 application to JDK 16 with Tomcat 9 and it is working fine.

java.lang.reflect.InaccessibleObjectException jboss-client.jar with Wildfly

Recently we we upgrading our application from JDK 8 to JDK16. We encountered following exception in our application running under Tomcat 9.  We were using wildfly-23 running with JDK-16 and Tomcat-9 running wih JDK-16


Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field private static final java.lang.reflect.Method jdk.proxy29.$Proxy136.m0 accessible: module jdk.proxy29 does not "opens jdk.proxy29" to unnamed module @182f9a0
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:357) ~[?:?]
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297) ~[?:?]
at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:177) ~[?:?]
at java.base/java.lang.reflect.Field.setAccessible(Field.java:171) ~[?:?]
at org.jboss.ejb-client@4.0.39.Final//org.jboss.ejb.client.EJBProxyInformation$1.doCompute(EJBProxyInformation.java:101) ~[jboss-client.jar:24.0.0.Final]
at org.jboss.ejb-client@4.0.39.Final//org.jboss.ejb.client.EJBProxyInformation$1.computeValue(EJBProxyInformation.java:72) ~[jboss-client.jar:24.0.0.Final]
at org.jboss.ejb-client@4.0.39.Final//org.jboss.ejb.client.EJBProxyInformation$1.computeValue(EJBProxyInformation.java:66) ~[jboss-client.jar:24.0.0.Final]
at java.base/java.lang.ClassValue.getFromHashMap(ClassValue.java:228) ~[?:?]
at java.base/java.lang.ClassValue.getFromBackup(ClassValue.java:210) ~[?:?]
at java.base/java.lang.ClassValue.get(ClassValue.java:116) ~[?:?]
at org.jboss.ejb-client@4.0.39.Final//org.jboss.ejb.client.EJBProxyInformation.forViewType(EJBProxyInformation.java:242) ~[jboss-client.jar:24.0.0.Final]
at org.jboss.ejb-client@4.0.39.Final//org.jboss.ejb.client.EJBLocator.getProxyInformation(EJBLocator.java:375) ~[jboss-client.jar:24.0.0.Final]
at org.jboss.ejb-client@4.0.39.Final//org.jboss.ejb.client.EJBLocator.getProxyConstructor(EJBLocator.java:370) ~[jboss-client.jar:24.0.0.Final]
at org.jboss.ejb-client@4.0.39.Final//org.jboss.ejb.client.EJBLocator.createProxyInstance(EJBLocator.java:387) ~[jboss-client.jar:24.0.0.Final]
at org.jboss.ejb-client@4.0.39.Final//org.jboss.ejb.client.EJBClient.createProxy(EJBClient.java:161) ~[jboss-client.jar:24.0.0.Final]
at org.jboss.ejb-client@4.0.39.Final//org.jboss.ejb.client.EJBClient.createProxy(EJBClient.java:156) ~[jboss-client.jar:24.0.0.Final]
at org.jboss.as.ejb3@23.0.0.Final//org.jboss.as.ejb3.remote.RemoteViewManagedReferenceFactory.getReference(RemoteViewManagedReferenceFactory.java:105) ~[?:?]
at org.jboss.as.ejb3@23.0.0.Final//org.jboss.as.ejb3.deployment.processors.EjbJndiBindingsDeploymentUnitProcessor$2$1.getReference(EjbJndiBindingsDeploymentUnitProcessor.java:268) ~[?:?]
at org.jboss.as.naming@23.0.0.Final//org.jboss.as.naming.ServiceBasedNamingStore.lookup(ServiceBasedNamingStore.java:143) ~[?:?]
... 14 more

We tried upgrading jboss-client.jar on Tomcat to version 24 but this did not help and we were still getting above error.

Then we tried upgrading Wildfly server also to version 24.0.0.Final, and after upgrading server to Wildfly-24 it started working.




Tuesday 20 July 2021

java.lang.NoSuchFieldError: DEFAULT_INCOMPATIBLE_IMPROVEMENTS

 Recently we upgraded our application to spring 5.3.9 and after that it started failing with following error.


Caused by: java.lang.NoSuchFieldError: DEFAULT_INCOMPATIBLE_IMPROVEMENTS
	at org.springframework.ui.freemarker.FreeMarkerConfigurationFactory.newConfiguration(FreeMarkerConfigurationFactory.java:327)
	at org.springframework.ui.freemarker.FreeMarkerConfigurationFactory.createConfiguration(FreeMarkerConfigurationFactory.java:257)
	at org.springframework.ui.freemarker.FreeMarkerConfigurationFactoryBean.afterPropertiesSet(FreeMarkerConfigurationFactoryBean.java:63)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1845)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1782)
	... 95 more

Reason of this error was traced to Freemarker library which we were using. After upgrading Freemarker library to 2.3.31 this problem was fixed.




Saturday 26 June 2021

When to use ForkJoinPool vs ExecutorService?

With introduction of ForkJoinPool in Java people started getting confused weather to use ForkJoinPool or ExecutorService. In this post I am going to discuss which thread pool use in which case.

ForkJoinPool is designed to be used for CPU intensive workloads. The default number of threads in ForkJoinPool is equal to number of CPUs on the system. If any threads goes into waiting state due to calling join() on some other ForkJoinTask an new compensatory thread is started to utilize all CPUs of the system. ForkJoinPool has a common pool which can be get by calling ForkJoinPool.commonPool() static method. The aim of this design is to use only single ForkJoinPool in the system with number of threads being equal to number of processors on the system. It can utilize full computation capacity of the system if all ForkJoinTasks are doing computation intensive activities.

But in real life scenario tasks are a mix of CPU and IO intensive tasks. IO intensive task are a bad choice for a ForkJoinPool. You should use Executor service for doing IO intensive tasks. In ExecutorService you can set number of threads according to IO capacity of your system instead of CPU capacity of your system.

If you want to call an IO intensive operation from a ForkJoinTask then you should create a class which implement ForkJoinPool.ManagedBlocker interface and do IO intensive operation in block() method. You need to call your ForkJoinPool.ManagedBlocker implementation using static method ForkJoinPool.managedBlock(). This method creates a compensatory threads before calling block() method. block() method is supposed to do IO operation and store result in some instance variable. After calling ForkJoinPool.managedBlock() you are supposed to call your business method to get result of IO operation. This way you can mix CPU intensive operations with IO intensive operations. A classic example is WebCrawler where you fetch pages from internet which is an IO intensive operation and after that you need to parse the HTML page to extract links which is a CPU intensive operation. 

I have not implemented a full WebCrawler but a sample code where I fetch web pages using an ExecutorService with 10 threads. I am using common pool of ForkJoinPool for submitting ForkJoinTasks. My ForkJoinTask submits the page fetch request to ExecutorService and wait for result using ForkJoinPool.managedBlock() static method. After getting the page it calculates SHA-256 sum for the content of the page and stores it in a ConcurrentHashMap. This way we can make full use of CPU capacity of the system and IO capacity of the system.

The sample code is:


import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
public class ForkJoinPoolTest {
	
	public static class FetchPage implements ForkJoinPool.ManagedBlocker {
		
		private String url;
		private ExecutorService executorSerivce;
		private byte[] pageBytes;
		
		private static ConcurrentHashMap<String,byte[]> pagesMap = new ConcurrentHashMap<>();
		
		public FetchPage(String url, ExecutorService executorSerivce) {
			this.url = url;
			this.executorSerivce = executorSerivce;
		}

		@Override
		public boolean block() throws InterruptedException {
			if((pageBytes= pagesMap.get(url))!=null) {
				return true;
			}
			Callable<byte[]> callable= new Callable<byte[]>() {
				public byte[] call() throws Exception {
					CloseableHttpClient client = HttpClients.createDefault();
					HttpGet request = new HttpGet(url);
					CloseableHttpResponse response = client.execute(request);
					return EntityUtils.toByteArray(response.getEntity());
				}
			};
			Future<byte[]> future = executorSerivce.submit(callable);
			try {
				pageBytes = future.get();
			} catch (InterruptedException | ExecutionException e) {
				pageBytes=null;
			}
			return true;
		}

		@Override
		public boolean isReleasable() {
			if(pageBytes!=null) {
				return true;
			}
			return false;
		}
		
		public byte[] getPage() {
			return pageBytes;
		}
		
	}
	
	private static ConcurrentHashMap<String, String> hashPageMap = new ConcurrentHashMap<>();
	
	public static class MyRecursiveTask extends RecursiveTask<String> {
		
		private String url;
		private ExecutorService executorSerivce;
		public MyRecursiveTask(String url, ExecutorService executorSerivce) {
			this.url = url;
			this.executorSerivce = executorSerivce;
		}

		protected String compute() {
			try {
				FetchPage fp = new FetchPage(url,executorSerivce);
				ForkJoinPool.managedBlock(fp );
				byte[] bytes = fp.getPage();
				if(bytes!=null) {
					String code = toHexString(getSHA(bytes));
					hashPageMap.put(url, code);
					return code;
				}
			} catch (InterruptedException | NoSuchAlgorithmException e) {
				return null;
			}
			return null;
		}
		
	}
	
	public static void main(String[] args) {
		ExecutorService executorSerivce = Executors.newFixedThreadPool(10);
		ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();
		
		MyRecursiveTask task1 = new MyRecursiveTask("https://www.yahoo.com", executorSerivce);
		MyRecursiveTask task2 = new MyRecursiveTask("https://www.google.com", executorSerivce);
		
		Future<String> f1 = forkJoinPool.submit(task1);
		Future<String> f2 = forkJoinPool.submit(task2);	
		try {
			String res1 = f1.get();
			String res2 = f2.get();
			System.out.println(res1);
			System.out.println(res2);
			executorSerivce.shutdown();
		} catch (InterruptedException | ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
    public static byte[] getSHA(byte[] input) throws NoSuchAlgorithmException
    { 
        // Static getInstance method is called with hashing SHA 
        MessageDigest md = MessageDigest.getInstance("SHA-256"); 
  
        // digest() method called 
        // to calculate message digest of an input 
        // and return array of byte
        return md.digest(input); 
    }
    
    public static String toHexString(byte[] hash)
    {
        // Convert byte array into signum representation 
        BigInteger number = new BigInteger(1, hash); 
  
        // Convert message digest into hex value 
        StringBuilder hexString = new StringBuilder(number.toString(16)); 
  
        // Pad with leading zeros
        while (hexString.length() < 32) 
        { 
            hexString.insert(0, '0'); 
        } 
  
        return hexString.toString(); 
    }
}

This article is also published at GeeksForGeeks

Apache HttpClient hangs

Recently we were trying to hit a website in multiple threads and get the response to improve the speed. But unfortunately the HttpClient hangs when we try to hit it from multiple threads. Following is my code which does not work.


import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

public class HttpClientTest {
	public static class CallableRequest implements Callable<CloseableHttpResponse> {
		CloseableHttpClient client;
		
		public CallableRequest(CloseableHttpClient client) {
			this.client = client;
		}
		
		@Override
		public CloseableHttpResponse call() throws Exception {
			HttpGet request = new HttpGet("https://www.yahoo.com");
			CloseableHttpResponse response = client.execute(request);
			return response;
		}
		
	}
	public static void main(String[] args) throws IOException, InterruptedException, ExecutionException {
		CloseableHttpClient client = HttpClients.createDefault();
		ExecutorService executorSerivce = Executors.newFixedThreadPool(10);
		List<Future<CloseableHttpResponse>> respList = new ArrayList<>();
		
		for(int i=0; i<10; i++) {
			CallableRequest req = new CallableRequest(client);
			Future<CloseableHttpResponse> future = executorSerivce.submit(req);
			respList.add(future);
		}

		for(Future<CloseableHttpResponse> respFuture: respList) {
			CloseableHttpResponse resp = respFuture.get();
			System.out.println("Status code:" + resp.getStatusLine().getStatusCode());
			resp.close();
		}
		executorSerivce.shutdown();
	}

}

The HttpClient looks like to have an internal queue and process a limited number of open requests. It can process new requests only after response of earlier requests is closed.

I am closing the response after reading it, so it should free up the open requests and it should work. Right? But it does not work.  

It is not working because I am submitting 10 requests and waiting for response in sequence. But the sequence of submitting the request and execution of them by HttpClient can be different. If HttpClient can process only 4 concurrent requests and your first request happen to be 5th request in HttpClient then you will be waiting for your first request to complete and it will never complete because some other 4 requests need to be closed for your first request to be processed. The 4 request which are completed are later in your list. That is why this program hanged.

The Solution

The solution is to wait for results in sequence of completion and not in sequence of submission. For doing that you need to use ExecutorCompletionService for submitting your request. It has a method take() which returns you next completed task. You can process the results of you requests in sequence in which they are getting completed. This way you HttpClient will not get locked up. Following is the code:


import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class HttpClientTest2 {
	public static class CallableRequest implements Callable<CloseableHttpResponse> {
		CloseableHttpClient client;
		
		public CallableRequest(CloseableHttpClient client) {
			this.client = client;
		}
		
		@Override
		public CloseableHttpResponse call() throws Exception {
			HttpGet request = new HttpGet("https://www.yahoo.com");
			CloseableHttpResponse response = client.execute(request);
			return response;
		}
		
	}
	public static void main(String[] args) throws IOException, InterruptedException, ExecutionException {
		CloseableHttpClient client = HttpClients.createDefault();
		ExecutorService executorSerivce = Executors.newFixedThreadPool(10);
		CompletionService<CloseableHttpResponse> completionService = new ExecutorCompletionService<>(executorSerivce);
		List<Future<CloseableHttpResponse>> respList = new ArrayList<>();
		
		for(int i=0; i<10; i++) {
			CallableRequest req = new CallableRequest(client);
			Future<CloseableHttpResponse> future = completionService.submit(req);
			respList.add(future);
		}

		for(int i=0; i<10; i++) {
			CloseableHttpResponse resp = completionService.take().get();
			System.out.println("Status code:" + resp.getStatusLine().getStatusCode());
			resp.close();
		}
		executorSerivce.shutdown();
	}

}

This code works perfectly with reordering of processing of the requests which can happen in multi-threading.

Saturday 12 June 2021

Accessing postgres running on your local machine from Minikube cluster

I was looking to containerize my application running in Wildfly  and Tomcat connecting to Postgres database. For experimenting with this I installed Minikube on my CentOS 7 Laptop.

I did not want to move my postgres database to Kubernetes cluster because of ephemeral nature of containers. I wanted to move my Wildfly and Tomcat instances to Kubernetes and connect to the database running on my local machine from these containers. In my production environment I plan to use AWS Postgres service.

Connecting to postgres server on my local machine was not straight forward, but once you know how to do it, it is an easy process. Here I am describing what I learned in this process.


Running postgres service on IP address interfaced with Minikube

Minikube runs inside a docker container. Docker creates many virtual networks on your machine. These interfaces are like multiple LAN cables connected to your computer and other end of these LAN cables it connected to different containers. Your computer have multiple IP addresses assigned to it. One for each interface. When you try to connect back from container you need to connect on the IP address of your computer assigned by the network on which your container is hosted. 

You can run following command to list out all interfaces attached to your computer.

ip addr
On my computer it returned the following output.

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enp1s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000
    link/ether 70:5a:0f:2a:78:76 brd ff:ff:ff:ff:ff:ff
3: wlp2s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 44:85:00:58:b2:a9 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.7/24 brd 192.168.1.255 scope global noprefixroute dynamic wlp2s0
       valid_lft 61564sec preferred_lft 61564sec
    inet6 fe80::c8fe:901b:5a99:57ca/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
4: virbr0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
    link/ether 52:54:00:d1:46:6b brd ff:ff:ff:ff:ff:ff
    inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0
       valid_lft forever preferred_lft forever
5: virbr0-nic: <BROADCAST,MULTICAST> mtu 1500 qdisc pfifo_fast master virbr0 state DOWN group default qlen 1000
    link/ether 52:54:00:d1:46:6b brd ff:ff:ff:ff:ff:ff
7: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:28:3e:b3:9e brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:28ff:fe3e:b39e/64 scope link 
       valid_lft forever preferred_lft forever
8: br-fdf8159cfe40: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 02:42:22:25:29:3e brd ff:ff:ff:ff:ff:ff
    inet 192.168.49.1/24 brd 192.168.49.255 scope global br-fdf8159cfe40
       valid_lft forever preferred_lft forever
    inet6 fe80::42:22ff:fe25:293e/64 scope link 
       valid_lft forever preferred_lft forever
14: veth683e46d@if13: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-fdf8159cfe40 state UP group default 
    link/ether 1e:b5:dd:3f:2c:87 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::1cb5:ddff:fe3f:2c87/64 scope link 
       valid_lft forever preferred_lft forever

Now run following command to find IP address of Minikube container.

minikube ip
On my local machine it returned following IP address:
192.168.49.2
Now you can analyze output of  "ip addr" and find out that interface 8 is connected to Minikube and IP address for virtual inteface connected to that network is 192.168.49.1

For your postgres instance to be connectable from Minikube containers it need to listen at 192.168.49.1

You need to modify /var/lib/pgsql/13/data/postgresql.conf file to include 192.168.49.1 in listen addresses. My file reads as follows:
listen_addresses = 'localhost,192.168.49.1'             # what IP address(es) to listen on;
Now you need to modify /var/lib/pgsql/13/data/pg_hba.conf file to allow access form Minikube network. My file reads like this:
# TYPE  DATABASE        USER            CIDR-ADDRESS            METHOD

# "local" is for Unix domain socket connections only
local   all             all                                     trust
#local   all         postgres                             password
# IPv4 local connections:
host    all             all             127.0.0.1/32           password
# IPv6 local connections:
host    all             all             ::1/128                password 
#localhost and kubernetes connection
host    all             all             192.168.49.0/24         password
local   all             dashboard                              password
Now restart postgres service using following command:
sudo systemctl restart postgresql-13
Now you are ready to connect to your local postgres server from Minikube cluster containers. You need to use 192.168.49.1 as host IP address for connecting to your local postgres server.

Testing connectivity to local postgres instance from Minikube cluster

For testing connectivity to your local postgres server from Minikube cluster you need to run a postgres image on Minikube cluster. This image contains "psql" command which can be used to connect to any remote postgres server. We will use this command to connect to our local postgres server from a pod running postgres image to confirm that connectivity is established.

Now run following command to run postgres image on one pod in Minikube cluster.
kubectl run postgres --image=postgres --env="POSTGRES_PASSWORD=mysecretpassword"
Now get a list of running pods using following command:
kubectl get pods
On my computer it returned following output:
NAME                                     READY   STATUS    RESTARTS   AGE
balanced-5744b548b4-fnvff                1/1     Running   3          13d
hello-minikube-6ddfcc9757-vfpcj          1/1     Running   3          13d
hello-node-7567d9fdc9-bpcfd              1/1     Running   3          13d
kubernetes-bootcamp-769746fd4-q65cm      1/1     Running   3          13d
kubernetes-bootcamp-769746fd4-tv2bd      1/1     Running   3          13d
postgres                                 1/1     Running   0          2m44s
tomcat-jas-deployment-85f8f68b99-jk6zr   2/2     Running   0          3h46m
I can see that my pod is running. Now run following command to connect to postgres pod and open a terminal window for this pod.
kubectl exec postgres -t -i -- /bin/bash
Transcript for my session is as follows:
localhost.localdomain: ~/docker-files/tomcat-jas >kubectl exec postgres -t -i -- /bin/bash
root@postgres:/# psql -h 192.168.49.1 -U postgres
Password for user postgres: 
psql (13.3 (Debian 13.3-1.pgdg100+1))
Type "help" for help.
Password used here is password for my local postgres server instance. I can access to all my tables from this postgre prompt.

Initially I faced connectivity problems and they were resolved after running following command:
sudo iptables --flush
Use the above command only if you need it. 

Please let me know in comments below if you face any problem in following this post.

Substituting environment variables in tomcat configuration file

I was looking to run my application running under tomcat in Kubernetes cluster. For that I need to create a container image. I don't want to put username and passwords of database in the image. But at the same time I need it in tomcat configuration. I was looking for a way to  substitute environment variables in tomcat configuration.

In Kubernetes we can populate environment variables from secrets managed by Kubernetes. This way you can keep your secrets and containerize your application also.

You need to add following argument to CATALINA_OPTS


-Dorg.apache.tomcat.util.digester.PROPERTY_SOURCE=org.apache.tomcat.util.digester.EnvironmentPropertySource

 

After passing this argument you can use environment variables same way as system properties. You can use it like this:


${ENV_VARIABE_NAME}


Substituting environment variables in Wildfly config

 I was looking a way to run my Wildfly application in Kubernetes. I realized that my database hostname, database name, username and passwords are written in Wildfly's standalone-full.xml file. I don't want to make these details a part of container image. This information is secret and confidential. Kubernetes provide a way to manage secrets and can make these secret available as environment variables.

It will be good if I can use these environment variables in my config file. This way I don't have to hard code sensitive  information in container image. Luckily Wildfly has a way of doing this. This is how you use it.


                <datasource jta="true" jndi-name="java:/PostgresDS" pool-name="PostgresDS" enabled="true" use-java-context="true">
                    <connection-url>jdbc:postgresql://${env.DBHOST}:5432/${env.DBNAME}</connection-url>
                    <driver>postgresql</driver>
                    <pool>
                        <min-pool-size>20</min-pool-size>
                        <initial-pool-size>20</initial-pool-size>
                        <max-pool-size>200</max-pool-size>
                        <prefill>true</prefill>
                    </pool>
                    <security>
                        <user-name>${env.DBUSER}</user-name>
                        <password>${env.DBPASSWORD}</password>
                    </security>
                </datasource>

As you can see environment variable name are prefixed with "env." it tells wildfly that we are looking for environment and not for system property. You can specify a default value also in case environment variable is undefined syntax for that is:


${env.DBUSER:sampleuser}

Here sampleuser is the default value.

Running local Docker image in Minikube

I was using Minikube to learn Kubernetes. Kubernetes pulls images from public container registries or your private registry hosted on a public server.  I did not want to upload my confidential container images to any public repository or spend money in creating my private repository hosted on a public server.

In this post I am going to describe a method to run container images from my local machine in single node kubernetes cluster hosted in Minikube. I am doing this experiment on n CentOS 7 Laptop.

Creating a Dockerfile

For creating a container image you first need to create a Dockerfile. In Dockerfile you provide a base image and modification you want on top of the content provided by the base image.

You can refer to https://docs.docker.com/develop/develop-images/dockerfile_best-practices/ for more information on Dockerfile.

I am using a tomcat image and creating my image without doing any modification to it. I just want to test if Minikube will be able to pick my image locally.

My Dockerfile content is:

FROM tomcat:latest

Setting docker environment variables

Minikube runs inside a docker container on linux box and hosts a local docker registry there. You can point to that docker registry by setting some environment variables. These environment variables and their value is listed by running following command.


minikube docker-env

The output on my local machine is:


export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.49.2:2376"
export DOCKER_CERT_PATH="/home/deployer/.minikube/certs"
export MINIKUBE_ACTIVE_DOCKERD="minikube"

# To point your shell to minikube's docker-daemon, run:
# eval $(minikube -p minikube docker-env)

Creating Docker image

After setting these environment variables you can run following command to build your image:


docker build -t tomcat-jas .

Now my docker image named tomcat-jas is pushed to Minikube's container registry. Now we can use it in docker deployments. 

Creating Kubernetes deployment

We need to create deployment using yaml file instead of providing image name in kubectl command because there we can't provide imagePullPolicy parameter. My tomcat-jas.yaml file has following content:


apiVersion: apps/v1
kind: Deployment
metadata:
  name: tomcat-jas-deployment
  labels:
    app: tomcat-jas
spec:
  replicas: 1
  selector:
    matchLabels:
      app: tomcat-jas
  template:
    metadata:
      labels:
        app: tomcat-jas
    spec:
      containers:
      - name: tomcat-jas
        image: tomcat-jas:latest
        imagePullPolicy: Never
        ports:
        - containerPort: 8080
      - name: httpd
        image: httpd:latest
        ports:
        - containerPort: 80

I am running two containers inside my pod one is httpd running at port 80 and another is tomcat running at port 8080. You can create deployment using following command:

kubectl create -f tomcat-jas.yaml

You can check status of you deployment using following command:


kubectl get deployment

Output on my machine is:


NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
balanced                1/1     1            1           13d
hello-minikube          1/1     1            1           13d
hello-node              1/1     1            1           13d
kubernetes-bootcamp     2/2     2            2           13d
tomcat-jas-deployment   1/1     1            1           2m21s

You can see that deployment is successfully running.

Exposing the deployment as a service

You can expose your newly created deployment to outside world using the following command:


kubectl expose deployment/tomcat-jas-deployment --type LoadBalancer --port 80,8080

You can check status of your service using following command:

kubectl get service

Output on my machine is:


NAME                    TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                       AGE
balanced                LoadBalancer   10.105.156.60   <pending>     8080:31961/TCP                13d
hello-minikube          NodePort       10.106.246.13   <none>        8080:31293/TCP                13d
hello-node              LoadBalancer   10.104.148.22   <pending>     8080:31192/TCP                13d
kubernetes              ClusterIP      10.96.0.1       <none>        443/TCP                       13d
kubernetes-bootcamp     NodePort       10.99.64.116    <none>        8080:30470/TCP                12d
tomcat-jas-deployment   LoadBalancer   10.103.14.144   <pending>     80:30147/TCP,8080:30692/TCP   19s

Accessing tomcat and httpd from your browser

The port 80 of our deployment is mapped to port number 30147 and port 8080 of our deployment is mapped to port 30690 of Minikube host. The IP address of host can be checked with following command:

minikube ip

Output of above command for me is:


192.168.49.2

You can combine port number and host IP address to make URL of exposed service for me URL will be http://192.168.49.2:30147 and http://192.168.49.2:30692

Thursday 3 June 2021

Fixing JBOSS-LOCAL-USER: javax.security.sasl.SaslException: ELY05128: Failed to read challenge file

 Recently I was trying to access EJBs hosted in wildfly 23 from a remote machine. Earlier I tested the client running on same machine and it was working fine. But when I put the client on a remote machine it started failing with a strange FileNotFoundError.


Caused by: javax.security.sasl.SaslException: Authentication failed: all available authentication mechanisms failed:
   JBOSS-LOCAL-USER: javax.security.sasl.SaslException: ELY05128: Failed to read challenge file [Caused by java.io.FileNotFoundException: /xxx/wildfly/standalone/tmp/auth/local3418030740192890591.challenge (No such file or directory)]
        at org.jboss.remoting3.remote.ClientConnectionOpenListener.allMechanismsFailed(ClientConnectionOpenListener.java:109) ~[jboss-client.jar:20.0.1.Final]
        at org.jboss.remoting3.remote.ClientConnectionOpenListener$Capabilities.handleEvent(ClientConnectionOpenListener.java:445) ~[jboss-client.jar:20.0.1.Final]
        at org.jboss.remoting3.remote.ClientConnectionOpenListener$Capabilities.handleEvent(ClientConnectionOpenListener.java:244) ~[jboss-client.jar:20.0.1.Final]
        at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92) ~[jboss-client.jar:20.0.1.Final]
        at org.xnio.conduits.ReadReadyHandler$ChannelListenerHandler.readReady(ReadReadyHandler.java:66) ~[jboss-client.jar:20.0.1.Final]
        at org.xnio.nio.NioSocketConduit.handleReady(NioSocketConduit.java:89) ~[jboss-client.jar:20.0.1.Final]
        at org.xnio.nio.WorkerThread.run(WorkerThread.java:591) ~[jboss-client.jar:20.0.1.Final]
        at ...asynchronous invocation...(Unknown Source) ~[?:?]
        at org.jboss.remoting3.EndpointImpl.connect(EndpointImpl.java:599) ~[jboss-client.jar:20.0.1.Final]
        at org.jboss.remoting3.EndpointImpl.connect(EndpointImpl.java:565) ~[jboss-client.jar:20.0.1.Final]
        at org.jboss.remoting3.ConnectionInfo$None.getConnection(ConnectionInfo.java:82) ~[jboss-client.jar:20.0.1.Final]
        at org.jboss.remoting3.ConnectionInfo.getConnection(ConnectionInfo.java:55) ~[jboss-client.jar:20.0.1.Final]
        at org.jboss.remoting3.EndpointImpl.doGetConnection(EndpointImpl.java:499) ~[jboss-client.jar:20.0.1.Final]
        at org.jboss.remoting3.EndpointImpl.getConnectedIdentity(EndpointImpl.java:445) ~[jboss-client.jar:20.0.1.Final]
        at org.jboss.remoting3.UncloseableEndpoint.getConnectedIdentity(UncloseableEndpoint.java:52) ~[jboss-client.jar:20.0.1.Final]
        at org.wildfly.naming.client.remote.RemoteNamingProvider.getFuturePeerIdentityPrivileged(RemoteNamingProvider.java:151) ~[jboss-client.jar:20.0.1.Final]
        at org.wildfly.naming.client.remote.RemoteNamingProvider.lambda$getFuturePeerIdentity$0(RemoteNamingProvider.java:138) ~[jboss-client.jar:20.0.1.Final]
        at org.wildfly.naming.client.remote.RemoteNamingProvider$$Lambda$80/601221733.run(Unknown Source) ~[?:?]
        at java.security.AccessController.doPrivileged(Native Method) ~[?:1.8.0_31]
        at org.wildfly.naming.client.remote.RemoteNamingProvider.getFuturePeerIdentity(RemoteNamingProvider.java:138) ~[jboss-client.jar:20.0.1.Final]
        at org.wildfly.naming.client.remote.RemoteNamingProvider.getPeerIdentity(RemoteNamingProvider.java:126) ~[jboss-client.jar:20.0.1.Final]
        at org.wildfly.naming.client.remote.RemoteNamingProvider.getPeerIdentityForNaming(RemoteNamingProvider.java:106) ~[jboss-client.jar:20.0.1.Final]
        ... 90 more
        Suppressed: javax.security.sasl.SaslException: ELY05128: Failed to read challenge file
                at org.wildfly.security.sasl.localuser.LocalUserClient.evaluateMessage(LocalUserClient.java:108) ~[jboss-client.jar:20.0.1.Final]
                at org.wildfly.security.sasl.util.AbstractSaslParticipant.evaluateMessage(AbstractSaslParticipant.java:219) ~[jboss-client.jar:20.0.1.Final]
                at org.wildfly.security.sasl.util.AbstractSaslClient.evaluateChallenge(AbstractSaslClient.java:98) ~[jboss-client.jar:20.0.1.Final]
                at org.wildfly.security.sasl.util.AbstractDelegatingSaslClient.evaluateChallenge(AbstractDelegatingSaslClient.java:54) ~[jboss-client.jar:20.0.1.Final]
                at org.wildfly.security.sasl.util.PrivilegedSaslClient.lambda$evaluateChallenge$0(PrivilegedSaslClient.java:55) ~[jboss-client.jar:20.0.1.Final]
                at org.wildfly.security.sasl.util.PrivilegedSaslClient$$Lambda$128/635454149.run(Unknown Source) ~[?:?]
                at java.security.AccessController.doPrivileged(Native Method) ~[?:1.8.0_31]
                at org.wildfly.security.sasl.util.PrivilegedSaslClient.evaluateChallenge(PrivilegedSaslClient.java:55) ~[jboss-client.jar:20.0.1.Final]
                at org.jboss.remoting3.remote.ClientConnectionOpenListener$Authentication.lambda$handleEvent$0(ClientConnectionOpenListener.java:649) ~[jboss-client.jar:20.0.1.Final]
                at org.jboss.remoting3.remote.ClientConnectionOpenListener$Authentication$$Lambda$129/1032360688.run(Unknown Source) ~[?:?]
                at org.jboss.remoting3.EndpointImpl$TrackingExecutor.lambda$execute$0(EndpointImpl.java:991) ~[jboss-client.jar:20.0.1.Final]
                at org.jboss.remoting3.EndpointImpl$TrackingExecutor$$Lambda$127/1508635946.run(Unknown Source) ~[?:?]
                at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35) ~[jboss-client.jar:20.0.1.Final]
                at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:1982) ~[jboss-client.jar:20.0.1.Final]
                at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1486) ~[jboss-client.jar:20.0.1.Final]
                at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1377) ~[jboss-client.jar:20.0.1.Final]
                at java.lang.Thread.run(Thread.java:745) ~[?:1.8.0_31]

Initially the problem looked like to be a bug in LocalUserClient class. But later I found that it is a functionality. For authenticating local users using sasl server creates a challenge file on server and send path of the file to client and client is supposed to return content of that file. If client is also running on same machine then it is able to read content of the file and return and authentication passes. If you are doing it from a remote machine it fails.

So how to authenticate from a remote client?

You need comment out local authentication line from your standalone-full.xml file to force it use remote authentication mechanism.


            <security-realm name="ApplicationRealm">
                <server-identities>
                    <ssl>
                        <keystore path="application.keystore" relative-to="jboss.server.config.dir" keystore-password="xxx" alias="xxx" key-password="xxx" generate-self-signed-certificate-host="localhost"/>
                    </ssl>
                </server-identities>
                <authentication>
                   <!-- <local default-user="$local" allowed-users="*" skip-group-loading="true"/>-->
                    <properties path="application-users.properties" relative-to="jboss.server.config.dir"/>
                </authentication>
                <authorization>
                    <properties path="application-roles.properties" relative-to="jboss.server.config.dir"/>
                </authorization>
            </security-realm>

After that you need to use add-user.sh command to add a user to wildfly. Now create wildfly-config.xml file with your user's credential on client machine.


<configuration>
    <authentication-client xmlns="urn:elytron:1.0">
        <authentication-rules>
            <rule use-configuration="default"/>
        </authentication-rules>
        <authentication-configurations>
            <configuration name="default">
                <sasl-mechanism-selector selector="#ALL"/>
                <set-user-name name="user"/>
                <credentials>
                    <clear-password password="password"/>
                </credentials>
            </configuration>
        </authentication-configurations>
    </authentication-client>
</configuration>

Now you can pass this credentials file to your EJB client using  parameter:


-Dwildfly.config.url=<your dir>/wildfly-config.xml

Now your client should start working without any authentication error. 

Please comment if you need any more information on this.