net.sf.jasperreports.data.http.HttpDataService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jasperreports Show documentation
Show all versions of jasperreports Show documentation
Free Java Reporting Library
/*
* JasperReports - Free Java Reporting Library.
* Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved.
* http://www.jaspersoft.com
*
* Unless you have purchased a commercial license agreement from Jaspersoft,
* the following license terms apply:
*
* This program is part of JasperReports.
*
* JasperReports is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* JasperReports is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with JasperReports. If not, see .
*/
package net.sf.jasperreports.data.http;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import net.sf.jasperreports.annotations.properties.Property;
import net.sf.jasperreports.annotations.properties.PropertyScope;
import net.sf.jasperreports.data.AbstractDataAdapterService;
import net.sf.jasperreports.data.DataFileConnection;
import net.sf.jasperreports.data.DataFileService;
import net.sf.jasperreports.engine.JRDataset;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRParameter;
import net.sf.jasperreports.engine.JRPropertiesUtil;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.ParameterContributorContext;
import net.sf.jasperreports.properties.PropertyConstants;
import net.sf.jasperreports.util.SecretsUtil;
/**
* @author Lucian Chirita ([email protected])
*/
public class HttpDataService implements DataFileService
{
private static final Log log = LogFactory.getLog(HttpDataService.class);
public static final String HTTP_DATA_SERVICE_NAME = "net.sf.jasperreports.data.file.service:HTTP";
public static final String EXCEPTION_MESSAGE_KEY_NO_HTTP_URL_SET = "data.http.no.http.url.set";
public static final String EXCEPTION_MESSAGE_KEY_UNKNOWN_REQUEST_METHOD = "data.http.unknown.request.method";
/**
* @deprecated Replaced by {@link #PROPERTY_URL}.
*/
public static final String PARAMETER_URL = "HTTP_DATA_URL";
/**
* @deprecated Replaced by {@link #PROPERTY_USERNAME}.
*/
public static final String PARAMETER_USERNAME = "HTTP_DATA_USERNAME";
/**
* @deprecated Replaced by {@link #PROPERTY_PASSWORD}.
*/
public static final String PARAMETER_PASSWORD = "HTTP_DATA_PASSWORD";
/**
* @deprecated Replaced by {@link #PROPERTY_URL_PARAMETER}.
*/
public static final String PARAMETER_PREFIX_URL_PARAMETER = "HTTP_DATA_URL_PARAMETER_";
/**
* @deprecated Replaced by {@link #PROPERTY_POST_PARAMETER}.
*/
public static final String PARAMETER_PREFIX_POST_PARAMETER = "HTTP_DATA_POST_PARAMETER_";
/**
* Property that specifies the HTTP request method to be used by the HTTP data adapters.
* When used at parameter level, it does not need to provide a value, but is just used to mark the parameter that will provide the HTTP method.
*/
@Property (
category = PropertyConstants.CATEGORY_DATA_SOURCE,
scopes = {PropertyScope.DATASET, PropertyScope.PARAMETER},
scopeQualifications = {HTTP_DATA_SERVICE_NAME},
sinceVersion = PropertyConstants.VERSION_6_4_3,
valueType = RequestMethod.class
)
public static final String PROPERTY_METHOD = JRPropertiesUtil.PROPERTY_PREFIX + "http.data.method";
/**
* Property that specifies the base URL to be used by the HTTP data adapters.
* When used at parameter level, it does not need to provide a value, but is just used to mark the parameter that will provide the URL value.
*/
@Property (
category = PropertyConstants.CATEGORY_DATA_SOURCE,
scopes = {PropertyScope.DATASET, PropertyScope.PARAMETER},
scopeQualifications = {HTTP_DATA_SERVICE_NAME},
sinceVersion = PropertyConstants.VERSION_6_3_1
)
public static final String PROPERTY_URL = JRPropertiesUtil.PROPERTY_PREFIX + "http.data.url";
/**
* Property that specifies the user name to be used by the HTTP data adapters with basic authentication.
* When used at parameter level, it does not need to provide a value, but is just used to mark the parameter that will provide the user name value.
*/
@Property (
category = PropertyConstants.CATEGORY_DATA_SOURCE,
scopes = {PropertyScope.DATASET, PropertyScope.PARAMETER},
scopeQualifications = {HTTP_DATA_SERVICE_NAME},
sinceVersion = PropertyConstants.VERSION_6_3_1
)
public static final String PROPERTY_USERNAME = JRPropertiesUtil.PROPERTY_PREFIX + "http.data.username";
/**
* Property that specifies the password to be used by the HTTP data adapters with basic authentication.
* When used at parameter level, it does not need to provide a value, but is just used to mark the parameter that will provide the user password value.
*/
@Property (
category = PropertyConstants.CATEGORY_DATA_SOURCE,
scopes = {PropertyScope.DATASET, PropertyScope.PARAMETER},
scopeQualifications = {HTTP_DATA_SERVICE_NAME},
sinceVersion = PropertyConstants.VERSION_6_3_1
)
public static final String PROPERTY_PASSWORD = JRPropertiesUtil.PROPERTY_PREFIX + "http.data.password";
/**
* Property that specifies the name of the request parameter to be added to the URL when HTTP data adapter is used.
* If the property is present, but has no value, the name of the request parameter is the same as the report parameter name.
*/
@Property (
category = PropertyConstants.CATEGORY_DATA_SOURCE,
scopes = {PropertyScope.PARAMETER},
scopeQualifications = {HTTP_DATA_SERVICE_NAME},
sinceVersion = PropertyConstants.VERSION_6_3_1
)
public static final String PROPERTY_URL_PARAMETER = JRPropertiesUtil.PROPERTY_PREFIX + "http.data.url.parameter";
/**
* Property that specifies the POST/PUT request body to be sent when HTTP data adapter is used.
* When used at parameter level, it does not need to provide a value, but is just used to mark the parameter that will provide the POST/PUT request body value.
*/
@Property (
category = PropertyConstants.CATEGORY_DATA_SOURCE,
scopes = {PropertyScope.DATASET, PropertyScope.PARAMETER},
scopeQualifications = {HTTP_DATA_SERVICE_NAME},
sinceVersion = PropertyConstants.VERSION_6_3_1
)
public static final String PROPERTY_BODY = JRPropertiesUtil.PROPERTY_PREFIX + "http.data.body";
/**
* Property that specifies the name of the request POST parameter to be sent when HTTP data adapter is used.
* If the property is present, but has no value, the name of the request parameter is the same as the report parameter name.
*/
@Property (
category = PropertyConstants.CATEGORY_DATA_SOURCE,
scopes = {PropertyScope.PARAMETER},
scopeQualifications = {HTTP_DATA_SERVICE_NAME},
sinceVersion = PropertyConstants.VERSION_6_3_1
)
public static final String PROPERTY_POST_PARAMETER = JRPropertiesUtil.PROPERTY_PREFIX + "http.data.post.parameter";
/**
* Property that specifies the name of the request header to be sent when HTTP data adapter is used.
* If the property is present, but has no value, the name of the request header is the same as the report parameter name.
*/
@Property (
category = PropertyConstants.CATEGORY_DATA_SOURCE,
scopes = {PropertyScope.PARAMETER},
scopeQualifications = {HTTP_DATA_SERVICE_NAME},
sinceVersion = PropertyConstants.VERSION_6_3_1
)
public static final String PROPERTY_HEADER = JRPropertiesUtil.PROPERTY_PREFIX + "http.data.header";
private final ParameterContributorContext context;
private final HttpDataLocation dataLocation;
public HttpDataService(ParameterContributorContext context, HttpDataLocation dataLocation)
{
this.context = context;
this.dataLocation = dataLocation;
}
@Override
public DataFileConnection getDataFileConnection(Map parameters) throws JRException
{
CloseableHttpClient httpClient = createHttpClient(parameters);
HttpRequestBase request = createRequest(parameters);
return new HttpDataConnection(httpClient, request);
}
protected CloseableHttpClient createHttpClient(Map parameters)
{
HttpClientBuilder clientBuilder = HttpClients.custom();
// single connection
BasicHttpClientConnectionManager connManager = new BasicHttpClientConnectionManager();
clientBuilder.setConnectionManager(connManager);
// ignore cookies for now
RequestConfig requestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.IGNORE_COOKIES).build();
clientBuilder.setDefaultRequestConfig(requestConfig);
setAuthentication(parameters, clientBuilder);
CloseableHttpClient client = clientBuilder.build();
return client;
}
protected void setAuthentication(Map parameters, HttpClientBuilder clientBuilder)
{
String username = getUsername(parameters);
if (username != null)
{
String password = getPassword(parameters);
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
//FIXME proxy authentication?
credentialsProvider.setCredentials(
new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT),
new UsernamePasswordCredentials(username, password));
clientBuilder.setDefaultCredentialsProvider(credentialsProvider);
}
}
protected String getUsername(Map parameters)
{
String username = getPropertyOrParameterValue(PROPERTY_USERNAME, PARAMETER_USERNAME, parameters);
if (username == null)
{
username = dataLocation.getUsername();
}
return username;
}
protected String getPassword(Map parameters)
{
String password = getPropertyOrParameterValue(PROPERTY_PASSWORD, PARAMETER_PASSWORD, parameters);
if (password == null)
{
password = dataLocation.getPassword();
}
if (password != null)
{
SecretsUtil secrets = SecretsUtil.getInstance(context.getJasperReportsContext());
password = secrets.getSecret(AbstractDataAdapterService.SECRETS_CATEGORY, password);
}
return password;
}
protected HttpRequestBase createRequest(Map parameters)
{
URI requestURI = getRequestURI(parameters);
RequestMethod method = getMethod(parameters);
String body = getBody(parameters);
List postParameters = collectPostParameters(parameters);
if (method == null)
{
method = (body == null && postParameters.isEmpty()) ? RequestMethod.GET : RequestMethod.POST;
}
HttpRequestBase request;
switch (method)
{
case GET:
if (body != null)
{
log.warn("Ignoring request body for GET request to " + dataLocation.getUrl());
}
if (!postParameters.isEmpty())
{
log.warn("Ignoring POST parameters for GET request to " + dataLocation.getUrl());
}
request = createGetRequest(requestURI);
break;
case POST:
if (body == null)
{
request = createPostRequest(requestURI, postParameters);
}
else
{
if (!postParameters.isEmpty())
{
log.warn("Ignoring POST parameters for POST request having request body to " + dataLocation.getUrl());
}
request = createPostRequest(requestURI, body);
}
break;
case PUT:
if (body == null)
{
request = createPutRequest(requestURI, postParameters);
}
else
{
if (!postParameters.isEmpty())
{
log.warn("Ignoring POST parameters for PUT request having request body to " + dataLocation.getUrl());
}
request = createPutRequest(requestURI, body);
}
break;
default:
throw
new JRRuntimeException(
EXCEPTION_MESSAGE_KEY_UNKNOWN_REQUEST_METHOD,
new Object[]{method});
}
List headers = collectHeaders(parameters);
if (headers != null)
{
for (NameValuePair header : headers)
{
request.addHeader(header.getName(), header.getValue());
}
}
return request;
}
protected HttpGet createGetRequest(URI requestURI)
{
HttpGet httpGet = new HttpGet(requestURI);
return httpGet;
}
protected HttpPost createPostRequest(URI requestURI, String body)
{
HttpPost httpPost = new HttpPost(requestURI);
HttpEntity entity = createRequestEntity(body);
httpPost.setEntity(entity);
return httpPost;
}
protected HttpPost createPostRequest(URI requestURI, List postParameters)
{
HttpPost httpPost = new HttpPost(requestURI);
HttpEntity entity = createRequestEntity(postParameters);
httpPost.setEntity(entity);
return httpPost;
}
protected HttpPut createPutRequest(URI requestURI, String body)
{
HttpPut httpPost = new HttpPut(requestURI);
HttpEntity entity = createRequestEntity(body);
httpPost.setEntity(entity);
return httpPost;
}
protected HttpPut createPutRequest(URI requestURI, List postParameters)
{
HttpPut httpPost = new HttpPut(requestURI);
HttpEntity entity = createRequestEntity(postParameters);
httpPost.setEntity(entity);
return httpPost;
}
protected HttpEntity createRequestEntity(String body)
{
return new StringEntity(body, "UTF-8");//allow custom?
}
protected HttpEntity createRequestEntity(List postParameters)
{
UrlEncodedFormEntity formEntity;
try
{
formEntity = new UrlEncodedFormEntity(postParameters, "UTF-8");//allow custom?
}
catch (UnsupportedEncodingException e)
{
// should not happen
throw new JRRuntimeException(e);
}
return formEntity;
}
protected List collectUrlParameters(Map reportParameters)
{
return collectParameters(dataLocation.getUrlParameters(), reportParameters, PROPERTY_URL_PARAMETER, PARAMETER_PREFIX_URL_PARAMETER);
}
protected List collectPostParameters(Map reportParameters)
{
return collectParameters(dataLocation.getPostParameters(), reportParameters, PROPERTY_POST_PARAMETER, PARAMETER_PREFIX_POST_PARAMETER);
}
protected List collectHeaders(Map reportParameters)
{
return collectParameters(dataLocation.getHeaders(), reportParameters, PROPERTY_HEADER, null);
}
protected List collectParameters(
List dataAdapterParameters,
Map parameterValues,
String propertyName,
String parameterPrefix
)
{
List postParameters = new ArrayList();
Map requestParamMappings = new HashMap();
if (
context.getDataset() != null
&& context.getDataset().getParameters() != null
)
{
for (JRParameter parameter : context.getDataset().getParameters())
{
if (
parameter.hasProperties()
&& parameter.getPropertiesMap().containsProperty(propertyName)
)
{
String requestParamName = parameter.getPropertiesMap().getProperty(propertyName);
if (requestParamName == null)
{
requestParamName = parameter.getName();
}
requestParamMappings.put(requestParamName, parameter.getName());
}
}
}
if (dataAdapterParameters != null && !dataAdapterParameters.isEmpty())
{
for (HttpLocationParameter dataAdapterParameter : dataAdapterParameters)
{
String dataAdapterParamName = dataAdapterParameter.getName();
String paramValue = dataAdapterParameter.getValue();
if (paramValue != null)
{
String prefixConventionParamName = parameterPrefix == null ? null : (parameterPrefix + dataAdapterParamName);
String mappedParamName = requestParamMappings.get(dataAdapterParamName);
if (
(prefixConventionParamName != null
&& parameterValues.containsKey(prefixConventionParamName))
|| (requestParamMappings.containsKey(dataAdapterParamName)
&& parameterValues.containsKey(mappedParamName))
)
{
if (log.isDebugEnabled())
{
log.debug("data adapter parameter " + dataAdapterParamName + " overridden by the report");
}
}
else
{
if (log.isDebugEnabled())
{
log.debug("adding parameter " + dataAdapterParamName + " with value " + paramValue);
}
postParameters.add(new BasicNameValuePair(dataAdapterParamName, paramValue));
}
}
}
}
if (parameterPrefix != null)
{
for (Entry paramEntry : parameterValues.entrySet())
{
String paramName = paramEntry.getKey();
Object value = paramEntry.getValue();
if (paramName.startsWith(parameterPrefix))
{
String requestParamName = paramName.substring(parameterPrefix.length(), paramName.length());
String mappedParamName = requestParamMappings.get(requestParamName);
if (
value != null
&& (!requestParamMappings.containsKey(requestParamName)
|| !parameterValues.containsKey(mappedParamName))
)
{
String paramValue = toHttpParameterValue(value);
if (log.isDebugEnabled())
{
log.debug("adding parameter " + requestParamName + " with value " + paramValue);
}
postParameters.add(new BasicNameValuePair(requestParamName, paramValue));
}
else
{
if (log.isDebugEnabled())
{
log.debug("prefix convention parameter " + requestParamName + " overridden by the property mapped parameter");
}
}
}
}
}
if (
context.getDataset() != null
&& context.getDataset().getParameters() != null
)
{
// here, loop through dataset parameters and not through requestParamMappings because we want to support request parameter arrays
// by allowing multiple dataset parameters to provide value for the same request parameter
for (JRParameter parameter : context.getDataset().getParameters())
{
if (
parameter.hasProperties()
&& parameter.getPropertiesMap().containsProperty(propertyName)
)
{
String requestParamName = parameter.getPropertiesMap().getProperty(propertyName);
if (requestParamName == null)
{
requestParamName = parameter.getName();
}
String paramName = parameter.getName();
if (parameterValues.containsKey(paramName))
{
Object value = parameterValues.get(paramName);
String paramValue = toHttpParameterValue(value);
if (log.isDebugEnabled())
{
log.debug("adding parameter " + requestParamName + " with value " + paramValue);
}
postParameters.add(new BasicNameValuePair(requestParamName, paramValue));
}
}
}
}
return postParameters;
}
protected URI getRequestURI(Map parameters)
{
String url = getURL(parameters);
if (url == null)
{
throw
new JRRuntimeException(
EXCEPTION_MESSAGE_KEY_NO_HTTP_URL_SET,
(Object[])null);
}
try
{
URIBuilder uriBuilder = new URIBuilder(url);
List urlParameters = collectUrlParameters(parameters);
if (!urlParameters.isEmpty())
{
uriBuilder.addParameters(urlParameters);
}
URI uri = uriBuilder.build();
if (log.isDebugEnabled())
{
log.debug("request URI " + uri);
}
return uri;
}
catch (URISyntaxException e)
{
throw new JRRuntimeException(e);
}
}
protected String getURL(Map parameters)
{
String url = getPropertyOrParameterValue(PROPERTY_URL, PARAMETER_URL, parameters);
if (url == null)
{
url = dataLocation.getUrl();
}
return url;
}
protected RequestMethod getMethod(Map parameters)
{
String method = getPropertyOrParameterValue(PROPERTY_METHOD, null, parameters);
return method != null ? RequestMethod.valueOf(method.toUpperCase()) : dataLocation.getMethod();
}
protected String getBody(Map parameters)
{
String body = getPropertyOrParameterValue(PROPERTY_BODY, null, parameters);
if (body == null)
{
body = dataLocation.getBody();
}
return body;
}
protected String getPropertyOrParameterValue(String propName, String paramName, Map parameterValues)
{
String value = null;
JRDataset dataset = context.getDataset();
if (dataset != null && dataset.hasProperties())
{
value = JRPropertiesUtil.getOwnProperty(dataset, propName);
}
if (paramName != null && parameterValues.containsKey(paramName))//FIXMEDATAADAPTER should we fallback to prop name used as param name?
{
value = (String) parameterValues.get(paramName);
}
if (dataset != null)
{
JRParameter[] parameters = dataset.getParameters();
if (parameters != null)
{
for (JRParameter parameter : parameters)
{
if (
parameter.hasProperties()
&& parameter.getPropertiesMap().containsProperty(propName)
&& parameterValues.containsKey(parameter.getName())
)
{
value = (String)parameterValues.get(parameter.getName());
}
}
}
}
return value;
}
protected String toHttpParameterValue(Object value)
{
return String.valueOf(value);//FIXME do something smarter than String.valueOf
}
}