/******************************************************************************* * * Copyright 2010 Alexandru Craciun, and individual contributors as indicated * by the @authors tag. * * This 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. * * This software 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 this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. ******************************************************************************/ package org.netxilia.spi.impl.storage.db; import java.sql.SQLException; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javax.sql.DataSource; import org.netxilia.api.exception.NotFoundException; import org.netxilia.api.exception.StorageException; import org.netxilia.api.impl.NetxiliaSystemImpl; import org.netxilia.api.impl.model.Workbook; import org.netxilia.api.model.IWorkbook; import org.netxilia.api.model.SheetData; import org.netxilia.api.model.SheetFullName; import org.netxilia.api.model.SheetType; import org.netxilia.api.model.WorkbookId; import org.netxilia.api.storage.DataSourceConfiguration; import org.netxilia.api.storage.DataSourceConfigurationId; import org.netxilia.api.storage.IDataSourceConfigurationListener; import org.netxilia.api.storage.IDataSourceConfigurationService; import org.netxilia.spi.impl.storage.db.ddl.ExtendedDataSource; import org.netxilia.spi.impl.storage.db.ddl.IDDLUtilsFactory; import org.netxilia.spi.impl.storage.db.sql.IConnectionWrapperFactory; import org.netxilia.spi.storage.ISheetStorageService; import org.netxilia.spi.storage.IWorkbookStorageService; import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; /** * This service stores workbooks and sheets in a database. See storage schema on the website. The storage is meant to * support multiple access on different workbooks. * * @author <a href='mailto:ax.craciun@gmail.com'>Alexandru Craciun</a> * */ public class DbWorkbookStorageServiceImpl implements IWorkbookStorageService, ApplicationContextAware, InitializingBean, IDataSourceConfigurationListener { // private final static Logger log = Logger.getLogger(DbWorkbookStorageServiceImpl.class); // configurable params @Autowired private IDataSourceConfigurationService dataSourceConfigurationService; @Autowired private IDDLUtilsFactory ddlUtilsFactory; @Autowired private IConnectionWrapperFactory connectionWrapperFactory; @Autowired private WorkbooksMapper workbooksMapper; @Autowired private SheetsMapper sheetsMapper; @Autowired private RowsMapper rowsMapper; @Autowired private ColumnsMapper columnsMapper; @Autowired private CellsMapper cellsMapper; @Autowired private SparseMatrixMapper matrixMapper; private ApplicationContext context; private ConcurrentMap<SheetFullName, DbSheetStorageServiceImpl> sheetCache = new ConcurrentHashMap<SheetFullName, DbSheetStorageServiceImpl>(); private ConcurrentMap<WorkbookId, ExtendedDataSource> dataSources = new ConcurrentHashMap<WorkbookId, ExtendedDataSource>(); private ConcurrentMap<DataSourceConfigurationId, ExtendedDataSource> configurations = new ConcurrentHashMap<DataSourceConfigurationId, ExtendedDataSource>(); // ADD row/data cache with EHCACHE public IDDLUtilsFactory getDdlUtilsFactory() { return ddlUtilsFactory; } public void setDdlUtilsFactory(IDDLUtilsFactory ddlUtilsFactory) { this.ddlUtilsFactory = ddlUtilsFactory; } public IConnectionWrapperFactory getConnectionWrapperFactory() { return connectionWrapperFactory; } public void setConnectionWrapperFactory(IConnectionWrapperFactory connectionWrapperFactory) { this.connectionWrapperFactory = connectionWrapperFactory; } public WorkbooksMapper getWorkbooksMapper() { return workbooksMapper; } public void setWorkbooksMapper(WorkbooksMapper workbooksMapper) { this.workbooksMapper = workbooksMapper; } public SheetsMapper getSheetsMapper() { return sheetsMapper; } public void setSheetsMapper(SheetsMapper sheetsMapper) { this.sheetsMapper = sheetsMapper; } public CellsMapper getCellsMapper() { return cellsMapper; } public void setCellsMapper(CellsMapper cellsMapper) { this.cellsMapper = cellsMapper; } public IDataSourceConfigurationService getDataSourceConfigurationService() { return dataSourceConfigurationService; } public void setDataSourceConfigurationService(IDataSourceConfigurationService dataSourceConfigurationService) { this.dataSourceConfigurationService = dataSourceConfigurationService; } public RowsMapper getRowsMapper() { return rowsMapper; } public void setRowsMapper(RowsMapper rowsMapper) { this.rowsMapper = rowsMapper; } public ColumnsMapper getColumnsMapper() { return columnsMapper; } public void setColumnsMapper(ColumnsMapper columnsMapper) { this.columnsMapper = columnsMapper; } public SparseMatrixMapper getMatrixMapper() { return matrixMapper; } public void setMatrixMapper(SparseMatrixMapper matrixMapper) { this.matrixMapper = matrixMapper; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; } private ExtendedDataSource getDataSource(DataSourceConfiguration datasourceConfiguration) throws StorageException, NotFoundException, SQLException { ExtendedDataSource extDataSource = configurations.get(datasourceConfiguration.getId()); if (extDataSource == null) { DataSource dataSource = dataSourceConfigurationService.buildDataSource(datasourceConfiguration); ExtendedDataSource newExtDataSource = new ExtendedDataSource(ddlUtilsFactory.newInstance(dataSource)); extDataSource = configurations.putIfAbsent(datasourceConfiguration.getId(), newExtDataSource); if (extDataSource == null) { extDataSource = newExtDataSource; } } return extDataSource; } private ExtendedDataSource getDataSource(WorkbookId workbookKey) throws StorageException, NotFoundException, SQLException { ExtendedDataSource extDataSource = dataSources.get(workbookKey); if (extDataSource == null) { DataSourceConfiguration datasourceConfiguration = dataSourceConfigurationService .loadByWorkbook(workbookKey); if (datasourceConfiguration == null) { throw new StorageException("No datasource was defined for workbook [" + workbookKey + "]"); } ExtendedDataSource newExtDataSource = getDataSource(datasourceConfiguration); extDataSource = dataSources.putIfAbsent(workbookKey, newExtDataSource); if (extDataSource == null) { extDataSource = newExtDataSource; } } return extDataSource; } /** * create a new session to the corresponding database. The session must be closed when done with! * * @param workbookKey * @return * @throws StorageException * @throws NotFoundException */ public WorkbookDbSession newDbSession(WorkbookId workbookKey) throws StorageException, NotFoundException { WorkbookDbSession wkdata; try { ExtendedDataSource extDataSource = getDataSource(workbookKey); wkdata = new WorkbookDbSession(workbookKey, extDataSource.getDdl(), extDataSource.getSchema(), connectionWrapperFactory.newInstance(extDataSource.getDataSource())); } catch (SQLException e) { throw new StorageException(e); } return wkdata; } @Override public void deleteWorkbook(WorkbookId workbookId) throws StorageException, NotFoundException { WorkbookDbSession data = newDbSession(workbookId); try { // TODO find a better way to give access to services to Workbooks and Sheets NetxiliaSystemImpl workbookProcessor = context.getBean(NetxiliaSystemImpl.class); workbooksMapper.delete(workbookProcessor, data, workbookId); dataSourceConfigurationService.deleteConfigurationForWorkbook(workbookId); // remove all storage info for workbook } catch (SQLException e) { throw new StorageException(e); } finally { data.close(); } } @Override public IWorkbook add(DataSourceConfigurationId dataSourceConfigId, WorkbookId workbookId) throws StorageException, NotFoundException { // TODO Auto-generated method stub dataSourceConfigurationService.setConfigurationForWorkbook(workbookId, dataSourceConfigId); NetxiliaSystemImpl workbookProcessor = context.getBean(NetxiliaSystemImpl.class); return Workbook.newInstance(workbookProcessor, workbookId); } @Override public List<SheetData> loadSheets(WorkbookId workbookId) throws StorageException, NotFoundException { WorkbookDbSession data = newDbSession(workbookId); try { return sheetsMapper.loadSheets(data); } finally { data.close(); } } @Override public void afterPropertiesSet() throws Exception { // workbooksMapper.setStorageService(this); sheetsMapper.setStorageService(this); rowsMapper.setStorageService(this); columnsMapper.setStorageService(this); cellsMapper.setStorageService(this); matrixMapper.setStorageService(this); dataSourceConfigurationService.addDataSourceConfigurationListener(this); } protected DbSheetStorageServiceImpl newSheetStorage(SheetFullName sheetName, SheetsMapper sheetsMapper, RowsMapper rowsMapper, ColumnsMapper columnsMapper, CellsMapper cellsMapper, SparseMatrixMapper matrixMapper) { return new DbSheetStorageServiceImpl(this, sheetName, sheetsMapper, rowsMapper, columnsMapper, cellsMapper, matrixMapper); } @Override public ISheetStorageService getSheetStorage(SheetFullName sheetName, SheetType sheetType) throws StorageException, NotFoundException { DbSheetStorageServiceImpl entry = sheetCache.get(sheetName); if (entry == null) { DbSheetStorageServiceImpl newEntry = newSheetStorage(sheetName, sheetsMapper, rowsMapper, columnsMapper, cellsMapper, matrixMapper); SheetDbSession session = newEntry.newDbSession(); try { newEntry.getOrCreateSheetStorage(session, sheetType); } finally { session.close(); } entry = sheetCache.putIfAbsent(sheetName, newEntry); if (entry == null) { entry = newEntry; } } return entry; } @Override public void deleteSheet(SheetFullName sheetName, SheetType sheetType) throws StorageException, NotFoundException { ISheetStorageService sheetStorage = getSheetStorage(sheetName, sheetType); sheetStorage.deleteSheet(); sheetCache.remove(sheetName); } @Override public void onModifyConfiguration(DataSourceConfigurationId id) { onDeleteConfiguration(id); } @Override public void onDeleteConfiguration(DataSourceConfigurationId id) { ExtendedDataSource ds = configurations.remove(id); if (ds != null) { // TODO do we need here synchronization for (Map.Entry<WorkbookId, ExtendedDataSource> entry : dataSources.entrySet()) { if (entry.getValue() == ds) { dataSources.remove(entry.getKey()); } } } } @Override public void onDeleteConfigurationForWorkbook(WorkbookId workbookKey) { dataSources.remove(workbookKey); } }