code: package it.unibz.inf.ontop.protege.connection;
  
  /*
   * #%L
   * ontop-obdalib-core
   * %%
   * Copyright (C) 2009 - 2014 Free University of Bozen-Bolzano
   * %%
   * Licensed 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.
   * #L%
   */
  
  
  import it.unibz.inf.ontop.shaded.com.google.common.collect.ImmutableSet;
  import it.unibz.inf.ontop.injection.OntopSQLCoreSettings;
  import it.unibz.inf.ontop.injection.OntopSQLCredentialSettings;
  import it.unibz.inf.ontop.protege.utils.DialogUtils;
  import it.unibz.inf.ontop.protege.utils.EventListenerList;
  import it.unibz.inf.ontop.utils.ImmutableCollectors;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import javax.annotation.Nonnull;
  import java.io.*;
  import java.net.URI;
  import java.sql.Connection;
  import java.sql.DriverManager;
  import java.sql.SQLException;
  import java.util.*;
  
  import static it.unibz.inf.ontop.injection.OntopSQLCoreSettings.JDBC_DRIVER;
  import static it.unibz.inf.ontop.injection.OntopSQLCoreSettings.JDBC_URL;
  import static it.unibz.inf.ontop.injection.OntopSQLCredentialSettings.JDBC_PASSWORD;
  import static it.unibz.inf.ontop.injection.OntopSQLCredentialSettings.JDBC_USER;
  
  public class DataSource {
  
  	private static final Logger LOGGER = LoggerFactory.getLogger(DataSource.class);
  	
  	private final Properties properties = new Properties();
  
  	private final URI id;
  	private String driver = "", url = "", username = "", password = "";
  
  	private final EventListenerList<DataSourceListener> listeners = new EventListenerList<>();
  
  	private Connection connection;
  
  	/*  TODO: default parameters for creating statements (to be enforced?)
  			JDBC_AUTOCOMMIT, false
  			JDBC_FETCHSIZE, 100
  			JDBC_RESULTSETCONCUR, ResultSet.CONCUR_READ_ONLY
  			JDBC_RESULTSETTYPE, ResultSet.TYPE_FORWARD_ONLY
  		 */
  
  
  	public DataSource() {
  		this.id = URI.create(UUID.randomUUID().toString());
  	}
  
  	/**
  	 * Closes the connection quietly
  	 */
  	public void dispose() {
  		try {
  			if (connection != null) {
  				connection.close();
  				connection = null;
  			}
  		}
  		catch (Exception e) {
  			LOGGER.error(e.getMessage());
  		}
  	}
  
  
  	@Nonnull
  	public String getDriver() {
  		return driver;
  	}
  
  	@Nonnull
  	public String getUsername() {
  		return username;
  	}
  
  	@Nonnull
  	public String getPassword() {
  		return password;
  	}
  
  	@Nonnull
  	public String getURL() {
  		return url;
  	}
  
  	public void set(String url, String username, String password, String driver) {
  		Objects.requireNonNull(url);
  		Objects.requireNonNull(username);
  		Objects.requireNonNull(password);
  		Objects.requireNonNull(driver);
  
  		boolean changed = !this.url.equals(url) || !this.username.equals(username)
  					|| !this.password.equals(password) || !this.driver.equals(driver);
  
  		this.url = url;
  		this.username = username;
  		this.password = password;
  		this.driver = driver;
  
  		if (changed) {
  			dispose();
  			listeners.fire(l -> l.changed(this));
  		}
  	}
  
  	/**
  	 * Retrieves the connection object.
  	 * If the connection doesn't exist or is dead, it will create a new connection.
  	 */
  	public Connection getConnection() throws SQLException {
  		if (connection == null || connection.isClosed())
  			connection = DriverManager.getConnection(url, username, password);
  
  		return connection;
  	}
  
  	public Properties asProperties() {
  		Properties p = new Properties();
  		p.putAll(properties);
  		p.put(OntopSQLCoreSettings.JDBC_URL, url);
  		p.put(OntopSQLCredentialSettings.JDBC_USER, username);
  		p.put(OntopSQLCredentialSettings.JDBC_PASSWORD, password);
  		p.put(OntopSQLCoreSettings.JDBC_DRIVER, driver);
  		return p;
  	}
  
  	public static final ImmutableSet<String> CONNECTION_PARAMETER_NAMES = ImmutableSet.of(
  			OntopSQLCoreSettings.JDBC_URL,
  			OntopSQLCredentialSettings.JDBC_USER,
  			OntopSQLCredentialSettings.JDBC_PASSWORD,
  			OntopSQLCoreSettings.JDBC_DRIVER);
  
  	public void clear() {
  		properties.clear();
  		driver = "";
  		url = "";
  		username = "";
  		password = "";
  		connection = null;
  	}
  
  	public ImmutableSet<String> getPropertyKeys() {
  		return properties.keySet().stream()
  				.map(Object::toString)
  				.filter(k -> !CONNECTION_PARAMETER_NAMES.contains(k))
  				.collect(ImmutableCollectors.toSet());
  	}
  
  	public Object getProperty(@Nonnull String key) {
  		return properties.getProperty(key);
  	}
  
  	public void setProperty(@Nonnull String key, @Nonnull Object value) {
  		if (CONNECTION_PARAMETER_NAMES.contains(key))
  			throw new IllegalArgumentException("Cannot change reserved " + key);
  
  		Object oldValue = properties.get(key); // can be null
  		if (!value.equals(oldValue)) {
  			properties.put(key, value);
  			listeners.fire(l -> l.changed(this));
  		}
  	}
  
  	public void removeProperty(@Nonnull String key) {
  		if (CONNECTION_PARAMETER_NAMES.contains(key))
  			throw new IllegalArgumentException("Cannot remove reserved " + key);
  
  		properties.remove(key);
  		listeners.fire(l -> l.changed(this));
  	}
  
  	/**
  	 * Should not be called twice in a row without clear() in between.
  	 *
  	 * No need to remove listeners - this is handled by OBDAModelManager.
  	 * @param listener
  	 */
  	public void addListener(DataSourceListener listener) {
  		listeners.add(listener);
  	}
  
  	public void load(File propertyFile) throws IOException {
  		if (propertyFile.exists())
  			try (FileReader reader = new FileReader(propertyFile)) {
  				properties.load(reader);
  				updateDataSourceParametersFromUserSettings();
  			}
  	}
  
  	/**
  	 * possibly called after load() to override settings from the OBDA file
  	*/
  	public void update(Properties p) {
  		properties.putAll(p);
  		updateDataSourceParametersFromUserSettings();
  	}
  
  	private void updateDataSourceParametersFromUserSettings() {
  		set(Optional.ofNullable(properties.getProperty(JDBC_URL))
  						.orElseGet(this::getURL),
  				Optional.ofNullable(properties.getProperty(JDBC_USER))
  						.orElseGet(this::getUsername),
  				Optional.ofNullable(properties.getProperty(JDBC_PASSWORD))
  						.orElseGet(this::getPassword),
  				Optional.ofNullable(properties.getProperty(JDBC_DRIVER))
  						.orElseGet(this::getDriver));
  	}
  
  
  	public void store(File propertyFile) throws IOException {
  		Properties properties = asProperties();
  		boolean nonEmpty = !properties.isEmpty();
  
  		DialogUtils.saveFileOrDeleteEmpty(!nonEmpty, propertyFile, file -> {
  			try (FileOutputStream outputStream = new FileOutputStream(propertyFile)) {
  				properties.store(outputStream, null);
  			}
  		}, LOGGER);
  	}
  }
  