/**
* 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;
}
}