/*
 *  $Id$
 *
 *  This file is part of the OpenLink Software Virtuoso Open-Source (VOS)
 *  project.
 *
 *  Copyright (C) 1998-2024 OpenLink Software
 *
 *  This project is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; only version 2 of the License, dated June 1991.
 *
 *  This program 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
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 *
 */

package virtuoso.sesame3.driver;

import java.io.File;
import java.sql.DriverManager;
import java.sql.SQLException;

import org.openrdf.model.BNodeFactory;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.URIFactory;
import org.openrdf.model.LiteralFactory;
import org.openrdf.model.impl.BNodeFactoryImpl;
import org.openrdf.model.impl.ValueFactoryImpl;
import org.openrdf.model.impl.LiteralFactoryImpl;
import org.openrdf.model.impl.URIFactoryImpl;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryMetaData;
import org.openrdf.store.StoreException;

import virtuoso.jdbc4.VirtuosoConnectionPoolDataSource;

/**
 * A Sesame repository that contains RDF data that can be queried and updated.
 * Access to the repository can be acquired by openening a connection to it.
 * This connection can then be used to query and/or update the contents of the
 * repository. Depending on the implementation of the repository, it may or may
 * not support multiple concurrent connections.
 * <p>
 * Please note that a repository needs to be initialized before it can be used
 * and that it should be shut down before it is discarded/garbage collected.
 * Forgetting the latter can result in loss of data (depending on the Repository
 * implementation)!
 * 
 */
public class VirtuosoRepository implements Repository {
	
	private URIFactory uf = new URIFactoryImpl();
	private LiteralFactory lf = new LiteralFactoryImpl();
	private BNodeFactory bf = new BNodeFactoryImpl();

	File dataDir;
	
	private boolean readOnly;
	private RepositoryMetaData metadata;
        private VirtuosoConnectionPoolDataSource pds = new VirtuosoConnectionPoolDataSource();
	private String url;
	private String user;
	private String password;
	private String host = "localhost";
	private int port = 1111;
	private String charset = "UTF-8";
	private boolean roundrobin = false;
	String defGraph;

	boolean useLazyAdd = false;
	int prefetchSize = 200;
	int queryTimeout = 0;
	private boolean initialized = false;
	String ruleSet;
    
	

	/**
	 * Construct a VirtuosoRepository with a specified parameters
	 * 
	 * @param hostlist
	 *        the Virtuoso database hostlist 
	 *        <pre>
	 *        "hostone:1112,hosttwo:1113" 
	 *     or "hostone,hosttwo" if default port=1111 is used on hosts
	 *        </pre>
	 * @param user
	 *        the database user on whose behalf the connection is being made
	 * @param password
	 *        the user's password
	 * @param defGraph
	 *        a default Graph name, used for Sesame calls, when contexts list
	 *        is empty, exclude <tt>exportStatements, hasStatement, getStatements</tt> methods 
	 * @param useLazyAdd
	 *        set <tt>true</tt>  to enable using batch optimization for sequence of 
	 *        <pre>
	 *	  add(Resource subject, URI predicate, Value object, Resource... contexts);
         *        add(Statement statement, Resource... contexts);
	 *        </pre>
         *        methods, when autoCommit mode is off. The triples will be sent to DBMS on commit call
         *        or when batch size become more than predefined batch max_size. 
         *
	 */
	public VirtuosoRepository(String hostlist, String user, String password, String defGraph, boolean useLazyAdd) {
	        super();
		this.host = hostlist;
		this.user = user;
		this.password = password;
		this.defGraph = defGraph;
		this.useLazyAdd = useLazyAdd;
	}

	/**
	 * Construct a VirtuosoRepository with a specified parameters
	 * 
	 * @param host
	 *        the Virtuoso database hostname
	 * @param port
	 *        the Virtuoso database portnumber
	 * @param user
	 *        the database user on whose behalf the connection is being made
	 * @param password
	 *        the user's password
	 * @param defGraph
	 *        a default Graph name, used for Sesame calls, when contexts list
	 *        is empty, exclude <tt>exportStatements, hasStatement, getStatements</tt> methods 
	 * @param useLazyAdd
	 *        set <tt>true</tt>  to enable using batch optimization for sequence of 
	 *        <pre>
	 *	  add(Resource subject, URI predicate, Value object, Resource... contexts);
         *        add(Statement statement, Resource... contexts);
	 *        </pre>
         *        methods, when autoCommit mode is off. The triples will be sent to DBMS on commit call
         *        or when batch size become more than predefined batch max_size. 
         *
	 */
	public VirtuosoRepository(String host, int port, String user, String password, String defGraph, boolean useLazyAdd) {

	        super();
		this.port = port;
		this.host = host;
		this.user = user;
		this.password = password;
		this.defGraph = defGraph;
		this.useLazyAdd = useLazyAdd;
	}

