/** * Licensed to JumpMind Inc under one or more contributor * license agreements. See the NOTICE file distributed * with this work for additional information regarding * copyright ownership. JumpMind Inc licenses this file * to you under the GNU General Public License, version 3.0 (GPLv3) * (the "License"); you may not use this file except in compliance * with the License. * * You should have received a copy of the GNU General Public License, * version 3.0 (GPLv3) along with this library; if not, see * <http://www.gnu.org/licenses/>. * * 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. */ package org.jumpmind.symmetric.transport; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.jumpmind.symmetric.common.ParameterConstants; import org.jumpmind.symmetric.service.IParameterService; import org.jumpmind.symmetric.statistic.IStatisticManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @see IConcurrentConnectionManager */ public class ConcurrentConnectionManager implements IConcurrentConnectionManager { private static final Logger log = LoggerFactory.getLogger(ConcurrentConnectionManager.class); protected IParameterService parameterService; protected Map<String, Map<String, Reservation>> activeReservationsByNodeByPool = new HashMap<String, Map<String, Reservation>>(); protected Map<String, Map<String, NodeConnectionStatistics>> nodeConnectionStatistics = new HashMap<String, Map<String, NodeConnectionStatistics>>(); protected Set<String> whiteList = new HashSet<String>(); public ConcurrentConnectionManager(IParameterService parameterService, IStatisticManager statisticManager) { this.parameterService = parameterService; } protected void logTooBusyRejection(String nodeId, String poolId) { getNodeConnectionStatistics(nodeId, poolId).numOfRejections++; } protected void logConnectedTimePeriod(String nodeId, long startMs, long endMs, String poolId) { NodeConnectionStatistics stats = getNodeConnectionStatistics(nodeId, poolId); stats.totalConnectionCount++; stats.totalConnectionTimeMs += endMs - startMs; stats.lastConnectionTimeMs = startMs; } private synchronized NodeConnectionStatistics getNodeConnectionStatistics(String nodeId, String poolId) { Map<String, NodeConnectionStatistics> statsMap = nodeConnectionStatistics.get(poolId); if (statsMap == null) { statsMap = new HashMap<String, NodeConnectionStatistics>(); nodeConnectionStatistics.put(poolId, statsMap); } NodeConnectionStatistics stats = statsMap.get(nodeId); if (stats == null) { stats = new NodeConnectionStatistics(); statsMap.put(nodeId, stats); } return stats; } synchronized public boolean releaseConnection(String nodeId, String channelId, String poolId) { String reservationKey = String.format("%s::%s", nodeId, channelId); Map<String, Reservation> reservations = getReservationMap(poolId); Reservation reservation = reservations.remove(reservationKey); if (reservation != null) { logConnectedTimePeriod(reservationKey, reservation.createTime, System.currentTimeMillis(), poolId); return true; } else { return false; } } synchronized public void addToWhitelist(String nodeId) { whiteList.add(nodeId); } synchronized public void removeFromWhiteList(String nodeId) { whiteList.remove(nodeId); } synchronized public String[] getWhiteList() { return whiteList.toArray(new String[whiteList.size()]); } synchronized public int getReservationCount(String poolId) { return getReservationMap(poolId).size(); } synchronized public boolean reserveConnection(String nodeId, String channelId, String poolId, ReservationType reservationRequest) { String reservationKey = String.format("%s::%s", nodeId, channelId); Map<String, Reservation> reservations = getReservationMap(poolId); int maxPoolSize = parameterService.getInt(ParameterConstants.CONCURRENT_WORKERS); long timeout = parameterService.getLong(ParameterConstants.CONCURRENT_RESERVATION_TIMEOUT); removeTimedOutReservations(reservations); if (reservations.size() < maxPoolSize || reservations.containsKey(reservationKey) || whiteList.contains(nodeId)) { Reservation existingReservation = reservations.get(reservationKey); if (existingReservation == null || existingReservation.getType() == ReservationType.SOFT) { reservations.put(reservationKey, new Reservation(reservationKey, reservationRequest == ReservationType.SOFT ? System.currentTimeMillis() + timeout : Long.MAX_VALUE, reservationRequest)); return true; } else { log.warn( "Node '{}' requested a {} connection for the {} channel, but was rejected because it already has one", nodeId, poolId, channelId); return false; } } else { return false; } } public Map<String, Date> getPullReservationsByNodeId() { return getReservationsByNodeId("pull"); } public Map<String, Date> getPushReservationsByNodeId() { return getReservationsByNodeId("push"); } protected Map<String, Date> getReservationsByNodeId(String urlPath) { Map<String, Date> byNodeId = new HashMap<String, Date>(); Set<String> poolIds = activeReservationsByNodeByPool.keySet(); for (String poolId : poolIds) { if (poolId.endsWith(urlPath)) { Map<String, Reservation> reservations = activeReservationsByNodeByPool.get(poolId); Set<String> nodeIds = new HashSet<String>(reservations.keySet()); for (String nodeId : nodeIds) { Reservation reservation = reservations.get(nodeId); if (reservation != null && reservation.getType() == ReservationType.HARD) { byNodeId.put(nodeId, new Date(reservation.getCreateTime())); } } } } return byNodeId; } protected void removeTimedOutReservations(Map<String, Reservation> reservations) { long currentTime = System.currentTimeMillis(); String[] keys = reservations.keySet().toArray(new String[reservations.size()]); if (keys != null) { for (String reservationKey : keys) { Reservation reservation = reservations.get(reservationKey); if (reservation.timeToLiveInMs < currentTime) { reservations.remove(reservationKey); } } } } private Map<String, Reservation> getReservationMap(String poolId) { Map<String, Reservation> reservations = activeReservationsByNodeByPool.get(poolId); if (reservations == null) { reservations = new HashMap<String, Reservation>(); activeReservationsByNodeByPool.put(poolId, reservations); } return reservations; } public static class Reservation { String nodeId; long timeToLiveInMs; long createTime = System.currentTimeMillis(); ReservationType type; public Reservation(String nodeId, long timeToLiveInMs, ReservationType type) { this.nodeId = nodeId; this.timeToLiveInMs = timeToLiveInMs; this.type = type; } @Override public int hashCode() { return nodeId.hashCode(); } @Override public boolean equals(Object obj) { if (obj instanceof Reservation) { return nodeId.equals(((Reservation) obj).nodeId); } else { return false; } } public String getNodeId() { return nodeId; } public long getTimeToLiveInMs() { return timeToLiveInMs; } public long getCreateTime() { return createTime; } public ReservationType getType() { return type; } } public Map<String, Map<String, NodeConnectionStatistics>> getNodeConnectionStatisticsByPoolByNodeId() { return this.nodeConnectionStatistics; } public class NodeConnectionStatistics { int numOfRejections; long totalConnectionCount; long totalConnectionTimeMs; long lastConnectionTimeMs; public int getNumOfRejections() { return numOfRejections; } public long getTotalConnectionCount() { return totalConnectionCount; } public long getTotalConnectionTimeMs() { return totalConnectionTimeMs; } public long getLastConnectionTimeMs() { return lastConnectionTimeMs; } } public Map<String, Map<String, Reservation>> getActiveReservationsByNodeByPool() { return activeReservationsByNodeByPool; } }