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

org.simplity.tp.HttpClient Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2017 simplity.org
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.simplity.tp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.URL;

import org.simplity.json.JSONObject;
import org.simplity.json.JSONWriter;
import org.simplity.kernel.ApplicationError;
import org.simplity.kernel.Tracer;
import org.simplity.kernel.comp.ValidationContext;
import org.simplity.kernel.db.DbAccessType;
import org.simplity.kernel.util.JsonUtil;
import org.simplity.kernel.util.TextUtil;
import org.simplity.kernel.util.XmlUtil;
import org.simplity.kernel.value.Value;
import org.simplity.service.ServiceContext;

/**
 * Get response from a server using rest call
 *
 * @author infosys.com
 *
 */
public class HttpClient extends Action {
	private static final char DOLLAR = '$';
	private static final String JSON = "/json";
	private static final String XML = "/xml";

	/**
	 * Complete HTTP URL string starting. Example
	 * http://app.acnecorp.com/a/b/c.html?b=s&j=3
	 * this string may contain ${variableName} in which case the string is
	 * created at run time after substituting values for these at run time. If
	 * no value is found, we treat it as empty string and still go ahead.
	 */
	String urlString;
	/**
	 * The HTTP method, GET, POST etc..
	 */
	String httpMethod;

	/**
	 * application/json, application/xml, and text/html are the common ones.
	 *
	 */

	String contentType;
	/**
	 * By default, we do not send any additional data as part of request. That
	 * is, we send only header for request. You may send data using a
	 * specification that is similar to OutputData of a service.
	 *
	 */
	OutputData requestData;
	/**
	 * In case the data to be sent for request is prepared using some logic into
	 * a field, we just send the value of that field
	 */
	String requestFieldName;
	/**
	 * By default, we extract all fields from response json/xml into service
	 * context. You may specify expected fields on the lines of
	 * inputSpecification for a service.
	 *
	 */
	InputData responseData;
	/**
	 * in case you have logic that processes the response, we set the response
	 * to this field
	 */
	String responseFieldName;
	/**
	 * Proxy details
	 */
	String proxy;
	/**
	 * proxy port. Required if proxy is specified.
	 */
	int proxyPort;
	/**
	 * Proxy username
	 */
	String proxyUserName;
	/**
	 * Proxy pwd
	 */
	String proxyPassword;

	/**
	 * in case url has variables in it, cache its parts for efficiency at run
	 * time. into an array which has its
	 * odd-idex (0-based) has names and other
	 */
	private String[] urlParts;

	/**
	 * if user and password are known at design time, we cache an instance of
	 * authentication
	 */
	private Authenticator authenticator;
	/**
	 * in case proxy user is a fieldName whose value is to be taken at run time
	 */
	private String proxyUserField;

	/**
	 * in case proxy pwd is a fieldName whose value is to be taken at run time
	 */
	private String proxyPwdField;
	/**
	 * handy boolean
	 */
	private boolean isJson;
	/**
	 * handy boolean
	 */
	private boolean isXml;

	@Override
	protected Value doAct(ServiceContext ctx) {
		String txt;
		if (this.urlParts == null) {
			txt = this.urlString;
		} else {
			txt = TextUtil.substituteFields(this.urlParts, ctx);
		}
		String responseText;
		if (txt.equals(".")) {
			/*
			 * special case for loop back
			 */
			responseText = this.getRequestText(ctx);
		} else {
			responseText = this.getHttpResponse(txt, ctx);
		}
		if (this.isJson) {
			if (this.responseData != null) {
				this.responseData.extractFromJson(responseText, ctx);
			} else {
				JsonUtil.extractAll(new JSONObject(responseText), ctx);
			}
		} else if (this.isXml) {
			if (this.responseData != null) {
				throw new ApplicationError(
						"We are not yet ready with xml based extraction of data.");
			}
			XmlUtil.extractAll(responseText, ctx);
		} else {
			ctx.setTextValue(this.responseFieldName, responseText);
		}
		return Value.VALUE_TRUE;
	}

	/**
	 * @param txt
	 * @param ctx
	 * @return
	 */
	private String getHttpResponse(String txt, ServiceContext ctx) {
		try {
			URL url = new URL(txt);
			HttpURLConnection conn = null;

			/*
			 * get connection
			 */
			if (this.proxy != null) {
				Authenticator auth = this.authenticator == null
						? this.getAuth(ctx) : this.authenticator;

				Proxy proxyCon = new Proxy(Proxy.Type.HTTP,
						new InetSocketAddress(this.proxy, this.proxyPort));
				Authenticator.setDefault(auth);
				conn = (HttpURLConnection) url.openConnection(proxyCon);
			} else {
				conn = (HttpURLConnection) url.openConnection();
			}

			/*
			 * despatch request
			 */
			conn.setRequestMethod(this.httpMethod);
			conn.setRequestProperty("Accept", this.contentType);
			conn.setDoOutput(true);
			String req = this.getRequestText(ctx);
			if(req != null){
				conn.getOutputStream().write(req.getBytes("UTF-8"));
			}
			/*
			 * receive response
			 */
			int resp = conn.getResponseCode();
			if (resp != 200) {
				throw new ApplicationError("Http call for url " + url
						+ " returned with a non200 status " + resp);
			}
			return readResponse(conn);
		} catch (ApplicationError e) {
			throw e;
		} catch (Exception e) {
			throw new ApplicationError(e,
					"Error while rest call using url " + txt);
		}
	}

