org.netxilia.api.impl.storage.DataSourceConfigurationServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.netxilia.api.impl.storage.DataSourceConfigurationServiceImpl.java

Source

/*******************************************************************************
 * 
 * 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.api.impl.storage;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.sql.DataSource;

import org.apache.commons.dbcp.ConnectionFactory;
import org.apache.commons.dbcp.DataSourceConnectionFactory;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDataSource;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.RegexFileFilter;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.apache.log4j.Logger;
import org.netxilia.api.exception.NetxiliaResourceException;
import org.netxilia.api.exception.NotFoundException;
import org.netxilia.api.exception.StorageException;
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.api.utils.Pair;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;

public class DataSourceConfigurationServiceImpl
        implements IDataSourceConfigurationService, ApplicationContextAware {
    private final static Logger log = Logger.getLogger(DataSourceConfigurationServiceImpl.class);

    private static final Pattern REGEX_FILE = Pattern.compile("datasource-(\\d+)\\.properties");
    private static final String PROP_NAME = "jdbc.name";
    private static final String PROP_DESCRIPTION = "jdbc.description";

    private static final String PROP_DRIVER = "jdbc.driver";
    private static final String PROP_URL = "jdbc.url";
    private static final String PROP_USERNAME = "jdbc.username";
    private static final String PROP_PASSWORD = "jdbc.password";

    private static final String NETXILIA_HOME_VAR = "${netxilia.home}";

    private String workbooksFile = "workbooks.properties";
    private String path;

    private ApplicationContext applicationContext;

    private Map<DataSourceConfigurationId, Pair<GenericObjectPool, DataSource>> pools = new HashMap<DataSourceConfigurationId, Pair<GenericObjectPool, DataSource>>();

    private List<IDataSourceConfigurationListener> listeners = new ArrayList<IDataSourceConfigurationListener>();

    private boolean initialized = false;

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public String getWorkbooksFile() {
        return workbooksFile;
    }

    public void setWorkbooksFile(String workbooksFile) {
        this.workbooksFile = workbooksFile;
    }

    private boolean createAppDir() {
        File file = new File(path);
        if (!file.exists()) {
            boolean ok = file.mkdirs();
            if (!ok) {
                log.warn("The datasource storage path[" + path + "] could not be created");
            }
            return ok;
        }
        return true;
    }

    private File fileForId(DataSourceConfigurationId id) {
        return new File(getPath(), "datasource-" + id.getId() + ".properties");
    }

    @Override
    public synchronized void delete(DataSourceConfigurationId id) throws StorageException, NotFoundException {
        init();
        File dsFile = fileForId(id);
        if (dsFile.exists()) {
            notifyDeleteConfiguration(id);
            dsFile.delete();
        } else {
            throw new NotFoundException(dsFile + " does not exists");
        }

    }

    private boolean copyToFile(String folder, String fileName) throws StorageException {
        InputStream in = Thread.currentThread().getContextClassLoader()
                .getResourceAsStream(folder + "/" + fileName);
        if (in == null) {
            return false;
        }

        FileOutputStream out = null;
        try {
            out = new FileOutputStream(new File(path, fileName));
            IOUtils.copy(in, out);
            in.close();
            out.flush();
        } catch (Exception e) {
            throw new StorageException(e);
        } finally {
            if (in != null) {
                IOUtils.closeQuietly(in);
            }
            if (out != null) {
                IOUtils.closeQuietly(out);
            }
        }
        return true;
    }

    private void init() throws StorageException {
        if (initialized) {
            return;
        }
        if (!createAppDir()) {
            // a read-only mode
            initialized = true;
            return;
        }
        File dir = new File(path);
        File[] cfgFiles = dir.listFiles((FilenameFilter) new RegexFileFilter(REGEX_FILE));
        if (cfgFiles == null || cfgFiles.length == 0) {

            // TODO use a scanner to find both in jar and in extracted version
            int i = 0;
            while (true) {
                String fileName = "datasource-" + i + ".properties";

                if (!copyToFile("datasources", fileName)) {
                    break;
                }
                ++i;

            }
            if (!copyToFile("datasources", "workbooks.properties")) {

            }
        }

        initialized = true;

    }

    @Override
    public synchronized List<DataSourceConfiguration> findAll() throws StorageException {
        init();
        File dir = new File(path);
        File[] cfgFiles = dir.listFiles((FilenameFilter) new RegexFileFilter(REGEX_FILE));
        if (cfgFiles == null || cfgFiles.length == 0) {
            return Collections.emptyList();
        }
        List<DataSourceConfiguration> configs = new ArrayList<DataSourceConfiguration>(cfgFiles.length);
        for (File cfgFile : cfgFiles) {
            configs.add(readConfig(cfgFile));
        }
        return configs;
    }

    private DataSourceConfiguration readConfig(File cfgFile) throws StorageException {
        Properties props = new Properties();
        FileReader reader = null;

        try {
            reader = new FileReader(cfgFile);
            props.load(reader);
            Matcher m = REGEX_FILE.matcher(cfgFile.getName());
            if (!m.find()) {
                throw new StorageException("Wrong file name:" + cfgFile);
            }
            DataSourceConfigurationId id = new DataSourceConfigurationId(Integer.valueOf(m.group(1)));

            DataSourceConfiguration cfg = new DataSourceConfiguration(id, props.getProperty(PROP_NAME),
                    props.getProperty(PROP_DESCRIPTION), props.getProperty(PROP_DRIVER),
                    props.getProperty(PROP_URL), props.getProperty(PROP_USERNAME),
                    props.getProperty(PROP_PASSWORD));
            return cfg;
        } catch (IOException e) {
            throw new StorageException(e);
        } finally {
            if (reader != null) {
                IOUtils.closeQuietly(reader);
            }
        }

    }

    @Override
    public synchronized DataSourceConfiguration load(DataSourceConfigurationId id)
            throws StorageException, NotFoundException {
        init();
        File cfgFile = fileForId(id);
        if (cfgFile == null || !cfgFile.exists()) {
            throw new NotFoundException(id + " not found");
        }
        return readConfig(cfgFile);
    }

    @Override
    public synchronized DataSourceConfiguration save(DataSourceConfiguration cfg) throws StorageException {
        init();
        DataSourceConfiguration returnCfg = cfg;

        if (cfg.getId() == null) {
            DataSourceConfigurationId newId = newId();
            returnCfg = new DataSourceConfiguration(newId, cfg.getName(), cfg.getDescription(),
                    cfg.getDriverClassName(), cfg.getUrl(), cfg.getUsername(), cfg.getPassword());
        } else {
            notifyModifyConfiguration(cfg.getId());
            Pair<GenericObjectPool, DataSource> poolInfo = pools.get(cfg.getId());
            if (poolInfo != null) {
                try {
                    poolInfo.getFirst().close();
                } catch (Exception e) {
                    log.error("Cannot close datasource pool:" + e, e);
                }
            }
            pools.remove(cfg.getId());
            buildDataSource(cfg);
        }
        Properties props = new Properties();
        FileWriter writer = null;

        try {
            props.setProperty(PROP_NAME, returnCfg.getName());
            props.setProperty(PROP_DESCRIPTION, returnCfg.getDescription());
            props.setProperty(PROP_DRIVER, returnCfg.getDriverClassName());
            props.setProperty(PROP_URL, returnCfg.getUrl());
            props.setProperty(PROP_USERNAME, returnCfg.getUsername());
            props.setProperty(PROP_PASSWORD, returnCfg.getPassword());

            writer = new FileWriter(fileForId(returnCfg.getId()));
            props.store(writer, "Saved by Netxilia");

        } catch (IOException e) {
            throw new StorageException(e);
        } finally {
            if (writer != null) {
                try {
                    writer.flush();
                } catch (Exception e2) {
                    throw new StorageException(e2);
                }
                IOUtils.closeQuietly(writer);
            }
        }

        return returnCfg;

    }

    private DataSourceConfigurationId newId() throws StorageException {
        List<DataSourceConfiguration> cfgs = findAll();
        if (cfgs == null) {
            return new DataSourceConfigurationId(0);
        }
        int max = -1;
        for (DataSourceConfiguration cfg : cfgs) {
            max = Math.max(max, cfg.getId().getId());
        }

        return new DataSourceConfigurationId(max + 1);
    }

    private Properties loadWorkbookToDataSourceFile() throws StorageException {
        Properties props = new Properties();
        FileReader reader = null;

        try {
            File propFile = new File(getPath(), workbooksFile);
            if (!propFile.exists()) {
                return props;
            }
            reader = new FileReader(propFile);
            props.load(reader);

            return props;
        } catch (IOException e) {
            throw new StorageException(e);
        } finally {
            if (reader != null) {
                IOUtils.closeQuietly(reader);
            }
        }
    }

    private void saveWorkbookToDataSourceFile(Properties props) throws StorageException {
        FileWriter writer = null;

        try {
            File propFile = new File(getPath(), workbooksFile);
            writer = new FileWriter(propFile);
            props.store(writer, "Saved by Netxilia");

        } catch (IOException e) {
            throw new StorageException(e);
        } finally {
            if (writer != null) {
                try {
                    writer.flush();
                } catch (Exception e2) {
                    throw new StorageException(e2);
                }
                IOUtils.closeQuietly(writer);
            }
        }
    }

    @Override
    public synchronized void deleteConfigurationForWorkbook(WorkbookId workbookKey)
            throws StorageException, NotFoundException {
        init();
        Properties props = loadWorkbookToDataSourceFile();
        if (props.containsKey(workbookKey.getKey())) {
            notifyDeleteConfigurationForWorkbook(workbookKey);
            props.remove(workbookKey.getKey());
            saveWorkbookToDataSourceFile(props);
        }

    }

    @Override
    public synchronized DataSourceConfiguration loadByWorkbook(WorkbookId workbookKey)
            throws StorageException, NotFoundException {
        init();
        Properties props = loadWorkbookToDataSourceFile();
        String configId = props.getProperty(workbookKey.getKey());
        if (configId == null) {
            throw new NotFoundException("Workbook:" + workbookKey + " is not configured");
        }
        return load(new DataSourceConfigurationId(configId));
    }

    @Override
    public synchronized void setConfigurationForWorkbook(WorkbookId workbookKey, DataSourceConfigurationId id)
            throws StorageException, NotFoundException {
        init();
        Properties props = loadWorkbookToDataSourceFile();
        props.setProperty(workbookKey.getKey(), id.toString());
        saveWorkbookToDataSourceFile(props);
    }

    @Override
    public synchronized void test(DataSourceConfigurationId id)
            throws StorageException, NotFoundException, SQLException {
        init();
        DataSourceConfiguration cfg = load(id);
        DataSource dataSource = buildSimpleDataSource(cfg);
        Connection connection = null;
        try {
            connection = dataSource.getConnection();

        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    // quiet
                }
            }
        }
    }

    @SuppressWarnings("unchecked")
    public DataSource buildSimpleDataSource(DataSourceConfiguration cfg) {
        SimpleDriverDataSource dataSource = new SimpleDriverDataSource();

        Class<? extends Driver> driverClass;
        try {
            driverClass = (Class<? extends Driver>) Class.forName(cfg.getDriverClassName());
        } catch (ClassNotFoundException e) {
            throw new NetxiliaResourceException("Cannot find class driver:" + cfg.getDriverClassName());
        }
        dataSource.setDriverClass(driverClass);
        dataSource.setUrl(cfg.getUrl().replace(NETXILIA_HOME_VAR, path));
        dataSource.setUsername(cfg.getUsername());
        dataSource.setPassword(cfg.getPassword());
        return dataSource;
    }

    public synchronized DataSource buildDataSource(DataSourceConfiguration cfg) {
        init();
        Pair<GenericObjectPool, DataSource> poolInfo = pools.get(cfg.getId());
        if (poolInfo != null) {
            return poolInfo.getSecond();
        }

        GenericObjectPool connectionPool = applicationContext.getBean(GenericObjectPool.class);
        DataSource simpleDataSource = buildSimpleDataSource(cfg);

        ConnectionFactory connectionFactory = new DataSourceConnectionFactory(simpleDataSource);

        // ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(cfg.getUrl().replace(
        // NETXILIA_HOME_VAR, path), cfg.getUsername(), cfg.getPassword());

        new PoolableConnectionFactory(connectionFactory, connectionPool, null, null, false, true);
        PoolingDataSource poolingDataSource = new PoolingDataSource(connectionPool);

        log.info("Creating a new datasource " + poolingDataSource + " for config:" + cfg.getId());
        pools.put(cfg.getId(), new Pair<GenericObjectPool, DataSource>(connectionPool, poolingDataSource));

        return poolingDataSource;
    }

    @Override
    public List<Pair<WorkbookId, DataSourceConfigurationId>> findAllWorkbooksConfigurations()
            throws StorageException {
        init();
        Properties props = loadWorkbookToDataSourceFile();
        List<Pair<WorkbookId, DataSourceConfigurationId>> list = new ArrayList<Pair<WorkbookId, DataSourceConfigurationId>>();
        for (Map.Entry<Object, Object> entry : props.entrySet()) {
            list.add(new Pair<WorkbookId, DataSourceConfigurationId>(new WorkbookId(entry.getKey().toString()),
                    new DataSourceConfigurationId(entry.getValue().toString())));
        }
        return list;
    }

    @Override
    public List<WorkbookId> findAllWorkbooksConfigurationsForDatasource(DataSourceConfigurationId id)
            throws StorageException {
        init();
        Properties props = loadWorkbookToDataSourceFile();
        List<WorkbookId> list = new ArrayList<WorkbookId>();
        for (Map.Entry<Object, Object> entry : props.entrySet()) {
            WorkbookId workbookId = new WorkbookId(entry.getKey().toString());
            DataSourceConfigurationId cfgId = new DataSourceConfigurationId(entry.getValue().toString());
            if (id.equals(cfgId)) {
                list.add(workbookId);
            }
        }
        return list;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;

    }

    public void close() {
        log.info("Closing Datasource pools");
        for (Map.Entry<DataSourceConfigurationId, Pair<GenericObjectPool, DataSource>> entry : pools.entrySet()) {
            try {
                log.info("Closed datasource " + entry.getValue().getSecond() + " for config:" + entry.getKey());
                entry.getValue().getFirst().close();

            } catch (Exception e) {
                log.error("Cannot close datasource pool:" + e, e);
            }
        }
        pools.clear();
    }

    @Override
    public void addDataSourceConfigurationListener(IDataSourceConfigurationListener listener) {
        listeners.add(listener);
    }

    @Override
    public void removeDataSourceConfigurationListener(IDataSourceConfigurationListener listener) {
        listeners.remove(listener);
    }

    private void notifyDeleteConfiguration(DataSourceConfigurationId id) {
        for (IDataSourceConfigurationListener listener : listeners) {
            listener.onDeleteConfiguration(id);
        }
    }

    private void notifyDeleteConfigurationForWorkbook(WorkbookId workbookKey) {
        for (IDataSourceConfigurationListener listener : listeners) {
            listener.onDeleteConfigurationForWorkbook(workbookKey);
        }
    }

    private void notifyModifyConfiguration(DataSourceConfigurationId id) {
        for (IDataSourceConfigurationListener listener : listeners) {
            listener.onModifyConfiguration(id);
        }
    }
}