/* * 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.awt.Color; import java.sql.Connection; import java.text.Collator; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import javax.jcr.RepositoryException; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Ehcache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Required; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.transaction.annotation.Transactional; import ro.nextreports.server.StorageConstants; import ro.nextreports.server.aop.Profile; import ro.nextreports.server.cache.Cache; import ro.nextreports.server.cache.CacheFactory; import ro.nextreports.server.cache.QueryCacheKey; import ro.nextreports.server.cache.ehcache.WidgetCacheEventListener; import ro.nextreports.server.domain.Chart; import ro.nextreports.server.domain.DashboardState; import ro.nextreports.server.domain.DrillEntityContext; import ro.nextreports.server.domain.Entity; import ro.nextreports.server.domain.Folder; import ro.nextreports.server.domain.Link; import ro.nextreports.server.domain.NextContent; import ro.nextreports.server.domain.Report; import ro.nextreports.server.domain.UserWidgetParameters; import ro.nextreports.server.domain.WidgetState; import ro.nextreports.server.exception.DuplicationException; import ro.nextreports.server.exception.NotFoundException; import ro.nextreports.server.exception.ReferenceException; 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.PermissionUtil; import ro.nextreports.server.util.ServerUtil; import ro.nextreports.server.util.StorageUtil; import ro.nextreports.server.util.WidgetUtil; import ro.nextreports.server.web.dashboard.Dashboard; import ro.nextreports.server.web.dashboard.DashboardColumn; import ro.nextreports.server.web.dashboard.DashboardUtil; import ro.nextreports.server.web.dashboard.DefaultDashboard; import ro.nextreports.server.web.dashboard.EntityWidget; import ro.nextreports.server.web.dashboard.Widget; import ro.nextreports.server.web.dashboard.WidgetDescriptor; import ro.nextreports.server.web.dashboard.WidgetFactory; import ro.nextreports.server.web.dashboard.WidgetLocation; import ro.nextreports.server.web.dashboard.WidgetRegistry; import ro.nextreports.server.web.dashboard.alarm.AlarmWidget; import ro.nextreports.server.web.dashboard.chart.ChartWidget; import ro.nextreports.server.web.dashboard.display.DisplayWidget; import ro.nextreports.server.web.dashboard.drilldown.DrillDownWidget; import ro.nextreports.server.web.dashboard.indicator.IndicatorWidget; import ro.nextreports.server.web.dashboard.table.TableWidget; import ro.nextreports.engine.ReportRunner; import ro.nextreports.engine.ReportRunnerException; import ro.nextreports.engine.chart.ChartRunner; import ro.nextreports.engine.exporter.exception.NoDataFoundException; import ro.nextreports.engine.exporter.util.AlarmData; import ro.nextreports.engine.exporter.util.DisplayData; import ro.nextreports.engine.exporter.util.IndicatorData; import ro.nextreports.engine.exporter.util.TableData; import ro.nextreports.engine.i18n.I18nLanguage; import ro.nextreports.engine.i18n.I18nUtil; import ro.nextreports.engine.querybuilder.sql.dialect.CSVDialect; import ro.nextreports.engine.util.ColorUtil; /** * @author Decebal Suiu */ public class DefaultDashboardService implements DashboardService { private static final Logger LOG = LoggerFactory.getLogger(DefaultDashboardService.class); private StorageService storageService; private SecurityService securityService; private WidgetFactory widgetFactory; private WidgetRegistry widgetRegistry; private CacheFactory cacheFactory; private CacheManager cacheManager; @Required public void setCacheFactory(CacheFactory cacheFactory) { this.cacheFactory = cacheFactory; } @Required public void setStorageService(StorageService storageService) { this.storageService = storageService; } @Required public void setSecurityService(SecurityService securityService) { this.securityService = securityService; } @Required public void setWidgetFactory(WidgetFactory widgetFactory) { this.widgetFactory = widgetFactory; } @Required public void setWidgetRegistry(WidgetRegistry widgetRegistry) { this.widgetRegistry = widgetRegistry; } @Required public void setCacheManager(CacheManager cacheManager) { this.cacheManager = cacheManager; // TODO in spring 3.x you can add CacheEventListener in EhCacheFactoryBean !!! Ehcache cache = cacheManager.getEhcache("entitiesCache"); WidgetCacheEventListener listener = new WidgetCacheEventListener(); listener.setDashboardService(DefaultDashboardService.this); cache.getCacheEventNotificationService().registerListener(listener); } @Transactional public List<Dashboard> getMyDashboards() { String dashboardsPath = getMyDashboardsPath(); String username = getUsername(); if (!storageService.entityExists(dashboardsPath)) { Folder dashboardsFolder = new Folder(username, dashboardsPath); try { storageService.addEntity(dashboardsFolder); } catch (DuplicationException e) { // never happening throw new RuntimeException(e); } LOG.info("Created 'dashboards' repository for user '" + username + "'"); String id = addDashboard(new DefaultDashboard(MY_DASHBOARD_NAME, 2)); LOG.info("Created '" + MY_DASHBOARD_NAME + "' dashboard for user '" + username + "'"); // cannot use getEntitiesByClassName (see below) because transaction is not committed yet try { Dashboard dashboard = getDashboard(id); List<Dashboard> tmp = new ArrayList<Dashboard>(); tmp.add(dashboard); return tmp; } catch (NotFoundException e) { // never happening throw new RuntimeException(e); } } Entity[] entities; try { entities = storageService.getEntitiesByClassName(dashboardsPath, DashboardState.class.getName()); } catch (NotFoundException e) { // never happening throw new RuntimeException(e); } return getDashboards(entities); } @Transactional(readOnly = true) public List<DashboardState> getDashboards(String user) { List<DashboardState> result = new ArrayList<DashboardState>(); String dashboardsPath = StorageConstants.DASHBOARDS_ROOT + "/" + user; if (!storageService.entityExists(dashboardsPath)) { return result; } Entity[] entities; try { entities = storageService.getEntitiesByClassName(dashboardsPath, DashboardState.class.getName()); } catch (NotFoundException e) { LOG.error(e.getMessage(), e); // never happening throw new RuntimeException(e); } for (Entity entity : entities) { result.add((DashboardState)entity); } Collections.sort(result, new Comparator<DashboardState>() { public int compare(DashboardState o1, DashboardState o2) { if (MY_DASHBOARD_NAME.equals(o1.getName())) { return -1; } else if (MY_DASHBOARD_NAME.equals(o2.getName())) { return 1; } else { return Collator.getInstance().compare(o1.getName(), o2.getName()); } } }); return result; } @Transactional(readOnly = true) public List<WidgetState> getWidgets(String dashboardPath) { List<WidgetState> result = new ArrayList<WidgetState>(); try { Dashboard dashboard = getDashboardByPath(StorageConstants.NEXT_SERVER_ROOT + dashboardPath); List<Widget> widgets = dashboard.getWidgets(); for (Widget w : widgets) { if (!w.isCollapsed()) { result.add(createWidgetState(dashboardPath, w)); } } Collections.sort(result, new Comparator<WidgetState>() { public int compare(WidgetState o1, WidgetState o2) { return Collator.getInstance().compare(o1.getName(), o2.getName()); } }); } catch (NotFoundException e) { LOG.error(e.getMessage(), e); } return result; } @Transactional(readOnly = true) public List<Link> getDashboardLinks(String user) { return getDashboardLinks(PermissionUtil.getRead(), user); } @Transactional(readOnly = true) public List<Link> getDashboardLinks() { return getDashboardLinks(PermissionUtil.getRead(), ServerUtil.getUsername()); } @Transactional(readOnly = true) public List<Link> getWritableDashboardLinks() { return getDashboardLinks(PermissionUtil.getWrite(), ServerUtil.getUsername()); } private List<Link> getDashboardLinks(int permission, String user) { // TODO improve filtering Entity[] entities; try { entities = storageService.getEntitiesByClassName(StorageConstants.DASHBOARDS_ROOT, DashboardState.class.getName()); } catch (NotFoundException e) { // never happening throw new RuntimeException(e); } List<Entity> tmp = new ArrayList<Entity>(); String dashboardsPath = StorageConstants.DASHBOARDS_ROOT + "/" + user; for (Entity entity : entities) { if (!entity.getPath().startsWith(dashboardsPath)) { tmp.add(entity); } } entities = new Entity[tmp.size()]; entities = tmp.toArray(entities); List<Link> links = new ArrayList<Link>(); for (Entity entity : entities) { try { boolean hasRead = securityService.hasPermissionsById(user, permission, entity.getId()); if (hasRead && !MY_DASHBOARD_NAME.equals(entity.getName())) { Link link = new Link(entity.getName(), entity.getPath()); link.setReference(entity.getId()); links.add(link); } } catch (NotFoundException e) { // never happening throw new RuntimeException(e); } } Collections.sort(links, new Comparator<Link>() { @Override public int compare(Link o1, Link o2) { return Collator.getInstance().compare(o1.getName(), o2.getName()); } }); return links; } @Transactional public String addDashboard(Dashboard dashboard) { try { return storageService.addEntity(createState(dashboard)); } catch (DuplicationException e) { // never happening throw new RuntimeException(e); } } @Transactional public void modifyDashboard(Dashboard dashboard) { try { DashboardState state = (DashboardState)storageService.getEntityById(dashboard.getId()); state.setName(dashboard.getTitle()); state.setColumnCount(dashboard.getColumnCount()); state.setPath(getDashboardPath(dashboard)); // exclude widgetStates children (otherwise their id is modified and existing iframes cannot work) storageService.modifyEntity(state, "widgetStates"); } catch (NotFoundException e) { throw new RuntimeException(e); } } @Transactional public void removeDashboard(String id) throws NotFoundException { // must delete user widget data for (Widget widget : getDashboard(id).getWidgets()) { storageService.clearUserWidgetData(widget.getId()); } storageService.removeEntityById(id); } @Transactional(readOnly = true) public Dashboard getDashboard(String id) throws NotFoundException { DashboardState state = (DashboardState) storageService.getEntityById(id); return load(state); } private Dashboard getDashboardByPath(String path) throws NotFoundException { DashboardState state = (DashboardState) storageService.getEntity(path); return load(state); } @Transactional(readOnly = true) public String getDashboardOwner(String dashboardId) throws NotFoundException { String path = storageService.getEntityPath(dashboardId); String parentPath = StorageUtil.getParentPath(path); String username = StorageUtil.getName(parentPath); return username; } @Transactional(readOnly = true) public Widget getWidgetById(String id) throws NotFoundException { WidgetState state = (WidgetState) storageService.getEntityById(id); return loadWidget(state); } @Transactional(readOnly = true) public DashboardColumn getDashboardColumn(String dashboardId, int column) throws NotFoundException { Dashboard dashboard = getDashboard(dashboardId); return new DashboardColumn(column, dashboard.getWidgets(column)); } @Transactional public String addWidget(String dashboardId, Widget widget) throws NotFoundException { return addWidget(dashboardId, widget, null); } @Transactional public String addWidget(String dashboardId, Widget widget, WidgetLocation location) throws NotFoundException { Dashboard dashboard = getDashboardById(dashboardId); try { return addWidget(dashboard, widget, location); } catch (DuplicationException e) { // TODO e.printStackTrace(); return null; } } @Profile @Transactional public void modifyWidget(String dashboardId, Widget widget) throws NotFoundException { String dashboardPath = storageService.getEntityPath(dashboardId); WidgetState state = createWidgetState(dashboardPath, widget); storageService.modifyEntity(state); } @Transactional public void updateWidgetLocations(String dashboardId, Map<String, WidgetLocation> widgetLocations) throws NotFoundException { Dashboard dashboard = getDashboard(dashboardId); List<Widget> widgets = dashboard.getWidgets(); for (Widget widget : widgets) { String id = widget.getId(); WidgetLocation location = widgetLocations.get(id); if ((widget.getRow() != location.getRow()) || (widget.getColumn() != location.getColumn())) { widget.setColumn(location.getColumn()); widget.setRow(location.getRow()); modifyWidget(dashboardId, widget); } resetDrillDownableCache(widget.getInternalSettings().get(ChartWidget.ENTITY_ID)); } } @Transactional public void removeWidget(String dashboardId, String id) throws NotFoundException { removeWidgetById(dashboardId, id); } private void removeWidgetById(String dashboardId, String id) throws NotFoundException { Dashboard dashboard = getDashboardById(dashboardId); try { removeWidget(dashboard, id); } catch (ReferenceException e) { // TODO e.printStackTrace(); } } @Transactional public void copyWidget(String fromDashboardId, String toDashboardId, String widgetId) throws NotFoundException { Widget widget = getWidgetById(widgetId); Dashboard dashboard = getDashboardById(toDashboardId); widget.setTitle(getUniqueWidgetTitle(dashboard, widget.getTitle(), 0)); try { addWidget(dashboard, widget, null); } catch (DuplicationException e) { // TODO Auto-generated catch block e.printStackTrace(); LOG.error(e.getMessage(),e); } } @Transactional public void moveWidget(String fromDashboardId, String toDashboardId, String widgetId) throws NotFoundException { // try to add it first: if successful we can remove it Widget widget = getWidgetById(widgetId); Dashboard dashboard = getDashboardById(toDashboardId); widget.setTitle(getUniqueWidgetTitle(dashboard, widget.getTitle(), 0)); try { addWidget(dashboard, widget, null); removeWidgetById(fromDashboardId, widgetId); } catch (DuplicationException e) { // TODO Auto-generated catch block e.printStackTrace(); LOG.error(e.getMessage(),e); } } private String getUniqueWidgetTitle(Dashboard dashboard, String title, int count) { String uniqueTitle = title; if (count > 0) { uniqueTitle = title + " " + count; } int columnCount = dashboard.getColumnCount(); for (int i = 0; i < columnCount; i++) { DashboardColumn dashboardColumn = new DashboardColumn(i); dashboardColumn.setWidgets(dashboard.getWidgets(i)); if (dashboardColumn.widgetExists(uniqueTitle)) { uniqueTitle = getUniqueWidgetTitle(dashboard, title, count + 1); } } return uniqueTitle; } public TableData getTableData(String widgetId, Map<String, Object> urlQueryParameters) throws ReportRunnerException, NoDataFoundException, TimeoutException { return getTableData(widgetId, null, urlQueryParameters); } public TableData getTableData(String widgetId, DrillEntityContext drillContext, Map<String, Object> urlQueryParameters) throws ReportRunnerException, NoDataFoundException, TimeoutException { Entity entity = null; Widget widget = null; try { widget = getWidgetById(widgetId); if (widget instanceof TableWidget) { entity = ((TableWidget) widget).getEntity(); } else if (widget instanceof ChartWidget) { entity = ((ChartWidget) widget).getEntity(); } else if (widget instanceof DrillDownWidget) { entity = ((DrillDownWidget) widget).getEntity(); } } catch (NotFoundException e) { throw new RuntimeException(e); } if (entity == null) { throw new ReportRunnerException("Widget class unknown."); } Map<String, Object> parameterValues = new HashMap<String, Object>(); ChartUtil.initParameterSettings(parameterValues, widget.getQueryRuntime(), getUserWidgetParameters(widgetId)); // drill parameters if (drillContext != null) { // put first settings values of the parent root report (some of then may be overridden by drill parameters) // there is a name parameter convention between current drilldown report and parent root report (the default // values of the current drill down report are overidden by the settings values of the parent root report) 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)); } // if a drill down is present and a table follows we have to load that entity try { entity = storageService.getEntityById(drillContext.getCurrentDrillEntityId()); } catch (NotFoundException ex) { LOG.error(ex.getMessage(), ex); } } // parameters from embedded code try { ReportUtil.addUrlQueryParameters(storageService.getSettings(), entity, parameterValues, urlQueryParameters); } catch (Exception e1) { e1.printStackTrace(); LOG.error(e1.getMessage(), e1); } // used by 'save as excel' action if (entity instanceof Chart) { Chart chart = (Chart) entity; Cache cache = null; QueryCacheKey cacheKey = null; boolean cacheable = chart.getExpirationTime() > 0; if (cacheable) { cache = cacheFactory.getCache(chart.getId(), chart.getExpirationTime()); cacheKey = new QueryCacheKey(parameterValues); boolean hitCache = cache.hasElement(cacheKey); if (hitCache) { TableData tableData = (TableData) cache.get(cacheKey); if (LOG.isDebugEnabled()) { LOG.debug("Get tableData for '" + StorageUtil.getPathWithoutRoot(chart.getPath()) + "' from cache"); } return tableData; } } final ChartRunner chartRunner = new ChartRunner(); chartRunner.setParameterValues(parameterValues); ro.nextreports.engine.chart.Chart nextChart = NextUtil.getChart(chart.getContent()); chartRunner.setChart(nextChart); // nextChart chartRunner.setFormat(ChartRunner.TABLE_FORMAT); I18nLanguage language = I18nUtil.getLocaleLanguage(nextChart); if (language != null) { chartRunner.setLanguage(language.getName()); } Connection connection; 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()); chartRunner.setConnection(connection, csv); int timeout = WidgetUtil.getTimeout(this, widget); chartRunner.setQueryTimeout(timeout); FutureTask<TableData> runTask = null; try { runTask = new FutureTask<TableData>(new Callable<TableData>() { public TableData call() throws Exception { chartRunner.run(); return chartRunner.getTableData(); } }); new Thread(runTask).start(); TableData tableData = runTask.get(timeout, TimeUnit.SECONDS); if (cacheable) { if (LOG.isDebugEnabled()) { LOG.debug("Put tableData for '" + StorageUtil.getPathWithoutRoot(chart.getPath()) + "' in cache"); } cache.put(cacheKey, tableData); } return tableData; } catch (Exception e) { // ehcache uses BlockingCache which waits someone to put a value in cache if gets a null value // so this put must be done to unblock the cache if a timeout or ane exception occurs)!! cache.put(cacheKey, null); if (e instanceof TimeoutException) { throw new TimeoutException("Timeout of " + timeout + " seconds ellapsed."); } else { throw new ReportRunnerException(e); } } finally { ConnectionUtil.closeConnection(connection); } } else { Report report = (Report) entity; Cache cache = null; QueryCacheKey cacheKey = null; boolean cacheable = report.getExpirationTime() > 0; if (cacheable) { cache = cacheFactory.getCache(report.getId(), report.getExpirationTime()); cacheKey = new QueryCacheKey(parameterValues); boolean hitCache = cache.hasElement(cacheKey); if (hitCache) { TableData tableData = (TableData) cache.get(cacheKey); if (LOG.isDebugEnabled()) { LOG.debug("Get tableData for '" + StorageUtil.getPathWithoutRoot(report.getPath()) + "' from cache"); } return tableData; } } ro.nextreports.engine.Report nextReport = NextUtil.getNextReport(storageService.getSettings(), (NextContent) report.getContent()); final ReportRunner reportRunner = new ReportRunner(); reportRunner.setParameterValues(parameterValues); reportRunner.setReport(nextReport); reportRunner.setFormat(ReportRunner.TABLE_FORMAT); if (TableWidget.ALLOW_COLUMNS_SORTING) { reportRunner.setTableRawData(true); // formatted data is shown inside TableRendererPanel in any PropertyColumn! } I18nLanguage language = I18nUtil.getLocaleLanguage(nextReport.getLayout()); if (language != null) { reportRunner.setLanguage(language.getName()); } Connection connection; try { connection = ConnectionUtil.createConnection(storageService, report.getDataSource()); } catch (RepositoryException e) { throw new ReportRunnerException("Cannot connect to database", e); } boolean csv = report.getDataSource().getDriver().equals(CSVDialect.DRIVER_CLASS); reportRunner.setConnection(connection, csv); int timeout = WidgetUtil.getTimeout(this, widget); reportRunner.setQueryTimeout(timeout); FutureTask<TableData> runTask = null; try { runTask = new FutureTask<TableData>(new Callable<TableData>() { public TableData call() throws Exception { reportRunner.run(); return reportRunner.getTableData(); } }); new Thread(runTask).start(); TableData tableData = runTask.get(timeout, TimeUnit.SECONDS); if (cacheable) { if (LOG.isDebugEnabled()) { LOG.debug("Put tableData for '" + StorageUtil.getPathWithoutRoot(report.getPath()) + "' in cache"); } cache.put(cacheKey, tableData); } return tableData; } catch (Exception e) { // ehcache uses BlockingCache which waits someone to put a value in cache if gets a null value // so this put must be done to unblock the cache if a timeout or an exception occurs)!! if (cacheable) { cache.put(cacheKey, null); } if (e instanceof TimeoutException) { throw new TimeoutException("Timeout of " + timeout + " seconds ellapsed."); } else if (e.getMessage().contains("NoDataFoundException")) { throw new NoDataFoundException(); } else { throw new ReportRunnerException(e); } } finally { ConnectionUtil.closeConnection(connection); } } } public AlarmData getAlarmData(String widgetId, Map<String, Object> urlQueryParameters) throws ReportRunnerException, NoDataFoundException, TimeoutException { Entity entity = null; Widget widget = null; try { widget = getWidgetById(widgetId); if (widget instanceof AlarmWidget) { entity = ((AlarmWidget) widget).getEntity(); } } catch (NotFoundException e) { throw new RuntimeException(e); } if (entity == null) { throw new ReportRunnerException("Widget class unknown for alarm."); } Report report = (Report) entity; Map<String, Object> parameterValues = new HashMap<String, Object>(); ChartUtil.initParameterSettings(parameterValues, widget.getQueryRuntime(), getUserWidgetParameters(widgetId)); // parameters from embedded code try { ReportUtil.addUrlQueryParameters(storageService.getSettings(), entity, parameterValues, urlQueryParameters); } catch (Exception e1) { e1.printStackTrace(); LOG.error(e1.getMessage(), e1); } Cache cache = null; QueryCacheKey cacheKey = null; boolean cacheable = report.getExpirationTime() > 0; if (cacheable) { cache = cacheFactory.getCache(report.getId(), report.getExpirationTime()); cacheKey = new QueryCacheKey(parameterValues); boolean hitCache = cache.hasElement(cacheKey); if (hitCache) { AlarmData alarmData = (AlarmData) cache.get(cacheKey); if (LOG.isDebugEnabled()) { LOG.debug("Get alarmData for '" + StorageUtil.getPathWithoutRoot(report.getPath()) + "' from cache"); } return alarmData; } } ro.nextreports.engine.Report nextReport = NextUtil.getNextReport(storageService.getSettings(), (NextContent) report.getContent()); final ReportRunner reportRunner = new ReportRunner(); reportRunner.setParameterValues(parameterValues); reportRunner.setReport(nextReport); reportRunner.setFormat(ReportRunner.ALARM_FORMAT); I18nLanguage language = I18nUtil.getLocaleLanguage(nextReport.getLayout()); if (language != null) { reportRunner.setLanguage(language.getName()); } Connection connection; try { connection = ConnectionUtil.createConnection(storageService, report.getDataSource()); } catch (RepositoryException e) { throw new ReportRunnerException("Cannot connect to database", e); } boolean csv = CSVDialect.DRIVER_CLASS.equals(report.getDataSource().getDriver()); reportRunner.setConnection(connection, csv); int timeout = WidgetUtil.getTimeout(this, widget); reportRunner.setQueryTimeout(timeout); FutureTask<AlarmData> runTask = null; try { runTask = new FutureTask<AlarmData>(new Callable<AlarmData>() { public AlarmData call() throws Exception { try { reportRunner.run(); } catch (NoDataFoundException ex) { return new AlarmData(ColorUtil.getHexColor(Color.WHITE), "No Data"); } return reportRunner.getAlarmData(); } }); new Thread(runTask).start(); AlarmData alarmData = runTask.get(timeout, TimeUnit.SECONDS); if (cacheable) { if (LOG.isDebugEnabled()) { LOG.debug("Put alarmData for '" + StorageUtil.getPathWithoutRoot(report.getPath()) + "' in cache"); } cache.put(cacheKey, alarmData); } return alarmData; } catch (Exception e) { // ehcache uses BlockingCache which waits someone to put a value in cache if gets a null value // so this put must be done to unblock the cache if a timeout or an exception occurs)!! if (cacheable) { cache.put(cacheKey, null); } if (e instanceof TimeoutException) { throw new TimeoutException("Timeout of " + timeout + " seconds ellapsed."); } else { throw new ReportRunnerException(e); } } finally { ConnectionUtil.closeConnection(connection); } } public IndicatorData getIndicatorData(String widgetId, Map<String, Object> urlQueryParameters) throws ReportRunnerException, NoDataFoundException, TimeoutException { Entity entity = null; Widget widget = null; try { widget = getWidgetById(widgetId); if (widget instanceof IndicatorWidget) { entity = ((IndicatorWidget) widget).getEntity(); } } catch (NotFoundException e) { throw new RuntimeException(e); } if (entity == null) { throw new ReportRunnerException("Widget class unknown for indicator."); } Report report = (Report) entity; Map<String, Object> parameterValues = new HashMap<String, Object>(); ChartUtil.initParameterSettings(parameterValues, widget.getQueryRuntime(), getUserWidgetParameters(widgetId)); // parameters from embedded code try { ReportUtil.addUrlQueryParameters(storageService.getSettings(), entity, parameterValues, urlQueryParameters); } catch (Exception e1) { e1.printStackTrace(); LOG.error(e1.getMessage(), e1); } Cache cache = null; QueryCacheKey cacheKey = null; boolean cacheable = report.getExpirationTime() > 0; if (cacheable) { cache = cacheFactory.getCache(report.getId(), report.getExpirationTime()); cacheKey = new QueryCacheKey(parameterValues); boolean hitCache = cache.hasElement(cacheKey); if (hitCache) { IndicatorData indicatorData = (IndicatorData) cache.get(cacheKey); if (LOG.isDebugEnabled()) { LOG.debug("Get indicatorData for '" + StorageUtil.getPathWithoutRoot(report.getPath()) + "' from cache"); } return indicatorData; } } ro.nextreports.engine.Report nextReport = NextUtil.getNextReport(storageService.getSettings(), (NextContent) report.getContent()); final ReportRunner reportRunner = new ReportRunner(); reportRunner.setParameterValues(parameterValues); reportRunner.setReport(nextReport); reportRunner.setFormat(ReportRunner.INDICATOR_FORMAT); I18nLanguage language = I18nUtil.getLocaleLanguage(nextReport.getLayout()); if (language != null) { reportRunner.setLanguage(language.getName()); } Connection connection; try { connection = ConnectionUtil.createConnection(storageService, report.getDataSource()); } catch (RepositoryException e) { throw new ReportRunnerException("Cannot connect to database", e); } boolean csv = CSVDialect.DRIVER_CLASS.equals(report.getDataSource().getDriver()); reportRunner.setConnection(connection, csv); int timeout = WidgetUtil.getTimeout(this, widget); reportRunner.setQueryTimeout(timeout); FutureTask<IndicatorData> runTask = null; try { runTask = new FutureTask<IndicatorData>(new Callable<IndicatorData>() { public IndicatorData call() throws Exception { try { reportRunner.run(); } catch (NoDataFoundException ex) { IndicatorData data = new IndicatorData(); data.setTitle("No Data"); return data; } return reportRunner.getIndicatorData(); } }); new Thread(runTask).start(); IndicatorData indicatorData = runTask.get(timeout, TimeUnit.SECONDS); if (cacheable) { if (LOG.isDebugEnabled()) { LOG.debug("Put indicatorData for '" + StorageUtil.getPathWithoutRoot(report.getPath()) + "' in cache"); } cache.put(cacheKey, indicatorData); } return indicatorData; } catch (Exception e) { e.printStackTrace(); // ehcache uses BlockingCache which waits someone to put a value in cache if gets a null value // so this put must be done to unblock the cache if a timeout or an exception occurs)!! if (cacheable) { cache.put(cacheKey, null); } if (e instanceof TimeoutException) { throw new TimeoutException("Timeout of " + timeout + " seconds ellapsed."); } else { throw new ReportRunnerException(e); } } finally { ConnectionUtil.closeConnection(connection); } } public DisplayData getDisplayData(String widgetId, Map<String, Object> urlQueryParameters) throws ReportRunnerException, NoDataFoundException, TimeoutException { Entity entity = null; Widget widget = null; try { widget = getWidgetById(widgetId); if (widget instanceof DisplayWidget) { entity = ((DisplayWidget) widget).getEntity(); } } catch (NotFoundException e) { throw new RuntimeException(e); } if (entity == null) { throw new ReportRunnerException("Widget class unknown for display."); } Report report = (Report) entity; Map<String, Object> parameterValues = new HashMap<String, Object>(); ChartUtil.initParameterSettings(parameterValues, widget.getQueryRuntime(), getUserWidgetParameters(widgetId)); // parameters from embedded code try { ReportUtil.addUrlQueryParameters(storageService.getSettings(), entity, parameterValues, urlQueryParameters); } catch (Exception e1) { e1.printStackTrace(); LOG.error(e1.getMessage(), e1); } Cache cache = null; QueryCacheKey cacheKey = null; boolean cacheable = report.getExpirationTime() > 0; if (cacheable) { cache = cacheFactory.getCache(report.getId(), report.getExpirationTime()); cacheKey = new QueryCacheKey(parameterValues); boolean hitCache = cache.hasElement(cacheKey); if (hitCache) { DisplayData displayData = (DisplayData) cache.get(cacheKey); if (LOG.isDebugEnabled()) { LOG.debug("Get displayData for '" + StorageUtil.getPathWithoutRoot(report.getPath()) + "' from cache"); } return displayData; } } ro.nextreports.engine.Report nextReport = NextUtil.getNextReport(storageService.getSettings(), (NextContent) report.getContent()); final ReportRunner reportRunner = new ReportRunner(); reportRunner.setParameterValues(parameterValues); reportRunner.setReport(nextReport); reportRunner.setFormat(ReportRunner.DISPLAY_FORMAT); I18nLanguage language = I18nUtil.getLocaleLanguage(nextReport.getLayout()); if (language != null) { reportRunner.setLanguage(language.getName()); } Connection connection; try { connection = ConnectionUtil.createConnection(storageService, report.getDataSource()); } catch (RepositoryException e) { throw new ReportRunnerException("Cannot connect to database", e); } boolean csv = CSVDialect.DRIVER_CLASS.equals(report.getDataSource().getDriver()); reportRunner.setConnection(connection, csv); int timeout = WidgetUtil.getTimeout(this, widget); reportRunner.setQueryTimeout(timeout); FutureTask<DisplayData> runTask = null; try { runTask = new FutureTask<DisplayData>(new Callable<DisplayData>() { public DisplayData call() throws Exception { try { reportRunner.run(); } catch (NoDataFoundException ex) { DisplayData data = new DisplayData(); data.setTitle("No Data"); return data; } return reportRunner.getDisplayData(); } }); new Thread(runTask).start(); DisplayData displayData = runTask.get(timeout, TimeUnit.SECONDS); if (cacheable) { if (LOG.isDebugEnabled()) { LOG.debug("Put displayData for '" + StorageUtil.getPathWithoutRoot(report.getPath()) + "' in cache"); } cache.put(cacheKey, displayData); } return displayData; } catch (Exception e) { e.printStackTrace(); // ehcache uses BlockingCache which waits someone to put a value in cache if gets a null value // so this put must be done to unblock the cache if a timeout or an exception occurs)!! if (cacheable) { cache.put(cacheKey, null); } if (e instanceof TimeoutException) { throw new TimeoutException("Timeout of " + timeout + " seconds ellapsed."); } else if (e instanceof NoDataFoundException) { throw (NoDataFoundException)e; } else { throw new ReportRunnerException(e); } } finally { ConnectionUtil.closeConnection(connection); } } public void resetCache(String entityId) { cacheFactory.resetCache(entityId); } public void resetCache(List<String> entityIds) { for (String entityId : entityIds) { cacheFactory.resetCache(entityId); } } public DashboardState getDashboardState(WidgetState widgetState) { Entity[] entities; try { // for embedded widgets in external html's we do not have any authorization object in session entities = storageService.getEntitiesByClassNameWithoutSecurity(StorageConstants.DASHBOARDS_ROOT, DashboardState.class.getName()); for (Entity entity : entities) { DashboardState ds = (DashboardState)entity; if (widgetState.getPath().contains(ds.getPath())) { // we may have dashboards with same name + suffix (ex: Test, Test1) // we have to test that following character in widgetState path is / String followingPath = widgetState.getPath().substring(ds.getPath().length()); if (followingPath.startsWith("/")) { return ds; } } } return null; } catch (NotFoundException e) { // never happening throw new RuntimeException(e); } } public int getWidgetColumn(String widgetId) { try { WidgetState state = (WidgetState) storageService.getEntityById(widgetId); return state.getColumn(); } catch (NotFoundException e) { return -1; } } // just a single widget in a dashboard with a single column public boolean isSingleWidget(String widgetId) { try { WidgetState state = (WidgetState) storageService.getEntityById(widgetId); DashboardState dState = getDashboardState(state); if (dState.getColumnCount() > 1) { return false; } if (dState.getWidgetStates().size() > 1) { return false; } return true; } catch (NotFoundException e) { return false; } } public void removeUserDashboards(String userName) { String dashboardsPath = StorageConstants.DASHBOARDS_ROOT + "/" + userName; try { storageService.removeEntity(dashboardsPath); } catch (ReferenceException e) { LOG.error(e.getMessage(), e); } } private String getUsername() { return SecurityContextHolder.getContext().getAuthentication().getName(); } private String getMyDashboardsPath() { return StorageConstants.DASHBOARDS_ROOT + "/" + getUsername(); } private List<Dashboard> getDashboards(Entity[] entities) { int n = entities.length; DashboardState[] states = new DashboardState[n]; System.arraycopy(entities, 0, states, 0, n); List<Dashboard> dashboards = new ArrayList<Dashboard>(); for (DashboardState state : states) { Dashboard dashboard = load(state); dashboards.add(dashboard); } Collections.sort(dashboards, new Comparator<Dashboard>() { public int compare(Dashboard o1, Dashboard o2) { if (MY_DASHBOARD_NAME.equals(o1.getTitle())) { return -1; } else if (MY_DASHBOARD_NAME.equals(o2.getTitle())) { return 1; } else { return Collator.getInstance().compare(o1.getTitle(), o2.getTitle()); } } }); return dashboards; } private Dashboard load(DashboardState state) { Dashboard dashboard = new DefaultDashboard(state.getId(), state.getName(), state.getColumnCount()); List<Widget> widgets = loadWidgets(state.getWidgetStates()); for (Widget widget : widgets) { dashboard.addWidget(widget); } return dashboard; } private List<Widget> loadWidgets(List<WidgetState> states) { List<Widget> widgets = new ArrayList<Widget>(); for (WidgetState state : states) { Widget widget = loadWidget(state); widgets.add(widget); } return widgets; } private Widget loadWidget(WidgetState state) { WidgetDescriptor widgetDescriptor = widgetRegistry.getWidgetDescriptor(state.getWidgetClassName()); Widget widget = widgetFactory.createWidget(widgetDescriptor); widget.setId(state.getId()); widget.setTitle(state.getName()); widget.setSettings(state.getSettings()); widget.setInternalSettings(state.getInternalSettings()); widget.setQueryRuntime(state.getQueryRuntime()); widget.setColumn(state.getColumn()); widget.setRow(state.getRow()); widget.init(); // collapsed is a property in internalSettings and I must call init after setInternalSettings if (widget instanceof EntityWidget) { String entityId = widget.getInternalSettings().get(EntityWidget.ENTITY_ID); try { ((EntityWidget) widget).setEntity(storageService.getEntityById(entityId)); } catch (NotFoundException e) { e.printStackTrace(); } } return widget; } private DashboardState createState(Dashboard dashboard) { String name = dashboard.getTitle(); String path = getDashboardPath(dashboard); DashboardState state = new DashboardState(name, path); state.setId(dashboard.getId()); state.setColumnCount(dashboard.getColumnCount()); List<WidgetState> widgetStates = new ArrayList<WidgetState>(); List<Widget> widgets = dashboard.getWidgets(); for (Widget widget : widgets) { WidgetState columnState = createWidgetState(path, widget); widgetStates.add(columnState); } state.setWidgetStates(widgetStates); return state; } private WidgetState createWidgetState(String dashboardPath, Widget widget) { String name = widget.getTitle(); String path = getWidgetPath(dashboardPath, name); WidgetState widgetState = new WidgetState(name, path); widgetState.setId(widget.getId()); widgetState.setWidgetClassName(widget.getClass().getName()); widgetState.setSettings(widget.getSettings()); widgetState.setInternalSettings(widget.getInternalSettings()); widgetState.setQueryRuntime(widget.getQueryRuntime()); widgetState.setColumn(widget.getColumn()); widgetState.setRow(widget.getRow()); return widgetState; } private Dashboard getDashboardById(String id) throws NotFoundException { DashboardState state = (DashboardState) storageService.getEntityById(id); return load(state); } private String addWidget(Dashboard dashboard, Widget widget, WidgetLocation location) throws DuplicationException { String dashboardPath = getDashboardPath(dashboard); int row = (location == null) ? 0 : location.getRow(); int column = (location == null) ? 0 : location.getColumn(); // update row indexes List<Widget> widgets = dashboard.getWidgets(column); for (Widget w : widgets) { if (w.getRow() >= row) { w.setRow(w.getRow() + 1); // update widget state in storage WidgetState state = createWidgetState(dashboardPath, w); storageService.modifyEntity(state); resetDrillDownableCache(w.getInternalSettings().get(ChartWidget.ENTITY_ID)); } } // add widget to storage widget.setRow(row); widget.setColumn(column); WidgetState state = createWidgetState(dashboardPath, widget); return storageService.addEntity(state); } private void removeWidget(Dashboard dashboard, String id) throws NotFoundException, ReferenceException { String dashboardPath = getDashboardPath(dashboard); Widget widget = getWidgetById(id); WidgetLocation location = DashboardUtil.getWidgetLocation(dashboard, widget); int row = location.getRow(); int column = location.getColumn(); // update row indexes List<Widget> widgets = dashboard.getWidgets(column); for (Widget w : widgets) { if (w.getRow() > row) { w.setRow(w.getRow() - 1); // update widget state in storage WidgetState state = createWidgetState(dashboardPath, w); storageService.modifyEntity(state); resetDrillDownableCache(w.getInternalSettings().get(ChartWidget.ENTITY_ID)); } } // remove widget from storage storageService.removeEntityById(id); // if user widget data is saved for this widget, we have to delete it storageService.clearUserWidgetData(id); } private String getDashboardPath(Dashboard dashboard) { String id = dashboard.getId(); if (id == null) { return getMyDashboardsPath() + "/" + dashboard.getTitle(); } try { return storageService.getEntityPath(id); } catch (NotFoundException e) { // never happening throw new RuntimeException(e); } } private String getWidgetPath(String dashboardPath, String title) { return dashboardPath + "/widgetStates/" + title; } // drill downable chart widgets contains also the on-click ajax script with position // we must reset cache otherwise drill down will not work // for drill down reports there is no need to reset cache when we move the widget private void resetDrillDownableCache(String entityId) { try { // has no chart id in settings if (entityId == null) { return; } Entity entity = storageService.getEntityById(entityId); if (entity instanceof Chart) { if (((Chart)entity).isDrillDownable()) { resetCache(entityId); } } } catch (NotFoundException ex) { LOG.error("Reset cache - not found", ex); } } @Transactional public void setDefaultDashboard(String dashboardId) { String path = getMyDashboardsPath(); storageService.setDefaultProperty(path, dashboardId); } @Transactional(readOnly = true) public String getDefaultDashboardId() throws NotFoundException { String path = getMyDashboardsPath(); return storageService.getDefaultProperty(path); } @Transactional(readOnly = true) public UserWidgetParameters getUserWidgetParameters(String widgetId) { if (widgetId == null) { return null; } UserWidgetParameters wp = null; try { String dashboardId = storageService.getDashboardId(widgetId); String user = ServerUtil.getUsername(); if (ServerUtil.UNKNOWN_USER.equals(user)) { // if we do not have user inside session (like for an iframe without authentication) return wp; } String owner = getDashboardOwner(dashboardId); boolean isDashboardLink = !owner.equals(user); String parentPath = WidgetUtil.getUserWidgetParametersPath(user); String path = parentPath + "/" + widgetId; boolean hasWrite = securityService.hasPermissionsById(user, PermissionUtil.getWrite(), dashboardId); if (isDashboardLink && !hasWrite) { if (storageService.entityExists(path)) { wp = (UserWidgetParameters)storageService.getEntity(path); } } return wp; } catch (NotFoundException ex) { throw new RuntimeException(ex); } } }