All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.simple4j.wsfeeler.pojoashttp.HTTPExposer Maven / Gradle / Ivy

package org.simple4j.wsfeeler.pojoashttp;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;

import org.apache.commons.beanutils.ConvertUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;

import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;

import spark.Spark;

/**
 * This class can be used to expose any method in any bean in a Spring ApplicationContext as a HTTP service
 */
public class HTTPExposer
{
    private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    private final ObjectMapper OBJECT_MAPPER = JsonMapper.builder()
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
            .serializationInclusion(Include.NON_NULL)
            .build();

    /**
     * org.springframework.context.ApplicationContext instance from which the bean methods are exposed
     */
    private ApplicationContext context;
    
    private int listenerPortNumber = -1;
    
    private int listenerThreadMax = 100;
    
    private int listenerThreadMin = 1;

    private int listenerIdleTimeoutMillis = 60000;
    
    private String urlBase = "/pojoashttp";
    
    /**
     * Constructor with org.springframework.context.ApplicationContext instance as parameter
     * @param context - org.springframework.context.ApplicationContext instance
     */
    public HTTPExposer(ApplicationContext context)
    {
    	super();
    	this.context = context;
    }
    
    /**
     * This method will turn on the listener to expose bean methods as web service
     */
    public void expose()
    {
    	if(listenerPortNumber > 0)
    	{
	        Spark.port(listenerPortNumber);
	        Spark.threadPool(listenerThreadMax, listenerThreadMin, listenerIdleTimeoutMillis);
    	}
        Spark.post(this.getUrlBase()+"/request.json", (request, response) -> 
        {
            String body = request.body();
            RequestJSON requestJSON = null;
            try
            {
                requestJSON = OBJECT_MAPPER.readValue(body, RequestJSON.class);
            }
            catch(Exception e)
            {
                throw new RuntimeException("Error parsing request body <" + body +">",  e);
            }
            
            LOGGER.info("parsed JSON");
            Object bean = context.getBean(requestJSON.getBeanId());
            LOGGER.info("got bean from Spring {}",bean);

            Class[] parameterTypes = null;
            Object[] parameterValues = null;
            MethodParameterJSON[] methodParameters = requestJSON.getMethodParameters();
            LOGGER.info("got method parameter");
            if(methodParameters != null && methodParameters.length > 0)
            {
                LOGGER.info("method parameter not null");
                parameterTypes = new Class[methodParameters.length];
                parameterValues = new Object[methodParameters.length];

                for (int i = 0; i < methodParameters.length; i++) {
                    LOGGER.info("creating instance for param:{}", methodParameters[i].getClassName());
                    LOGGER.info(":{}",methodParameters[i].getValue());
                    LOGGER.info(":{}",methodParameters[i].getValues());
                    //To call methods with primitive types, the client need to use TYPE field.
                    //Here is an example
                    //{"beanId":"someBean","methodName":"someMethod", "methodParameters" : [{"className":"java.lang.String","value":"someStringParam"},{"className":"java.lang.Integer.TYPE","value":"100"}]}
                    if(methodParameters[i].getClassName().endsWith(".TYPE"))
                    {
                        Class primitiveType = Class.forName(methodParameters[i].getClassName().replaceFirst("(\\.TYPE)$", ""));
                        parameterTypes[i] = (Class) primitiveType.getField("TYPE").get(null);
                    }
                    else
                    {
                        parameterTypes[i] = Class.forName(methodParameters[i].getClassName());
                    }
                    LOGGER.info("loaded param type {}", parameterTypes[i]);
                    if(methodParameters[i].getValue() != null)
                    {
                        if(parameterTypes[i].equals(methodParameters[i].getValue().getClass()))
                        {
                            LOGGER.info("parametertypes same as value type");
                            parameterValues[i] = methodParameters[i].getValue();
                        }
                        else
                        {
                            LOGGER.info("converting parametertype");
                            parameterValues[i] = ConvertUtils.convert(methodParameters[i].getValue(), parameterTypes[i]);
                            LOGGER.info("converting parametertype");
                        }
                    }
                    else if(methodParameters[i].getValues() != null)
                    {
                        parameterValues[i] = ConvertUtils.convert(methodParameters[i].getValues(), parameterTypes[i]);
                        parameterTypes[i] = parameterValues[i].getClass();
                    }
                    else
                    {
                        String parameterStringJSON = OBJECT_MAPPER.writeValueAsString(methodParameters[i].getValueJSON());
                        parameterValues[i] = OBJECT_MAPPER.readValue(parameterStringJSON, parameterTypes[i]);
                    }
                }
            }

            LOGGER.info("getting method instance");
            Method method = bean.getClass().getMethod(requestJSON.getMethodName(), parameterTypes);
            LOGGER.info("got it:{}", method);
            Object responseObj = method.invoke(bean, parameterValues);

            LOGGER.info("invoked it:{}",responseObj);

            String ret = OBJECT_MAPPER.writeValueAsString(responseObj);
            return "{\"returnValue\":"+ret+"}";
        });
    }

    /**
     * Port number to start the listener at
     */
	public int getListenerPortNumber()
	{
		return listenerPortNumber;
	}

	public void setListenerPortNumber(int listenerPortNumber)
	{
		this.listenerPortNumber = listenerPortNumber;
	}

    /**
     * Max thread count for the listener to serve requests
     */
	public int getListenerThreadMax()
	{
		return listenerThreadMax;
	}

	public void setListenerThreadMax(int listenerThreadMax)
	{
		this.listenerThreadMax = listenerThreadMax;
	}

    /**
     * Min thread count for the listener to serve requests
     */
	public int getListenerThreadMin()
	{
		return listenerThreadMin;
	}

	public void setListenerThreadMin(int listenerThreadMin)
	{
		this.listenerThreadMin = listenerThreadMin;
	}

    /**
     * Idle timeout in milli seconds for the listener thread to be destroyed to reach min thread count
     */
	public int getListenerIdleTimeoutMillis()
	{
		return listenerIdleTimeoutMillis;
	}

	public void setListenerIdleTimeoutMillis(int listenerIdleTimeoutMillis)
	{
		this.listenerIdleTimeoutMillis = listenerIdleTimeoutMillis;
	}

    /**
     * URL base path for the listener to expose the service
     */
	public String getUrlBase()
	{
		return urlBase;
	}

	public void setUrlBase(String urlBase)
	{
		this.urlBase = urlBase;
	}
    
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy