/* * Copyright (C) 2007 SQL Explorer Development Team http://sourceforge.net/projects/eclipsesql * * This program 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 2.1 of the License, or (at your option) * any later version. * * This library 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 library; if not, write to * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package net.sourceforge.sqlexplorer.dbproduct; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import net.sourceforge.sqlexplorer.ExplorerException; import net.sourceforge.sqlexplorer.IConstants; import net.sourceforge.sqlexplorer.connections.SessionEstablishedListener; import net.sourceforge.sqlexplorer.plugin.SQLExplorerPlugin; import org.dom4j.Element; import org.dom4j.tree.DefaultElement; import org.talend.core.GlobalServiceRegister; import org.talend.core.ITDQRepositoryService; import org.talend.core.database.EDatabaseTypeName; import org.talend.core.model.metadata.IMetadataConnection; import org.talend.core.model.metadata.builder.ConvertionHelper; import org.talend.core.model.metadata.builder.connection.DatabaseConnection; import org.talend.cwm.helper.ConnectionHelper; /** * Represents a username and password combo used to connect to an alias; contains a list of all connections made * * @author John Spackman */ public class User implements Comparable<User>, SessionEstablishedListener { /* package */static final String USER = "user"; /* package */static final String USER_NAME = "user-name"; /* package */static final String PASSWORD = "password"; private static final String AUTO_COMMIT = "auto-commit"; private static final String COMMIT_ON_CLOSE = "commit-on-close"; // Maximum number of connections to keep in the pool public static final int MAX_POOL_SIZE = 3; // The Alias we belong to private Alias alias; // Username and password to login as private String userName; private String password; // Pool of available connections private LinkedList<SQLConnection> unused = new LinkedList<SQLConnection>(); // List of connections in use private LinkedList<SQLConnection> allocated = new LinkedList<SQLConnection>(); // Special session for MetaData private MetaDataSession metaDataSession; // List of Sessions (exluding meta data session) private LinkedList<Session> sessions = new LinkedList<Session>(); // List of requests for a new session private LinkedList<SessionEstablishedListener> newSessionsQueue = new LinkedList<SessionEstablishedListener>(); // Auto commit behaviour private boolean autoCommit; private boolean commitOnClose; @Deprecated // get it from databaseConnection. private IMetadataConnection metadataConnection; // User relationship with DatabaseConnection is "one to one" private DatabaseConnection databaseConnection = null; /** * Getter for metadataConnection. * * @return the metadataConnection * @deprecated use {@link #getDatabaseConnection()} */ @Deprecated public IMetadataConnection getMetadataConnection() { return this.metadataConnection; } /** * Sets the metadataConnection. * * @param metadataConnection the metadataConnection to set * @deprecated use {@link #setDatabaseConnection(DatabaseConnection)} */ @Deprecated public void setMetadataConnection(IMetadataConnection metadataConnection) { this.metadataConnection = metadataConnection; } /** * Constructor * * @param userName * @param password */ public User(String userName, String password) { super(); this.userName = userName; this.password = password; // Get default autocommit behaviour autoCommit = SQLExplorerPlugin.getDefault().getPluginPreferences().getBoolean(IConstants.AUTO_COMMIT); commitOnClose = SQLExplorerPlugin.getDefault().getPluginPreferences().getBoolean(IConstants.COMMIT_ON_CLOSE); } /** * Constructs a User, from a definition previously recorded by describeAsXml() * * @param root */ public User(Element root) { super(); this.userName = root.elementText(USER_NAME); this.password = root.elementText(PASSWORD); autoCommit = getBoolean(root.attributeValue(AUTO_COMMIT), true); commitOnClose = getBoolean(root.attributeValue(COMMIT_ON_CLOSE), true); } /** * Describes the User in XML * * @return */ public Element describeAsXml() { Element root = new DefaultElement(USER); String tmpUserName = userName == null ? "" : userName; //$NON-NLS-1$ root.addElement(USER_NAME).setText(tmpUserName); // MOD mzhao bug:19539 Encript the password String tempPassword = ConnectionHelper.getEncryptPassword(password) == null ? "" : ConnectionHelper //$NON-NLS-1$ .getEncryptPassword(password); root.addElement(PASSWORD).setText(tempPassword); // ~19539 root.addAttribute(AUTO_COMMIT, Boolean.toString(autoCommit)); root.addAttribute(COMMIT_ON_CLOSE, Boolean.toString(commitOnClose)); return root; } /** * Creates a duplicate of this User * * @return */ public User createCopy() { User copy = new User(userName, password); return copy; } /** * Merges the definition of the User "that" - IE takes the password, auto-commit behaviour, etc * * @param that */ public void mergeWith(User that) { password = that.getPassword(); autoCommit = that.isAutoCommit(); commitOnClose = that.isCommitOnClose(); for (SQLConnection connection : that.unused) { connection.setUser(this); if (unused.size() < MAX_POOL_SIZE) { unused.add(connection); } else { try { closeConnection(connection); } catch (SQLException e) { SQLExplorerPlugin.error("Cannot close connection", e); } } } for (SQLConnection connection : that.allocated) { connection.setUser(this); allocated.add(connection); } for (Session session : that.sessions) { session.setUser(this); sessions.add(session); } metaDataSession = that.metaDataSession; // Make "that" unusable that.unused = null; that.allocated = null; that.sessions = null; that.metaDataSession = null; that.password = null; SQLExplorerPlugin.getDefault().getAliasManager().modelChanged(); } /** * Queues the listener to receive a session as soon as possible (FIFO queue); only one attempt to establish a * session at a time is made, and if one fails then the rest fail. This is typically important for startup when * restoring lots of connections and we want to avoid the user getting presented with more than one error dialog for * the same alias/user. * * @param listener * @param requirePassword */ public synchronized void queueForNewSession(SessionEstablishedListener listener, boolean requirePassword) { newSessionsQueue.add(listener); if (newSessionsQueue.size() == 1) { ConnectionJob.createSession(alias, this, this, requirePassword); } } /** * @see queueForNewSession(SessionEstablishedListener, boolean) */ public synchronized void queueForNewSession(SessionEstablishedListener listener) { queueForNewSession(listener, false); } /** * Callback when a session cannot be established; notifies all listeners and then clears down the list on the basis * that it was a terminal login error */ @Override public synchronized void cannotEstablishSession(User user) { for (SessionEstablishedListener listener : newSessionsQueue) { listener.cannotEstablishSession(this); } newSessionsQueue.clear(); } /** * Callback when a session has been established; notifies the next listener in the queue and then starts to * establish a new session */ @Override public synchronized void sessionEstablished(Session session) { SessionEstablishedListener listener = newSessionsQueue.removeFirst(); listener.sessionEstablished(session); if (!newSessionsQueue.isEmpty()) { ConnectionJob.createSession(alias, this, this, false); } } /** * Creates a new session; NOTE, this is a blocking call, use ConnectionJob for asychronous connections * * @return */ public Session createSession() throws SQLException { Session session = new Session(this); sessions.add(session); SQLExplorerPlugin.getDefault().getAliasManager().modelChanged(); return session; } /** * Returns the special session for accessing meta data * * @return * @throws SQLException */ public MetaDataSession getMetaDataSession() { if (metaDataSession == null) { try { metaDataSession = new MetaDataSession(this); } catch (SQLException e) { SQLExplorerPlugin.error(e); } } return metaDataSession; } /** * Releases a session * * @param session */ /* package */void releaseSession(Session session) { SQLExplorerPlugin.getDefault().getAliasManager().modelChanged(); sessions.remove(session); } /** * Returns a list of sessions * * @return */ public List<Session> getSessions() { return sessions; } /** * Returns true if the session belongs to this User * * @param session * @return */ public boolean contains(Session session) { return sessions.contains(session); } /** * Closes all connections; note that ConnectionListeners are NOT invoked */ /* package */void closeAllSessions() { // MOD qiongli 2012-11-12 TDQ-6166 avoid ConcurrentModificationException,repalce for with Iterator while Iterator<Session> iterator = sessions.iterator(); while (sessions.size() > 0 && iterator.hasNext()) { Session session = iterator.next(); session.close(); } if (metaDataSession != null) { metaDataSession.close(); metaDataSession = null; } } /** * Retrieves a new connection, either from the pool or by allocating a new one * * @return * @throws ExplorerException */ public SQLConnection getConnection() throws SQLException { SQLConnection connection; if (!unused.isEmpty()) { connection = unused.removeFirst(); } else { connection = createNewConnection(); SQLExplorerPlugin.getDefault().getAliasManager().modelChanged(); } allocated.add(connection); return connection; } /** * Releases a connection; the connection will be returned to the pool, unless the pool has grown too large (in which * case the connection is closed). Note that the connection may not be in the "allocated" list if it is a hidden * connection (see hideConnection()) * * @param connection * @throws ExplorerException */ public void releaseConnection(SQLConnection connection) throws SQLException { if (connection.getConnection() == null || connection.getConnection().isClosed()) { disposeConnection(connection); return; } boolean forPool = allocated.remove(connection); boolean commitOnClose = SQLExplorerPlugin.getDefault().getPluginPreferences().getBoolean(IConstants.COMMIT_ON_CLOSE); if (!connection.getAutoCommit()) { if (commitOnClose) { connection.commit(); } else { connection.rollback(); } } // Keep the pool small if (forPool && unused.size() < MAX_POOL_SIZE) { unused.add(connection); return; } // Close unwanted connections closeConnection(connection); } /** * DOC msjian Comment method "closeConnection". * * @param connection * @throws SQLException */ protected void closeConnection(SQLConnection connection) throws SQLException { if (!connection.getConnection().isClosed()) { if (!connection.isPooled()) { String url = connection.getSQLMetaData().getURL(); // we hold on hsql server's status when it is server mode and not In-Process mode. if (url != null && url.startsWith("jdbc:hsqldb") && (!url.startsWith("jdbc:hsqldb:hsql"))) { Statement statement = connection.createStatement(); statement.executeUpdate("SHUTDOWN;");//$NON-NLS-1$ statement.close(); } } connection.close(); } } /** * Disposes of the connection without returning it to the pool; usually called when the connection has been closed * by the server * * @param connection */ public void disposeConnection(SQLConnection connection) { allocated.remove(connection); try { closeConnection(connection); } catch (SQLException e) { // Nothing } } /** * Returns the connection from the pool, assuming the connection is currently in the pool * * @param connection * @return true if the connection was in the and has been removed */ public synchronized boolean releaseFromPool(SQLConnection connection) { try { closeConnection(connection); } catch (SQLException e) { SQLExplorerPlugin.error(e); } if (unused.remove(connection)) { SQLExplorerPlugin.getDefault().getAliasManager().modelChanged(); return true; } return false; } /** * Returns true if the connection is part of the pool of available connections * * @param connection * @return */ public boolean isInPool(SQLConnection connection) { return unused.contains(connection); } /** * Returns true if the User is in use (ie has any connections in use or active sessions) * * @return */ public boolean isInUse() { return !allocated.isEmpty() || !sessions.isEmpty(); } /** * Returns all connections * * @return */ public List<SQLConnection> getConnections() { LinkedList<SQLConnection> result = new LinkedList<SQLConnection>(); result.addAll(allocated); result.addAll(unused); return result; } /** * Returns unused connections * * @return */ public List<SQLConnection> getUnusedConnections() { LinkedList<SQLConnection> result = new LinkedList<SQLConnection>(); // MOD xqliu TDQ-7401 don't add allocated connections here // result.addAll(allocated); // ~ TDQ-7401 result.addAll(unused); return result; } /** * Returns true if the user has successfully authenticated at some point; IE, will grabConnection() be able to * return a valid connection, either from the pool or by establishing a new connection, without normally causing an * authentication failure * * @return */ public boolean hasAuthenticated() { return allocated.size() + unused.size() > 0; } /** * Creates a new connection, MOD xqliu 2013-04-03 TDQ-7003 * * @return * @throws ExplorerException * @throws SQLException */ protected synchronized SQLConnection createNewConnection() throws SQLException { SQLConnection connection = null; // if it is hive connection, should call tdqRepService.createHiveConnection() to create the connection, because // need use DynamicClassLoader to deal with it if (databaseConnection != null && EDatabaseTypeName.HIVE.getXmlName().equalsIgnoreCase(databaseConnection.getDatabaseType())) { if (GlobalServiceRegister.getDefault().isServiceRegistered(ITDQRepositoryService.class)) { ITDQRepositoryService tdqRepService = (ITDQRepositoryService) GlobalServiceRegister.getDefault().getService( ITDQRepositoryService.class); if (tdqRepService != null) { IMetadataConnection mdConn = ConvertionHelper.convert(databaseConnection); Connection hiveConnection = tdqRepService.createHiveConnection(mdConn); if (hiveConnection != null) { connection = new SQLConnection(this, hiveConnection, alias.getDriver(), "HiveConnection"); } } } } else { connection = alias.getDriver().getConnection(this); } return connection; } /** * Returns the Alias for this User * * @return */ public Alias getAlias() { return alias; } /** * Changes the alias for the User * * @param alias */ public void setAlias(Alias alias) { if (this.alias != null && alias != null) { if (this.alias != alias) { throw new IllegalArgumentException("Cannot change a User's Alias"); } return; } this.alias = alias; } public String getPassword() { return password; } public String getUserName() { return userName; } public void setPassword(String password) { this.password = password; } public void setUserName(String userName) { this.userName = userName; } public String getDescription() { return getAlias().getName() + '/' + getUserName(); } public boolean isAutoCommit() { return autoCommit; } public void setAutoCommit(boolean autoCommit) { this.autoCommit = autoCommit; } public boolean isCommitOnClose() { return commitOnClose; } public void setCommitOnClose(boolean commitOnClose) { this.commitOnClose = commitOnClose; } @Override public int compareTo(User that) { return userName.compareToIgnoreCase(that.getUserName()); } private boolean getBoolean(String value, boolean defaultValue) { try { return Boolean.parseBoolean(value); } catch (Exception e) { return defaultValue; } } @Override public String toString() { return getDescription(); } public DatabaseConnection getDatabaseConnection() { return this.databaseConnection; } public void setDatabaseConnection(DatabaseConnection databaseConnection) { this.databaseConnection = databaseConnection; } }