	/**
	 * Construct a VirtuosoRepository with a specified parameters
	 * <tt>defGraph</tt> will be set to <tt>"sesame:nil"</tt>.
	 * 
	 * @param hostlist
	 *        the Virtuoso database hostlist 
	 *        <pre>
	 *        "hostone:1112,hosttwo:1113" 
	 *     or "hostone,hosttwo" if default port=1111 is used on hosts
	 *        </pre>
	 * @param user
	 *        the database user on whose behalf the connection is being made
	 * @param password
	 *        the user's password
	 * @param useLazyAdd
	 *        set <tt>true</tt>  to enable using batch optimization for sequence of 
	 *        <pre>
	 *	  add(Resource subject, URI predicate, Value object, Resource... contexts);
         *        add(Statement statement, Resource... contexts);
	 *        </pre>
         *        methods, when autoCommit mode is off. The triples will be sent to DBMS on commit call
         *        or when batch size become more than predefined batch max_size. 
         *
	 */
	public VirtuosoRepository(String hostlist, String user, String password, boolean useLazyAdd) {
	        this(hostlist, user, password, "sesame:nil", useLazyAdd);
	}

	/**
	 * Construct a VirtuosoRepository with a specified parameters
	 * <tt>defGraph</tt> will be set to <tt>"sesame:nil"</tt>.
	 * 
	 * @param host
	 *        the Virtuoso database hostname
	 * @param port
	 *        the Virtuoso database portnumber
	 * @param user
	 *        the database user on whose behalf the connection is being made
	 * @param password
	 *        the user's password
	 * @param useLazyAdd
	 *        set <tt>true</tt>  to enable using batch optimization for sequence of 
	 *        <pre>
	 *	  add(Resource subject, URI predicate, Value object, Resource... contexts);
         *        add(Statement statement, Resource... contexts);
	 *        </pre>
         *        methods, when autoCommit mode is off. The triples will be sent to DBMS on commit call
         *        or when batch size become more than predefined batch max_size. 
         *
	 */
	public VirtuosoRepository(String host, int port, String user, String password, boolean useLazyAdd) {
	        this(host, port, user, password, "sesame:nil", useLazyAdd);
	}

	/**
	 * Construct a VirtuosoRepository with a specified parameters.
	 * useLazyAdd will be set to <tt>false</tt>.
	 * 
	 * @param hostlist
	 *        the Virtuoso database hostlist 
	 *        <pre>
	 *        "hostone:1112,hosttwo:1113" 
	 *     or "hostone,hosttwo" if default port=1111 is used on hosts
	 *        </pre>
	 * @param user
	 *        the database user on whose behalf the connection is being made
	 * @param password
	 *        the user's password
	 * @param defGraph
	 *        a default Graph name, used for Sesame calls, when contexts list
	 *        is empty, exclude <tt>exportStatements, hasStatement, getStatements</tt> methods 
         *
	 */
	public VirtuosoRepository(String hostlist, String user, String password, String defGraph) {
	        this(hostlist, user, password, defGraph, false);
	}

	/**
	 * Construct a VirtuosoRepository with a specified parameters.
	 * useLazyAdd will be set to <tt>false</tt>.
	 * 
	 * @param host
	 *        the Virtuoso database hostname
	 * @param port
	 *        the Virtuoso database portnumber
	 * @param user
	 *        the database user on whose behalf the connection is being made
	 * @param password
	 *        the user's password
	 * @param defGraph
	 *        a default Graph name, used for Sesame calls, when contexts list
	 *        is empty, exclude <tt>exportStatements, hasStatement, getStatements</tt> methods 
         *
	 */
	public VirtuosoRepository(String host, int port, String user, String password, String defGraph) {
	        this(host, port, user, password, defGraph, false);
	}

	/**
	 * Construct a VirtuosoRepository with a specified parameters.
	 * <tt>useLazyAdd</tt> will be set to <tt>false</tt>.
	 * <tt>defGraph</tt> will be set to <tt>"sesame:nil"</tt>.
	 * 
	 * @param hostlist
	 *        the Virtuoso database hostlist 
	 *        <pre>
	 *        "hostone:1112,hosttwo:1113" 
	 *     or "hostone,hosttwo" if default port=1111 is used on hosts
	 *        </pre>
	 * @param user
	 *        the database user on whose behalf the connection is being made
	 * @param password
	 *        the user's password
         *
	 */
	public VirtuosoRepository(String hostlist, String user, String password) {
	        this(hostlist, user, password, false);
	}

	/**
	 * Construct a VirtuosoRepository with a specified parameters.
	 * <tt>useLazyAdd</tt> will be set to <tt>false</tt>.
	 * <tt>defGraph</tt> will be set to <tt>"sesame:nil"</tt>.
	 * 
	 * @param host
	 *        the Virtuoso database hostname
	 * @param port
	 *        the Virtuoso database portnumber
	 * @param user
	 *        the database user on whose behalf the connection is being made
	 * @param password
	 *        the user's password
         *
	 */
	public VirtuosoRepository(String host, int port, String user, String password) {
	        this(host, port, user, password, false);
	}

