package org.pentaho.reporting.engine.classic.extensions.datasources.cda;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.swing.table.TableModel;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.NTCredentials;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.GetMethod;
import org.pentaho.reporting.engine.classic.core.DataFactory;
import org.pentaho.reporting.engine.classic.core.DataRow;
import org.pentaho.reporting.engine.classic.core.ReportDataFactoryException;
import org.pentaho.reporting.engine.classic.core.ResourceBundleFactory;
import org.pentaho.reporting.engine.classic.core.util.TypedTableModel;
import org.pentaho.reporting.libraries.base.config.Configuration;
import org.pentaho.reporting.libraries.base.util.CSVQuoter;
import org.pentaho.reporting.libraries.base.util.StringUtils;
import org.pentaho.reporting.libraries.formula.util.URLEncoder;
import org.pentaho.reporting.libraries.resourceloader.ResourceKey;
import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
/**
* Todo: Document me!
* <p/>
* Date: 16.12.10
* Time: 18:25
*
* @author Thomas Morgner.
*/
public class CdaDataFactory implements DataFactory, Cloneable
{
private static final char DOMAIN_SEPARATOR = '\\';
private String username;
private String password;
private String baseUrl;
private String baseUrlField;
private String solution;
private String path;
private String file;
private HashMap<String, String> querymappings;
private transient Configuration configuration;
private transient ResourceBundleFactory resourceBundleFactory;
private transient HttpClient client;
private transient volatile GetMethod httpCall;
public CdaDataFactory()
{
querymappings = new HashMap<String, String>();
}
public void initialize(final Configuration configuration,
final ResourceManager resourceManager,
final ResourceKey contextKey,
final ResourceBundleFactory resourceBundleFactory)
{
this.configuration = configuration;
this.resourceBundleFactory = resourceBundleFactory;
}
/**
* Checks whether the query would be executable by this datafactory. This performs a rough check, not a full query.
*
* @param query
* @param parameters
* @return
*/
public boolean isQueryExecutable(final String query, final DataRow parameters)
{
return querymappings.containsKey(query);
}
public void setQuery(final String name, final String queryString)
{
if (queryString == null)
{
querymappings.remove(name);
}
else
{
querymappings.put(name, queryString);
}
}
/**
* Queries a datasource. The string 'query' defines the name of the query. The Parameterset given here may contain
* more data than actually needed.
* <p/>
* The dataset may change between two calls, do not assume anything!
*
* @param query
* @param parameters
* @return
*/
public synchronized TableModel queryData(final String query, final DataRow parameters)
throws ReportDataFactoryException
{
if (query == null)
{
throw new NullPointerException("Query is null."); //$NON-NLS-1$
}
final String realQuery = getQuery(query);
if (realQuery == null)
{
throw new ReportDataFactoryException("Query '" + query + "' is not recognized."); //$NON-NLS-1$ //$NON-NLS-2$
}
final TypedTableModel parameterModel = fetchParameter(parameters, realQuery);
// name = 0
// type = 1
// defaultValue = 2
// pattern = 3
final HashMap<String, String> extraParams = new HashMap<String, String>();
extraParams.put("dataAccessId", realQuery);
final int nameIdx = parameterModel.findColumn("name");
final int typeIdx = parameterModel.findColumn("type");
final int defaultValueIdx = parameterModel.findColumn("defaultValue");
final int patternIdx = parameterModel.findColumn("pattern");
for (int p = 0; p < parameterModel.getRowCount(); p++)
{
final String name = (String) parameterModel.getValueAt(p, nameIdx);
final String type = (String) parameterModel.getValueAt(p, typeIdx);
final String pattern = (String) parameterModel.getValueAt(p, patternIdx);
// if parameter is null, use default value from cda
final Object value = parameters.get(name)==null?parameterModel.getValueAt(p, defaultValueIdx):parameters.get(name);
final String param = parameterToString(name, type, pattern, value);
extraParams.put(name, param);
}
return fetchData(parameters, "doQuery", extraParams);
}
private String parameterToString(final String name,
final String type,
final String pattern,
final Object raw) throws ReportDataFactoryException
{
if (raw == null)
{
return "";
}
if ("Date".equals(type))
{
if (raw instanceof Date == false && raw instanceof Number == false)
{
throw new ReportDataFactoryException("For parameter " + name + " Expected date, but got " + raw);
}
final SimpleDateFormat dateFormat = new SimpleDateFormat(pattern, resourceBundleFactory.getLocale());
dateFormat.setTimeZone(resourceBundleFactory.getTimeZone());
return dateFormat.format(raw);
}
if ("Integer".equals(type) || "Numeric".equals(type))
{
if (raw instanceof Number == false)
{
throw new ReportDataFactoryException("For parameter " + name + " Expected number, but got " + raw);
}
return String.valueOf(raw);
}
if ("String".equals(type))
{
return String.valueOf(raw);
}
if (type.endsWith("Array"))
{
if (raw.getClass().isArray() == false)
{
throw new ReportDataFactoryException("For parameter " + name + " Expected array, but got " + raw);
}
final CSVQuoter quoter = new CSVQuoter(';');
final String arrayType = type.substring(0, type.length() - 5);
final StringBuffer b = new StringBuffer();
final int length = Array.getLength(raw);
for (int i = 0; i < length; i++)
{
final Object o = Array.get(raw, i);
if (i > 0)
{
b.append(";");
}
final String str = parameterToString(name + "[" + i + "]", arrayType, pattern, o);
b.append(quoter.doQuoting(str));
}
}
throw new ReportDataFactoryException("Unknown type " + type + " for parameter " + name);
}
public String getQuery(final String name)
{
return querymappings.get(name);
}
public String[] getQueryNames()
{
return querymappings.keySet().toArray(new String[querymappings.size()]);
}
private String computeBaseUrl(final DataRow dataRow)
{
if (baseUrlField != null)
{
final Object baseUrlRaw = dataRow.get(baseUrlField);
if (baseUrlRaw != null)
{
return String.valueOf(baseUrlRaw);
}
}
return baseUrl;
}
private TypedTableModel fetchData(final DataRow dataRow,
final String method,
final Map<String, String> extraParameter) throws ReportDataFactoryException
{
final String baseURL = computeBaseUrl(dataRow);
if (StringUtils.isEmpty(baseURL, true))
{
throw new ReportDataFactoryException("Base URL is null");
}
try
{
final StringBuilder url = new StringBuilder();
url.append(baseURL);
url.append("/content/cda/");
url.append(method);
url.append("?");
url.append("outputType=xml");
url.append("&solution=");
url.append(encodeParameter(solution));
url.append("&path=");
url.append(encodeParameter(path));
url.append("&file=");
url.append(encodeParameter(file));
for (final Map.Entry<String, String> entry : extraParameter.entrySet())
{
final String key = encodeParameter(entry.getKey());
if (StringUtils.isEmpty(key))
{
continue;
}
// For custom parameters, we prepend 'param'. Exception is dataAccessId
if(key.equals("dataAccessId")){
url.append("&");
}
else{
url.append("¶m");
}
url.append(key);
url.append("=");
url.append(encodeParameter(entry.getValue()));
}
httpCall = new GetMethod(url.toString());
final HttpClient client = getHttpClient();
final int status = client.executeMethod(httpCall);
if (status != 200)
{
throw new ReportDataFactoryException("Failed to retrieve data: " + httpCall.getStatusLine());
}
final InputStream responseBody = httpCall.getResponseBodyAsStream();
return CdaResponseParser.performParse(responseBody);
}
catch (UnsupportedEncodingException use)
{
throw new ReportDataFactoryException("Failed to encode parameter", use);
}
catch (Exception e)
{
throw new ReportDataFactoryException("Failed to send request", e);
}
finally
{
httpCall = null;
}
}
private HttpClient getHttpClient()
{
if (client == null)
{
client = new HttpClient();
client.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
client.getParams().setAuthenticationPreemptive(true);
client.getState().setCredentials(AuthScope.ANY, getCredentials(username, password));
}
return client;
}
public static Credentials getCredentials(final String user,
final String password)
{
if (StringUtils.isEmpty(user))
{
return null;
}
final int domainIdx = user.indexOf(DOMAIN_SEPARATOR);
if (domainIdx == -1)
{
return new UsernamePasswordCredentials(user, password);
}
try
{
final String domain = user.substring(0, domainIdx);
final String username = user.substring(domainIdx + 1);
final String host = InetAddress.getLocalHost().getHostName();
return new NTCredentials(username, password, host, domain);
}
catch (UnknownHostException uhe)
{
return new UsernamePasswordCredentials(user, password);
}
}
private String getURLEncoding()
{
return configuration.getConfigProperty("org.pentaho.reporting.engine.classic.core.URLEncoding");
}
private String encodeParameter(final String value) throws UnsupportedEncodingException
{
if (StringUtils.isEmpty(value))
{
return "";
}
return URLEncoder.encode(value, getURLEncoding());
}
private TypedTableModel fetchParameter(final DataRow dataRow, final String queryId)
throws ReportDataFactoryException
{
final HashMap<String, String> extras = new HashMap<String, String>();
extras.put("dataAccessId", queryId);
return fetchData(dataRow, "listParameters", extras);
}
public DataFactory derive()
{
final CdaDataFactory clone = (CdaDataFactory) clone();
clone.client = null;
clone.httpCall = null;
return clone;
}
public String getUsername()
{
return username;
}
public void setUsername(final String username)
{
this.username = username;
}
public String getPassword()
{
return password;
}
public void setPassword(final String password)
{
this.password = password;
}
public String getBaseUrl()
{
return baseUrl;
}
public void setBaseUrl(final String baseUrl)
{
this.baseUrl = baseUrl;
}
public String getBaseUrlField()
{
return baseUrlField;
}
public void setBaseUrlField(final String baseUrlField)
{
this.baseUrlField = baseUrlField;
}
public String getSolution()
{
return solution;
}
public void setSolution(final String solution)
{
this.solution = solution;
}
public String getPath()
{
return path;
}
public void setPath(final String path)
{
this.path = path;
}
public String getFile()
{
return file;
}
public void setFile(final String file)
{
this.file = file;
}
public void open() throws ReportDataFactoryException
{
}
public void close()
{
client = null;
}
public Object clone()
{
try
{
final CdaDataFactory dataFactory = (CdaDataFactory) super.clone();
dataFactory.querymappings = (HashMap<String, String>) querymappings.clone();
dataFactory.httpCall = null;
return dataFactory;
}
catch (CloneNotSupportedException cne)
{
throw new IllegalStateException(cne);
}
}
public void cancelRunningQuery()
{
final GetMethod call = httpCall;
if (call != null)
{
call.abort();
}
}
}