/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ro.nextreports.server.service;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.jcr.RepositoryException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import ro.nextreports.server.cache.Cache;
import ro.nextreports.server.cache.CacheFactory;
import ro.nextreports.server.cache.ChartCacheKey;
import ro.nextreports.server.domain.Chart;
import ro.nextreports.server.domain.DrillEntityContext;
import ro.nextreports.server.exception.NotFoundException;
import ro.nextreports.server.report.next.NextUtil;
import ro.nextreports.server.report.util.ReportUtil;
import ro.nextreports.server.util.ChartUtil;
import ro.nextreports.server.util.ConnectionUtil;
import ro.nextreports.server.util.StorageUtil;
import ro.nextreports.server.util.WidgetUtil;
import ro.nextreports.server.web.dashboard.EntityWidget;
import ro.nextreports.server.web.dashboard.chart.ChartWidget;
import ro.nextreports.engine.ReportRunnerException;
import ro.nextreports.engine.chart.ChartRunner;
import ro.nextreports.engine.chart.ChartType;
import ro.nextreports.engine.exporter.exception.NoDataFoundException;
import ro.nextreports.engine.i18n.I18nLanguage;
import ro.nextreports.engine.i18n.I18nUtil;
import ro.nextreports.engine.querybuilder.sql.dialect.CSVDialect;
import ro.nextreports.engine.queryexec.QueryException;
import ro.nextreports.engine.util.ParameterUtil;
/**
* @author Decebal Suiu
*/
public class DefaultChartService implements ChartService {
private static final Logger LOG = LoggerFactory.getLogger(DefaultChartService.class);
private CacheFactory cacheFactory;
private StorageService storageService;
private DashboardService dashboardService;
@Required
public void setCacheFactory(CacheFactory cacheFactory) {
this.cacheFactory = cacheFactory;
}
@Required
public void setStorageService(StorageService storageService) {
this.storageService = storageService;
}
@Required
public void setDashboardService(DashboardService dashboardService) {
this.dashboardService = dashboardService;
}
// Methods with chartWidget makes also use of chart type and chart settings !!
// Methods with chart do not now anything about settings!!
public String getJsonData(Chart chart)
throws ReportRunnerException, NoDataFoundException, TimeoutException {
return getJsonData(chart, null, null);
}
public String getJsonData(Chart chart, boolean isHTML5)
throws ReportRunnerException, NoDataFoundException, TimeoutException {
return getJsonData(chart, null, null, isHTML5);
}
public String getJsonData(ChartWidget chartWidget)
throws ReportRunnerException, NoDataFoundException, TimeoutException {
return getJsonData(null, chartWidget, null, null);
}
public String getJsonData(ChartWidget chartWidget, boolean isHTML5)
throws ReportRunnerException, NoDataFoundException, TimeoutException {
return getJsonData(null, chartWidget, null, null, isHTML5);
}
public String getJsonData(Chart chart, DrillEntityContext drillContext)
throws ReportRunnerException, NoDataFoundException, TimeoutException {
return getJsonData(chart, null, drillContext, null);
}
public String getJsonData(Chart chart, DrillEntityContext drillContext, boolean isHTML5)
throws ReportRunnerException, NoDataFoundException, TimeoutException {
return getJsonData(chart, null, drillContext, null, isHTML5);
}
public String getJsonData(ChartWidget chartWidget, DrillEntityContext drillContext)
throws ReportRunnerException, NoDataFoundException, TimeoutException {
return getJsonData(null, chartWidget, drillContext, null);
}
public String getJsonData(ChartWidget chartWidget, DrillEntityContext drillContext, boolean isHTML5)
throws ReportRunnerException, NoDataFoundException, TimeoutException {
return getJsonData(null, chartWidget, drillContext, null, isHTML5);
}
public String getJsonData(Chart chart, Map<String,Object> urlQueryParameters)
throws ReportRunnerException, NoDataFoundException, TimeoutException {
return getJsonData(chart, null, null, urlQueryParameters);
}
public String getJsonData(Chart chart, Map<String,Object> urlQueryParameters, boolean isHTML5)
throws ReportRunnerException, NoDataFoundException, TimeoutException {
return getJsonData(chart, null, null, urlQueryParameters, isHTML5);
}
public String getJsonData(Chart chart, DrillEntityContext drillContext, Map<String,Object> urlQueryParameters)
throws ReportRunnerException, NoDataFoundException, TimeoutException {
return getJsonData(chart, null, drillContext, urlQueryParameters);
}
public String getJsonData(Chart chart, DrillEntityContext drillContext, Map<String,Object> urlQueryParameters, boolean isHTML5)
throws ReportRunnerException, NoDataFoundException, TimeoutException {
return getJsonData(chart, null, drillContext, urlQueryParameters, isHTML5);
}
public String getJsonData(ChartWidget chartWidget, Map<String,Object> urlQueryParameters)
throws ReportRunnerException, NoDataFoundException, TimeoutException {
return getJsonData(null, chartWidget, null, urlQueryParameters);
}
public String getJsonData(ChartWidget chartWidget, Map<String,Object> urlQueryParameters, boolean isHTML5)
throws ReportRunnerException, NoDataFoundException, TimeoutException {
return getJsonData(null, chartWidget, null, urlQueryParameters, isHTML5);
}
public String getJsonData(ChartWidget chartWidget, DrillEntityContext drillContext, Map<String,Object> urlQueryParameters)
throws ReportRunnerException, NoDataFoundException, TimeoutException {
return getJsonData(null, chartWidget, drillContext, urlQueryParameters);
}
public String getJsonData(ChartWidget chartWidget, DrillEntityContext drillContext, Map<String,Object> urlQueryParameters, boolean isHTML5)
throws ReportRunnerException, NoDataFoundException, TimeoutException {
return getJsonData(null, chartWidget, drillContext, urlQueryParameters, isHTML5);
}
private String getJsonData(Chart chart, ChartWidget chartWidget, DrillEntityContext drillContext, Map<String,Object> urlQueryParameters)
throws ReportRunnerException, NoDataFoundException, TimeoutException {
return getJsonData(chart, chartWidget, drillContext, urlQueryParameters, false);
}
private String getJsonData(Chart chart, ChartWidget chartWidget, DrillEntityContext drillContext, Map<String,Object> urlQueryParameters, boolean isHTML5)
throws ReportRunnerException, NoDataFoundException, TimeoutException {
if ((chart != null) && (chartWidget != null)) {
throw new IllegalArgumentException("Chart and Chart Widget cannot be both not null!");
}
if (chartWidget != null) {
chart = (Chart)chartWidget.getEntity();
if (chart == null) {
// setEntity on widget is done by loadWidget method from DefaultDashboardService
// if we are from a html iframe : entity is not set
// so take care here to set it if not found
String entityId = chartWidget.getInternalSettings().get(EntityWidget.ENTITY_ID);
try {
chart = (Chart)storageService.getEntityById(entityId);
} catch (NotFoundException e) {
throw new IllegalArgumentException("Chart widget has a null entity chart : could not get chart by id!");
}
}
}
ro.nextreports.engine.chart.Chart nextChart = NextUtil.getChart(chart.getContent());
if (chartWidget != null) {
// override settings
// keep the old style if we change chart type
byte oldStyle = nextChart.getType().getStyle();
String chartType = WidgetUtil.getChartType(dashboardService, chartWidget);
nextChart.setType(new ChartType(ChartUtil.getChartType(chartType), oldStyle));
}
Connection connection = null;
Map<String, Object> parameterValues = new HashMap<String, Object>();
if (chartWidget != null) {
// chart is running with settings parameter values
ChartUtil.initParameterSettings(parameterValues,
chartWidget.getQueryRuntime(),
dashboardService.getUserWidgetParameters(chartWidget.getId()));
} else {
// chart is running with default values
// test to see if we can get the values without creating a connection to database
if (ParameterUtil.checkForParametersWithDefaultSource(nextChart.getReport())) {
try {
connection = ConnectionUtil.createConnection(storageService, chart.getDataSource());
ParameterUtil.initNotHiddenDefaultParameterValues(connection, nextChart.getReport(), parameterValues);
} catch (RepositoryException e) {
throw new ReportRunnerException("Cannot connect to database", e);
} catch (QueryException e) {
ConnectionUtil.closeConnection(connection);
throw new ReportRunnerException(e);
}
} else {
try {
ParameterUtil.initStaticNotHiddenDefaultParameterValues(nextChart.getReport(), parameterValues);
} catch (QueryException e) {
throw new ReportRunnerException(e);
}
}
try {
ParameterUtil.initNotHiddenDefaultParameterValues(connection, nextChart.getReport(), parameterValues);
} catch (QueryException e) {
ConnectionUtil.closeConnection(connection);
throw new ReportRunnerException(e);
}
ChartUtil.initHiddenHardcodedParameters(parameterValues);
}
// drill parameters
if (drillContext != null) {
// put first settings values of the parent root chart (some of then may be overridden by drill parameters)
// there is a name parameter convention between current drilldown chart and parent root chart (the default
// values of the current drill down chart are overidden by the settings values of the parent root chart)
if (drillContext.getDrillParameterValues().size() > 0) {
Map<String, Object> settingsParams = drillContext.getSettingsValues();
for (String key : settingsParams.keySet()) {
parameterValues.put(key, settingsParams.get(key));
}
}
Map<String , Object> drillParams = drillContext.getDrillParameterValues();
for (String key : drillParams.keySet()) {
if (!parameterValues.containsKey(key)) {
System.err.println("Parameter " + key + " not found!");
} else {
System.out.println("Parameter " + key + " updated with value " + drillParams.get(key));
}
parameterValues.put(key, drillParams.get(key));
}
}
// parameters from embedded code
try {
ReportUtil.addUrlQueryParameters(storageService.getSettings(), chart, parameterValues, urlQueryParameters);
} catch (Exception e1) {
e1.printStackTrace();
LOG.error(e1.getMessage(), e1);
}
Cache cache = null;
ChartCacheKey cacheKey = null;
boolean cacheable = chart.getExpirationTime() > 0;
if (cacheable) {
cache = cacheFactory.getCache(chart.getId(), chart.getExpirationTime());
cacheKey = new ChartCacheKey(parameterValues, nextChart.getType().getType());
boolean hitCache = cache.hasElement(cacheKey);
if (hitCache) {
ConnectionUtil.closeConnection(connection);
String jsonData = (String) cache.get(cacheKey);
if (LOG.isDebugEnabled()) {
LOG.debug("Get jsonData for '" + StorageUtil.getPathWithoutRoot(chart.getPath()) + "' from cache");
}
return jsonData;
}
}
final ChartRunner runner = new ChartRunner();
if (isHTML5) {
runner.setGraphicType(ChartRunner.HTML5_TYPE);
}
runner.setParameterValues(parameterValues);
runner.setChart(nextChart);
I18nLanguage language = I18nUtil.getLocaleLanguage(nextChart);
if (language != null) {
runner.setLanguage(language.getName());
}
//connection was not created yet (all parameters have static default values)
if (connection == null) {
try {
connection = ConnectionUtil.createConnection(storageService, chart.getDataSource());
} catch (RepositoryException e) {
throw new ReportRunnerException("Cannot connect to database", e);
}
}
boolean csv = CSVDialect.DRIVER_CLASS.equals(chart.getDataSource().getDriver());
runner.setConnection(connection, csv);
int timeout = WidgetUtil.getTimeout(dashboardService, chartWidget);
runner.setQueryTimeout(timeout);
if (drillContext != null) {
runner.setDrillFunction(drillContext.getDrillLink());
}
FutureTask<String> runTask = null;
try {
runTask = new FutureTask<String>(new Callable<String>() {
public String call() throws Exception {
ByteArrayOutputStream outputStream = null;
try {
outputStream = new ByteArrayOutputStream();
runner.run(outputStream);
outputStream.close();
return new String(outputStream.toByteArray(), "UTF-8");
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
// ignore
}
}
}
}
});
new Thread(runTask).start();
String jsonData = runTask.get(timeout, TimeUnit.SECONDS);
if (cacheable) {
if (LOG.isDebugEnabled()) {
LOG.debug("Put jsonData for '" + StorageUtil.getPathWithoutRoot(chart.getPath()) + "' in cache");
}
cache.put(cacheKey, jsonData);
}
return jsonData;
} catch (Exception e) {
if (e instanceof TimeoutException) {
throw new TimeoutException("Timeout of " + timeout + " seconds ellapsed.");
} else {
if (e instanceof ExecutionException) {
Throwable cause = ((ExecutionException)e).getCause();
if (cause instanceof NoDataFoundException) {
throw (NoDataFoundException)cause;
} else {
throw new ReportRunnerException(cause);
}
}else if (e instanceof NoDataFoundException) {
throw (NoDataFoundException)e;
} else {
throw new ReportRunnerException(e);
}
}
} finally {
ConnectionUtil.closeConnection(connection);
}
}
}