	/**
	 * Opens a connection to this repository that can be used for querying and
	 * updating the contents of the repository. Created connections need to be
	 * closed to make sure that any resources they keep hold of are released. The
	 * best way to do this is to use a try-finally-block as follows:
	 * 
	 * <pre>
	 * Connection con = repository.getConnection();
	 * try {
	 * 	// perform operations on the connection
	 * }
	 * finally {
	 * 	con.close();
	 * }
	 * </pre>
	 * 
	 * @return A connection that allows operations on this repository.
	 * @throws RepositoryException
	 *         If something went wrong during the creation of the Connection.
	 */
	public RepositoryConnection getConnection() throws StoreException {
		try {
			pds.setServerName(host);
			pds.setPortNumber(port);
			pds.setUser(user);
			pds.setPassword(password);
			pds.setCharset(charset);
			pds.setRoundrobin(roundrobin);
			javax.sql.PooledConnection pconn = pds.getPooledConnection();
			java.sql.Connection connection = pconn.getConnection();
			return new VirtuosoRepositoryConnection(this, connection);
		}
		catch (SQLException e) {
			System.out.println("Connection to " + host + " is FAILED.");
			throw new StoreException(e);
		}
	}

	/**
	 * Set the buffer fetch size(default 200) 
	 * 
	 * @param sz
	 *        buffer fetch size.
	 */
	public void setFetchSize(int sz) {
		this.prefetchSize = sz;
	}

	/**
	 * Get the buffer fetch size
	 */
	public int getFetchSize() {
		return this.prefetchSize;
	}


	/**
	 * Set the query timeout(default 0) 
	 * 
	 * @param seconds
	 *        queryTimeout seconds, 0 - unlimited.
	 */
	public void setQueryTimeout(int seconds) {
		this.queryTimeout = seconds;
	}

	/**
	 * Get the query timeout seconds
	 */
	public int getQueryTimeout() {
		return this.queryTimeout;
	}

	/**
	 * Set the RoundRobin state for connection(default false) 
	 * 
	 * @param sz
	 *        buffer fetch size.
	 */
	public void setRoundrobin(boolean v) {
		this.roundrobin = v;
	}

	/**
	 * Get the RoundRobin state for connection
	 */
	public boolean getRoundrobin() {
		return this.roundrobin;
	}


	public RepositoryMetaData getMetaData()	throws StoreException
	{
		if (metadata == null) {
			metadata = new VirtuosoRepositoryMetaData(this);
		}
		return metadata;
	}

	/**
	 * Gets a ValueFactory for this Repository.
	 * 
	 * @return A repository-specific ValueFactory.
	 */
	@Deprecated
	public ValueFactory getValueFactory() {
		return new ValueFactoryImpl(bf, uf, lf);
	}

	/**
	 * Gets a URIFactory for this Repository.
	 * 
	 * @return A repository-specific URIFactory.
	 */
	public URIFactory getURIFactory()
	{
		return uf;
	}

	/**
	 * Gets a LiteralFactory for this Repository.
	 * 
	 * @return A repository-specific LiteralFactory.
	 */
	public LiteralFactory getLiteralFactory()
	{
		return lf;
	}

	/**
	 * Initializes this repository. A repository needs to be initialized before
	 * it can be used.
	 * 
	 * @throws RepositoryException
	 *         If the initialization failed.
	 */
	public void initialize() throws StoreException {
		initialized = true;
	}


	/**
	 * Set inference RuleSet name
	 * 
	 * @param name
	 *        RuleSet name.
	 */
	public void setRuleSet(String name) {
		if (name != null && name.equals("null"))
		  	name = null;
		this.ruleSet = name;
	}

	/**
	 * Get the RoundRobin state for connection
	 */
	public String getRuleSet() {
		return this.ruleSet;
	}

	
	/**
	 * Get the directory where data and logging for this repository is stored.
	 * 
	 * @return the directory where data for this repository is stored.
	 */
	public File getDataDir() {
		return this.dataDir;
	}

	/**
	 * Set the directory where data and logging for this repository is stored.
	 * 
	 * @param dataDir
	 *        the directory where data for this repository is stored
	 */
	public void setDataDir(File dataDir) {
		this.dataDir = dataDir;
	}

	/**
	 * Shuts the repository down, releasing any resources that it keeps hold of.
	 * Once shut down, the repository can no longer be used until it is
	 * re-initialized.
	 */
	public void shutDown() throws StoreException {
		initialized = false;
	}

	public boolean isReadOnly() {
		return readOnly;
	}

	public void setReadOnly(boolean readOnly) {
		this.readOnly = readOnly;
	}


        public void createRuleSet(String ruleSetName, String uriGraphRuleSet) throws StoreException
        {
          java.sql.Connection con = ((VirtuosoRepositoryConnection)getConnection()).getQuadStoreConnection();

          try {
	    java.sql.Statement st = con.createStatement();
	    st.execute("rdfs_rule_set('"+ruleSetName+"', '"+uriGraphRuleSet+"')");
	    st.close();
          } catch (Exception e) {
            throw new StoreException(e);
          }
        }

}
