/* * This file is part of AceQL HTTP. * AceQL HTTP: SQL Over HTTP * Copyright (C) 2020, KawanSoft SAS * (http://www.kawansoft.com). All rights reserved. * * AceQL HTTP 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. * * AceQL HTTP 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., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * * Any modifications to this file must keep this entire header * intact. */ package org.kawanfw.sql.servlet.connection; import java.sql.Array; import java.sql.Connection; import java.sql.RowId; import java.sql.SQLException; import java.sql.Savepoint; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import org.kawanfw.sql.api.server.connectionstore.ConnectionKey; import org.kawanfw.sql.util.FrameworkDebug; /** * * Stores the Connection in static for subsequent new calls by remote device/PC * clients. * * @author Nicolas de Pomereu */ public class ConnectionStore { private static boolean DEBUG = FrameworkDebug.isSet(ConnectionStore.class); /** * The connection store key composed of client username and client connection id */ private ConnectionKey connectionKey = null; /** Map of (username + sessionId + connectionId), connection= */ private static Map<ConnectionKey, Connection> connectionMap = new ConcurrentHashMap<>(); /** The map of Savepoints */ private static Map<ConnectionKey, Set<Savepoint>> savepointMap = new ConcurrentHashMap<>(); /** The map of Arrays */ private static Map<ConnectionKey, Set<Array>> arrayMap = new ConcurrentHashMap<>(); /** The map of RowIds */ private static Map<ConnectionKey, Set<RowId>> rowIdMap = new ConcurrentHashMap<>(); /** * Constructor * * @param username * @param sessionId * @param connectionId */ public ConnectionStore(String username, String sessionId, String connectionId) { if (username == null) { throw new IllegalArgumentException("username is null!"); } if (sessionId == null) { throw new IllegalArgumentException("sessionId is null!"); } // NO! Allow null connectionId // if (connectionId == null) { // throw new IllegalArgumentException("connectionId is null!"); // } this.connectionKey = new ConnectionKey(username, sessionId, connectionId); } /** * Stores the Connection in static for username + connectionId * * @param connection * the Connection to store */ public void put(Connection connection) { debug("Creating a Connection for user: " + connectionKey); if (connection == null) { throw new IllegalArgumentException("connection is null!"); } connectionMap.put(connectionKey, connection); } /** * Stores the Savepoint in static for username + connectionId * * @param savepoint * the Savepoint to store */ public void put(Savepoint savepoint) { debug("Creating a Savepoint for user: " + connectionKey); if (savepoint == null) { throw new IllegalArgumentException("savepoint is null!"); } Set<Savepoint> savepointSet = savepointMap.get(connectionKey); if (savepointSet == null) { savepointSet = new LinkedHashSet<Savepoint>(); } savepointSet.add(savepoint); savepointMap.put(connectionKey, savepointSet); } /** * Returns the Savepoint associated to username + connectionId and savepointInfo * * @param a * Savepoint that is just a container with the info to find the real * one * * @return the Savepoint associated to username + connectionId and savepointInfo */ public Savepoint getSavepoint(Savepoint savepointInfo) { Set<Savepoint> savepointSet = savepointMap.get(connectionKey); for (Iterator<Savepoint> iterator = savepointSet.iterator(); iterator.hasNext();) { Savepoint savepoint = (Savepoint) iterator.next(); try { if (savepoint.getSavepointId() == savepointInfo.getSavepointId()) { return savepoint; } } catch (SQLException e) { // We don't care: it's a named Savepoint } try { if (savepoint.getSavepointName().equals(savepointInfo.getSavepointName())) { return savepoint; } } catch (SQLException e) { // We don't care: it's a unnamed Savepoint } } return null; } /** * Remove the Savepoint associated to username + connectionId and savepointInfo * * @param a * Savepoint that is just a container with the info to find the real * one * */ public void remove(Savepoint savepointInfo) { Set<Savepoint> savepointSet = savepointMap.get(connectionKey); Set<Savepoint> savepointSetNew = new TreeSet<Savepoint>(); for (Iterator<Savepoint> iterator = savepointSet.iterator(); iterator.hasNext();) { Savepoint savepoint = (Savepoint) iterator.next(); boolean addIt = true; try { if (savepoint.getSavepointId() == savepointInfo.getSavepointId()) { addIt = false; } } catch (SQLException e) { // We don't care: it's a named Savepoint } try { if (savepoint.getSavepointName().equals(savepointInfo.getSavepointName())) { addIt = false; } } catch (SQLException e) { // We don't care: it's a unnamed Savepoint } if (addIt) { savepointSetNew.add(savepoint); } } // Replace old map by new savepointMap.put(connectionKey, savepointSetNew); } /** * Stores the Array in static for username + connectionId * * @param array * the Array to store */ public void put(Array array) { debug("Creating an array for user: " + connectionKey); if (array == null) { throw new IllegalArgumentException("array is null!"); } Set<Array> arraySet = arrayMap.get(connectionKey); if (arraySet == null) { arraySet = new LinkedHashSet<Array>(); } arraySet.add(array); arrayMap.put(connectionKey, arraySet); } /** * Returns the Array associated to username + connectionId and savepointInfo * * @param arrayId * the array id (it's haschode()) * * @return the Array associated to username + connectionId and arrayId */ public Array getArray(int arrayId) { Set<Array> arraySet = arrayMap.get(connectionKey); for (Iterator<Array> iterator = arraySet.iterator(); iterator.hasNext();) { Array array = (Array) iterator.next(); if (array.hashCode() == arrayId) { return array; } } return null; } // /** // * Remove the Array associated to username + connectionId and ArrayId // * // * @param arrayId // * the array id (it's haschode()) // * // */ // public void removeArray(int arrayId) { // Set<Array> arraySet = arrayMap.get(connectionKey); // // Set<Array> ArraySetNew = new TreeSet<Array>(); // // for (Iterator<Array> iterator = arraySet.iterator(); iterator.hasNext();) // { // Array array = (Array) iterator.next(); // // boolean addIt = true; // // if (array.hashCode() == arrayId) { // addIt = false; // } // // if (addIt) { // ArraySetNew.add(array); // } // } // // // Replace old map by new // arrayMap.put(connectionKey, ArraySetNew); // } // /** * Stores the RowId in static for username + connectionId * * @param rowId * the RowId to store */ public void put(RowId rowId) { debug("Creating a rowId for user: " + connectionKey); if (rowId == null) { throw new IllegalArgumentException("rowId is null!"); } Set<RowId> rowIdSet = rowIdMap.get(connectionKey); if (rowIdSet == null) { rowIdSet = new LinkedHashSet<RowId>(); } rowIdSet.add(rowId); rowIdMap.put(connectionKey, rowIdSet); } /** * Returns the RowId associated to username + connectionId and hashCode * * @param rowIdHashCode * the RowId id (it's haschode()) * * @return the Array associated to username + connectionId and arrayId */ public RowId getRowId(int rowIdHashCode) { Set<RowId> rowIdSet = rowIdMap.get(connectionKey); for (Iterator<RowId> iterator = rowIdSet.iterator(); iterator.hasNext();) { RowId rowId = (RowId) iterator.next(); if (rowId.hashCode() == rowIdHashCode) { return rowId; } } return null; } // /** // * Remove the RowId associated to username + connectionId and hashCode // * // * @param arrayId // * the array id (it's haschode()) // * // */ // public void removeRowId(String rowIdHashCode) { // Set<RowId> arraySet = rowIdMap.get(connectionKey); // // Set<RowId> ArraySetNew = new TreeSet<RowId>(); // // for (Iterator<RowId> iterator = arraySet.iterator(); iterator.hasNext();) // { // RowId rowId = (RowId) iterator.next(); // // boolean addIt = true; // // if (rowId.hashCode() == Integer.parseInt(rowIdHashCode)) { // addIt = false; // } // // if (addIt) { // ArraySetNew.add(rowId); // } // } // // // Replace old map by new // rowIdMap.put(connectionKey, ArraySetNew); // } /** * Returns the Connection associated to username + connectionId * * @return the Connection associated to username + connectionId */ public Connection get() { return connectionMap.get(connectionKey); } /** * Remove all stored instances in the ConnectionStore. This must be done only in * a Logout stage ({@code Connection#close()}). */ public void remove() { debug("Removing a Connection for user: " + connectionKey); connectionMap.remove(connectionKey); savepointMap.remove(connectionKey); arrayMap.remove(connectionKey); rowIdMap.remove(connectionKey); } /** * Returns the size of the Connection Store * * @return the size of the Connection Store */ public int size() { return connectionMap.size(); } /** * Returns the keys of the store * * @return the keys of the store */ public static Set<ConnectionKey> getKeys() { return connectionMap.keySet(); } public static Set<Connection> getAllConnections(String username, String sessionId) { Set<Connection> connections = new HashSet<>(); for (ConnectionKey connectionKey : connectionMap.keySet()) { if (connectionKey.getUsername().equals(username) && connectionKey.getSessionId().equals(sessionId)) { Connection connection = connectionMap.get(connectionKey); connections.add(connection); } } return connections; } /** * Returns the first available Connection of all Connections for * couple(username, sessionId) * * @return the first available Connection of all Connections for * couple(username, sessionId) */ public Connection getFirst() throws SQLException { Set<Connection> connections = getAllConnections(this.connectionKey.getUsername(), this.connectionKey.getSessionId()); if (connections.isEmpty()) { throw new SQLException("No Connection stored for (" + this.connectionKey.getUsername() + ", " + this.connectionKey.getSessionId() + ")"); } List<Connection> connectionsList = new ArrayList<>(); connectionsList.addAll(connections); return connectionsList.get(0); } public static void removeAll(String username, String sessionId) { // No!! Will triger a ConcurrentModificationException! // for (ConnectionKey connectionKey : connectionMap.keySet()) // { // if (connectionKey.getUsername().equals(username) && // connectionKey.getSessionId().equals(sessionId)) { // connectionMap.remove(connectionKey); // } // } // // Intermediate Collection to avoid ConcurrentModificationException on Map Set<ConnectionKey> connectionsKeys = new HashSet<>(connectionMap.keySet()); for (ConnectionKey connectionKey : connectionsKeys) { if (connectionKey.getUsername().equals(username) && connectionKey.getSessionId().equals(sessionId)) { connectionMap.remove(connectionKey); } } } /** * Method called by children Servlet for debug purpose Println is done only if * class name name is in kawansoft-debug.ini */ public static void debug(String s) { if (DEBUG) { System.out.println(new Date() + " " + s); } } }