	/**
	 * @param ctx
	 * @return
	 */
	private String getRequestText(ServiceContext ctx) {
		if (this.requestFieldName != null) {
			String req = ctx.getTextValue(this.requestFieldName);
			if (req == null) {
				Tracer.trace("No value for field " + this.requestFieldName
						+ " in context, and hence no data is sent with the http request.");
			}
			return req;
		}
		if (this.requestData != null) {
			if (!this.isJson) {
				throw new ApplicationError(
						"We are not ready with an xml output formatter.");

			}
			JSONWriter writer = new JSONWriter();
			writer.object();
			this.requestData.dataToJson(writer, ctx);
			writer.endObject();
			return writer.toString();
		}
		return null;
	}

	/**
	 * get an authenticator using our user name and password from ctx;
	 *
	 * @param ctx
	 * @return
	 */
	private Authenticator getAuth(ServiceContext ctx) {
		/*
		 * by our design, either userName or password is to be extracted from
		 * context
		 */
		String user;
		String pwd;
		if (this.proxyUserField == null) {
			user = this.proxyUserName;
		} else {
			Value value = ctx.getValue(this.proxyUserField);
			if (value == null) {
				throw new ApplicationError("Field " + this.proxyUserField
						+ " not found in service context at run time. This is required for a rest call through proxy.");
			}
			user = value.toString();
		}
		if (this.proxyPwdField == null) {
			pwd = this.proxyPassword;
		} else {
			Value value = ctx.getValue(this.proxyPwdField);
			if (value == null) {
				throw new ApplicationError("Field " + this.proxyPwdField
						+ " not found in service context at run time. This is required for a rest call through proxy.");
			}
			pwd = value.toString();
		}
		return new MyAuthenticator(user, pwd);
	}

	private static String readResponse(HttpURLConnection conn)
			throws IOException {
		BufferedReader reader = null;
		StringBuilder sbf = new StringBuilder();
		try {
			reader = new BufferedReader(
					new InputStreamReader((conn.getInputStream())));
			int ch;
			while ((ch = reader.read()) > -1) {
				sbf.append((char) ch);
			}
			reader.close();
			return sbf.toString();
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (Exception e) {
					//
				}
			}
		}
	}

	@Override
	public DbAccessType getDataAccessType() {
		return DbAccessType.NONE;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.simplity.tp.Action#getReady()
	 */
	@Override
	public void getReady(int idx, Service service) {
		super.getReady(idx, service);
		this.urlParts = TextUtil.parseToParts(this.urlString);
		if (this.proxyUserName != null
				&& this.proxyUserName.charAt(0) == DOLLAR) {
			this.proxyUserField = this.proxyUserName.substring(1);
		}
		if (this.proxyPassword != null
				&& this.proxyPassword.charAt(0) == DOLLAR) {
			this.proxyPwdField = this.proxyPassword.substring(1);
		}
		if (this.proxyPwdField != null && this.proxyUserField != null) {
			this.authenticator = new MyAuthenticator(this.proxyUserName,
					this.proxyPassword);
		}
		if (this.contentType.endsWith(JSON)) {
			this.isJson = true;
		} else if (this.contentType.endsWith(XML)) {
			this.isXml = true;
		} else if (this.responseFieldName == null) {
			throw new ApplicationError(
					"responseFieldName is requried for a restClient action that has its content type set to "
							+ this.contentType);
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.simplity.tp.Action#validate(org.simplity.kernel.comp.
	 * ValidationContext, org.simplity.tp.Service)
	 */
	@Override
	public int validate(ValidationContext ctx, Service service) {
		int count = super.validate(ctx, service);
		/*
		 * mandatory fields
		 */
		count += ctx.checkMandatoryField("urlString", this.urlString);
		count += ctx.checkMandatoryField("restMethod", this.httpMethod);
		count += ctx.checkMandatoryField("contentType", this.contentType);

		/*
		 * mandatory fields for proxy
		 */
		if (this.proxy == null) {
			if (this.proxyPort != 0 || this.proxyUserName != null
					|| this.proxyPassword != null) {
				ctx.addError(
						"proxy port, user, password are not relavant when proxy is not specified.");
				count++;
			}
		} else if (this.proxyPort == 0) {
				ctx.addError("proxyPort is required if proxy is to be used.");
				count++;
		}

		/*
		 * handling response
		 */
		boolean responseIsJson = false;
		boolean responseIsXml = false;
		if (this.contentType != null) {
			responseIsJson = this.contentType.endsWith(JSON);
			responseIsXml = this.contentType.endsWith(XML);
		}
		/*
		 * we are not ready with input/output specification for xml
		 */
		if (responseIsXml) {
			if (this.requestData != null || this.responseData != null) {
				ctx.addError(
						"We are yet to implement input/output data specification for xml. Please manage on your own with requestFieldName/responseFieldName for the time being.");
				count++;
			}
		}
		/*
		 * fieldName or output data, but not both..
		 */
		if (this.responseFieldName == null) {
			if (responseIsJson == false && responseIsXml == false) {
				ctx.addError(
						"You should specify responseFieldName when content-type is non-json and non-xml.");
				count++;
			}
		} else {
			if (this.responseData != null) {
				ctx.addError(
						"You should specify either responseFieldName or responseData, but not both, to handle the response.");
				count++;
			}
		}
		return count;
	}
}

/**
 * authenticator instance used only in this class.
 *
 */
class MyAuthenticator extends Authenticator {
	private final PasswordAuthentication auth;

	MyAuthenticator(String userName, String userPassword) {
		this.auth = new PasswordAuthentication(userName,
				userPassword.toCharArray());
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.net.Authenticator#getPasswordAuthentication()
	 */
	/** {@inheritDoc} */
	@Override
	protected PasswordAuthentication getPasswordAuthentication() {
		return this.auth;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy