/* * Copyright 2014-2016 CyberVision, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * 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.kaaproject.kaa.server.bootstrap.service; import static org.kaaproject.kaa.server.common.zk.ServerNameUtil.getNameFromConnectionInfo; import org.kaaproject.kaa.server.common.zk.ServerNameUtil; import org.kaaproject.kaa.server.common.zk.bootstrap.BootstrapNode; import org.kaaproject.kaa.server.common.zk.gen.OperationsNodeInfo; import org.kaaproject.kaa.server.common.zk.gen.TransportMetaData; import org.kaaproject.kaa.server.common.zk.gen.VersionConnectionInfoPair; import org.kaaproject.kaa.server.common.zk.operations.OperationsNodeListener; import org.kaaproject.kaa.server.sync.bootstrap.ProtocolConnectionData; import org.kaaproject.kaa.server.sync.bootstrap.ProtocolVersionId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; /** * OperationsServerListService Class. Receive new Operations Servers list form * Thrift service and create AVRP object EndPointServerList * * @author Andrey Panasenko * @author Andrey Shvayka */ @Service public class DefaultOperationsServerListService implements OperationsServerListService, OperationsNodeListener { private static final Logger LOG = LoggerFactory.getLogger(DefaultOperationsServerListService.class); private Map<String, OperationsNodeInfo> opsMap; private Memorizer<List<ProtocolVersionId>, Set<ProtocolConnectionData>> cache; private Object listenerLock = new Object(); /** * Default constructor. */ public DefaultOperationsServerListService() { opsMap = new ConcurrentHashMap<String, OperationsNodeInfo>(); cache = new Memorizer<List<ProtocolVersionId>, Set<ProtocolConnectionData>>( new Computable<List<ProtocolVersionId>, Set<ProtocolConnectionData>>() { @Override public Set<ProtocolConnectionData> compute(List<ProtocolVersionId> protocolVersions) throws InterruptedException { return filterProtocolInstances(protocolVersions); } }); } /** * Bean init-method. * * @param zkNode the bootstrap node */ public void init(BootstrapNode zkNode) { LOG.info("Initializing with {}", zkNode); opsMap.clear(); synchronized (listenerLock) { LOG.info("Registering as listener to ZK updates"); zkNode.addListener(this); LOG.info("Adding existing nodes"); for (OperationsNodeInfo info : zkNode.getCurrentOperationServerNodes()) { addNode(info); } LOG.info("Added existing nodes"); } } @Override public Set<ProtocolConnectionData> filter(List<ProtocolVersionId> keys) { try { return cache.compute(keys); } catch (InterruptedException ex) { LOG.info("Failed to filter protocols", ex); throw new RuntimeException(ex); } } @Override public void onNodeAdded(OperationsNodeInfo nodeInfo) { synchronized (listenerLock) { addNode(nodeInfo); } } @Override public void onNodeUpdated(OperationsNodeInfo nodeInfo) { synchronized (listenerLock) { addNode(nodeInfo); } } @Override public void onNodeRemoved(OperationsNodeInfo nodeInfo) { synchronized (listenerLock) { removeNode(nodeInfo); } } private void addNode(OperationsNodeInfo info) { LOG.info("Add/Update node {}", info); opsMap.put(getNameFromConnectionInfo(info.getConnectionInfo()), info); LOG.info("Cleanup cached responses"); cache.clear(); } private void removeNode(OperationsNodeInfo info) { if (opsMap.remove(getNameFromConnectionInfo(info.getConnectionInfo())) != null) { LOG.info("Removed node {}", info); } else { LOG.warn("Failed to remove node {}", info); } LOG.info("Cleanup cached responses"); cache.clear(); } protected Set<ProtocolConnectionData> filterProtocolInstances(List<ProtocolVersionId> keys) { Set<ProtocolConnectionData> result = new HashSet<>(); for (ProtocolVersionId key : keys) { for (OperationsNodeInfo node : opsMap.values()) { for (TransportMetaData md : node.getTransports()) { if (md.getId() == key.getProtocolId() && md.getMinSupportedVersion() <= key.getVersion() && key.getVersion() <= md.getMaxSupportedVersion()) { result.addAll(toProtocolConnectionData(node, md, key.getVersion())); } } } } return result; } private Set<ProtocolConnectionData> toProtocolConnectionData(OperationsNodeInfo node, TransportMetaData md, int version) { byte[] connectionData = null; Set<ProtocolConnectionData> result = new HashSet<>(); for (VersionConnectionInfoPair pair : md.getConnectionInfo()) { if (version == pair.getVersion()) { result.add(new ProtocolConnectionData(ServerNameUtil.crc32(node.getConnectionInfo()), new ProtocolVersionId(md.getId(), version), pair.getConenctionInfo().array()) ); } } return result; } public interface Computable<A, V> { V compute(A arg) throws InterruptedException; } public class Memorizer<A, V> implements Computable<A, V> { private final ConcurrentMap<A, Future<V>> cache = new ConcurrentHashMap<>(); private final Computable<A, V> computable; public Memorizer(Computable<A, V> computable) { this.computable = computable; } /** * Compute argument by field <code>compute</code>. * * @param arg the argument * @return computing result */ public V compute(final A arg) throws InterruptedException { while (true) { Future<V> future = cache.get(arg); if (future == null) { Callable<V> eval = new Callable<V>() { public V call() throws InterruptedException { return computable.compute(arg); } }; FutureTask<V> ft = new FutureTask<V>(eval); future = cache.putIfAbsent(arg, ft); if (future == null) { future = ft; ft.run(); } } try { return future.get(); } catch (CancellationException ex) { LOG.error("Cancellation exception exception ", ex); cache.remove(arg, future); } catch (ExecutionException ex) { LOG.error("Cache execution exception ", ex); } } } public void clear() { cache.clear(); } } }