net.sf.jasperreports.data.http.HttpDataService.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.jasperreports.data.http.HttpDataService.java

Source

/*
 * 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 <http://www.gnu.org/licenses/>.
 */
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 (lucianc@users.sourceforge.net)
 */
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<String, Object> parameters) throws JRException {
        CloseableHttpClient httpClient = createHttpClient(parameters);
        HttpRequestBase request = createRequest(parameters);
        return new HttpDataConnection(httpClient, request);
    }

    protected CloseableHttpClient createHttpClient(Map<String, Object> 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<String, Object> 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<String, Object> parameters) {
        String username = getPropertyOrParameterValue(PROPERTY_USERNAME, PARAMETER_USERNAME, parameters);
        if (username == null) {
            username = dataLocation.getUsername();
        }
        return username;
    }

    protected String getPassword(Map<String, Object> 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<String, Object> parameters) {
        URI requestURI = getRequestURI(parameters);

        RequestMethod method = getMethod(parameters);
        String body = getBody(parameters);
        List<NameValuePair> 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<NameValuePair> 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<NameValuePair> 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<NameValuePair> 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<NameValuePair> 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<NameValuePair> collectUrlParameters(Map<String, Object> reportParameters) {
        return collectParameters(dataLocation.getUrlParameters(), reportParameters, PROPERTY_URL_PARAMETER,
                PARAMETER_PREFIX_URL_PARAMETER);
    }

    protected List<NameValuePair> collectPostParameters(Map<String, Object> reportParameters) {
        return collectParameters(dataLocation.getPostParameters(), reportParameters, PROPERTY_POST_PARAMETER,
                PARAMETER_PREFIX_POST_PARAMETER);
    }

    protected List<NameValuePair> collectHeaders(Map<String, Object> reportParameters) {
        return collectParameters(dataLocation.getHeaders(), reportParameters, PROPERTY_HEADER, null);
    }

    protected List<NameValuePair> collectParameters(List<HttpLocationParameter> dataAdapterParameters,
            Map<String, Object> parameterValues, String propertyName, String parameterPrefix) {
        List<NameValuePair> postParameters = new ArrayList<NameValuePair>();

        Map<String, String> requestParamMappings = new HashMap<String, String>();
        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<String, Object> 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<String, Object> 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<NameValuePair> 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<String, Object> parameters) {
        String url = getPropertyOrParameterValue(PROPERTY_URL, PARAMETER_URL, parameters);
        if (url == null) {
            url = dataLocation.getUrl();
        }
        return url;
    }

    protected RequestMethod getMethod(Map<String, Object> parameters) {
        String method = getPropertyOrParameterValue(PROPERTY_METHOD, null, parameters);
        return method != null ? RequestMethod.valueOf(method.toUpperCase()) : dataLocation.getMethod();
    }

    protected String getBody(Map<String, Object> parameters) {
        String body = getPropertyOrParameterValue(PROPERTY_BODY, null, parameters);
        if (body == null) {
            body = dataLocation.getBody();
        }
        return body;
    }

    protected String getPropertyOrParameterValue(String propName, String paramName,
            Map<String, Object> 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
    }
}