/* * Copyright (c) 2008-2012, Hazel Bilisim Ltd. All Rights Reserved. * * 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 com.hazelcast.impl; import com.hazelcast.core.*; import com.hazelcast.nio.Connection; import com.hazelcast.nio.ConnectionListener; import com.hazelcast.nio.Data; import com.hazelcast.nio.Packet; import com.hazelcast.util.ConcurrentHashSet; import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; import java.net.SocketAddress; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import static com.hazelcast.nio.IOUtil.toData; public class ClientEndpoint implements EntryListener, InstanceListener, MembershipListener, ConnectionListener, ClientHandlerService.ClientListener, Client { final Connection conn; final Map<Integer, CallContext> callContexts = new HashMap<Integer, CallContext>(100); final Map<ITopic, MessageListener<Object>> messageListeners = new HashMap<ITopic, MessageListener<Object>>(); final List<IMap> listeningMaps = new ArrayList<IMap>(); final List<MultiMap> listeningMultiMaps = new ArrayList<MultiMap>(); final List<Map.Entry<IMap, Object>> listeningKeysOfMaps = new ArrayList<Map.Entry<IMap, Object>>(); final List<Map.Entry<MultiMap, Object>> listeningKeysOfMultiMaps = new ArrayList<Map.Entry<MultiMap, Object>>(); final Map<IQueue, ItemListener<Object>> queueItemListeners = new ConcurrentHashMap<IQueue, ItemListener<Object>>(); final Map<Long, DistributedTask> runningExecutorTasks = new ConcurrentHashMap<Long, DistributedTask>(); final ConcurrentHashSet<ClientRequestHandler> currentRequests = new ConcurrentHashSet<ClientRequestHandler>(); final Node node; final Map<String, AtomicInteger> attachedSemaphorePermits = new ConcurrentHashMap<String, AtomicInteger>(); volatile boolean authenticated = false; LoginContext loginContext = null; ClientEndpoint(Node node, Connection conn) { this.node = node; this.conn = conn; } public CallContext getCallContext(int threadId) { CallContext context = callContexts.get(threadId); if (context == null) { int locallyMappedThreadId = ThreadContext.get().createNewThreadId(); context = new CallContext(locallyMappedThreadId, true); callContexts.put(threadId, context); } return context; } public synchronized void addThisAsListener(IMap map, Data key, boolean includeValue) { if (!listeningMaps.contains(map) && !(listeningKeyExist(map, key))) { map.addEntryListener(this, includeValue); } if (key == null) { listeningMaps.add(map); } else { listeningKeysOfMaps.add(new Entry(map, key)); } } public synchronized void removeThisListener(IMap map, Data key) { List<Map.Entry<IMap, Object>> entriesToRemove = new ArrayList<Map.Entry<IMap, Object>>(); if (key == null) { listeningMaps.remove(map); } else { for (Map.Entry<IMap, Object> entry : listeningKeysOfMaps) { if (entry.getKey().equals(map) && entry.getValue().equals(key)) { entriesToRemove.add(entry); break; } } } listeningKeysOfMaps.removeAll(entriesToRemove); if (!listeningMaps.contains(map) && !(listeningKeyExist(map, key))) { map.removeEntryListener(this); } } public synchronized void addThisAsListener(MultiMap<Object, Object> multiMap, Data key, boolean includeValue) { if (!listeningMultiMaps.contains(multiMap) && !(listeningKeyExist(multiMap, key))) { multiMap.addEntryListener(this, includeValue); } if (key == null) { listeningMultiMaps.add(multiMap); } else { listeningKeysOfMultiMaps.add(new Entry(multiMap, key)); } } public synchronized void removeThisListener(MultiMap multiMap, Data key) { List<Map.Entry<MultiMap, Object>> entriesToRemove = new ArrayList<Map.Entry<MultiMap, Object>>(); if (key == null) { listeningMultiMaps.remove(multiMap); } else { for (Map.Entry<MultiMap, Object> entry : listeningKeysOfMultiMaps) { if (entry.getKey().equals(multiMap) && entry.getValue().equals(key)) { entriesToRemove.add(entry); break; } } } listeningKeysOfMultiMaps.removeAll(entriesToRemove); if (!listeningMultiMaps.contains(multiMap) && !(listeningKeyExist(multiMap, key))) { multiMap.removeEntryListener(this); } } private boolean listeningKeyExist(IMap map, Object key) { for (Map.Entry<IMap, Object> entry : listeningKeysOfMaps) { if (entry.getKey().equals(map) && (key == null || entry.getValue().equals(key))) { return true; } } return false; } private boolean listeningKeyExist(MultiMap map, Object key) { for (Map.Entry<MultiMap, Object> entry : listeningKeysOfMultiMaps) { if (entry.getKey().equals(map) && (key == null || entry.getValue().equals(key))) { return true; } } return false; } @Override public int hashCode() { return this.conn.hashCode(); } public void entryAdded(EntryEvent event) { processEvent(event); } public void entryEvicted(EntryEvent event) { processEvent(event); } public void entryRemoved(EntryEvent event) { processEvent(event); } public void entryUpdated(EntryEvent event) { processEvent(event); } public void instanceCreated(InstanceEvent event) { processEvent(event); } public void instanceDestroyed(InstanceEvent event) { processEvent(event); } public void memberAdded(MembershipEvent membershipEvent) { processEvent(membershipEvent); } public void memberRemoved(MembershipEvent membershipEvent) { processEvent(membershipEvent); } private void processEvent(MembershipEvent membershipEvent) { Packet packet = createMembershipEventPacket(membershipEvent); sendPacket(packet); } private void processEvent(InstanceEvent event) { Packet packet = createInstanceEventPacket(event); sendPacket(packet); } /** * if a client is listening for both key and the entire * map, then we should make sure that we don't send * two separate events. One is enough. so check * if we already sent one. * <p/> * called by executor service threads * * @param event */ private void processEvent(EntryEvent event) { Packet packet = createEntryEventPacket(event); sendPacket(packet); } void sendPacket(Packet packet) { if (conn != null && conn.live()) { conn.getWriteHandler().enqueueSocketWritable(packet); } } Packet createEntryEventPacket(EntryEvent event) { Packet packet = new Packet(); DataAwareEntryEvent dataAwareEntryEvent = (DataAwareEntryEvent) event; Data valueEvent = null; if (dataAwareEntryEvent.getNewValueData() != null) { Keys keys = new Keys(); keys.add(dataAwareEntryEvent.getNewValueData()); keys.add(dataAwareEntryEvent.getOldValueData()); valueEvent = toData(keys); } String name = dataAwareEntryEvent.getLongName(); if (name.startsWith(Prefix.MAP_OF_LIST)) { name = name.substring(Prefix.MAP_FOR_QUEUE.length()); valueEvent = ((DataAwareEntryEvent) event).getNewValueData(); } packet.set(name, ClusterOperation.EVENT, dataAwareEntryEvent.getKeyData(), valueEvent); packet.longValue = event.getEventType().getType(); return packet; } Packet createInstanceEventPacket(InstanceEvent event) { Packet packet = new Packet(); packet.set(null, ClusterOperation.EVENT, toData(event.getInstance().getId()), toData(event.getEventType().getId())); return packet; } Packet createMembershipEventPacket(MembershipEvent membershipEvent) { Packet packet = new Packet(); packet.set(null, ClusterOperation.EVENT, toData(membershipEvent.getMember()), toData(membershipEvent.getEventType())); return packet; } public void connectionAdded(Connection connection) { } public void connectionRemoved(Connection connection) { LifecycleServiceImpl lifecycleService = (LifecycleServiceImpl) node.factory.getLifecycleService(); if (connection.equals(this.conn) && !lifecycleService.paused.get()) { destroyEndpointThreads(); rollbackTransactions(); removeEntryListeners(); removeEntryListenersWithKey(); removeMessageListeners(); cancelRunningOperations(); releaseAttachedSemaphorePermits(); node.clusterManager.sendProcessableToAll(new ClientHandlerService.CountDownLatchLeave(conn.getEndPoint()), true); node.clientService.remove(this); } } private void destroyEndpointThreads() { Set<Integer> threadIds = new HashSet<Integer>(callContexts.size()); for (CallContext callContext : callContexts.values()) { threadIds.add(callContext.getThreadId()); } Set<Member> allMembers = node.getClusterImpl().getMembers(); MultiTask task = new MultiTask(new DestroyEndpointThreadsCallable(node.getThisAddress(), threadIds), allMembers); node.factory.getExecutorService().execute(task); } private void cancelRunningOperations() { for (ClientRequestHandler clientRequestHandler : currentRequests) { clientRequestHandler.cancel(); } currentRequests.clear(); } private void rollbackTransactions() { for (CallContext callContext : callContexts.values()) { ThreadContext.get().setCallContext(callContext); if (callContext.getTransaction() != null && callContext.getTransaction().getStatus() == Transaction.TXN_STATUS_ACTIVE) { callContext.getTransaction().rollback(); } } } private void removeMessageListeners() { for (ITopic topic : messageListeners.keySet()) { topic.removeMessageListener(messageListeners.get(topic)); } } private void removeEntryListenersWithKey() { for (Map.Entry e : listeningKeysOfMaps) { IMap m = (IMap) e.getKey(); m.removeEntryListener(this, e.getValue()); } } private void removeEntryListeners() { for (IMap map : listeningMaps) { map.removeEntryListener(this); } } private void releaseAttachedSemaphorePermits() { for (Map.Entry<String, AtomicInteger> entry : attachedSemaphorePermits.entrySet()) { final ISemaphore semaphore = node.factory.getSemaphore(entry.getKey()); final int permits = entry.getValue().get(); if (permits > 0) { semaphore.releaseDetach(permits); } else { semaphore.reducePermits(permits); semaphore.attach(permits); } } } public void attachDetachPermits(String name, int permits) { if (attachedSemaphorePermits.containsKey(name)) { attachedSemaphorePermits.get(name).addAndGet(permits); } else { attachedSemaphorePermits.put(name, new AtomicInteger(permits)); } } public void storeTask(long callId, DistributedTask task) { this.runningExecutorTasks.put(callId, task); } public void removeTask(long callId) { this.runningExecutorTasks.remove(callId); } public DistributedTask getTask(long taskId) { return this.runningExecutorTasks.get(taskId); } public void addRequest(ClientRequestHandler clientRequestHandler) { this.currentRequests.add(clientRequestHandler); } public void removeRequest(ClientRequestHandler clientRequestHandler) { this.currentRequests.remove(clientRequestHandler); } public void setLoginContext(LoginContext loginContext) { this.loginContext = loginContext; } public LoginContext getLoginContext() { return loginContext; } public Subject getSubject() { return loginContext != null ? loginContext.getSubject() : null; } public void authenticated() { this.authenticated = true; node.clientService.add(this); } public boolean isAuthenticated() { return authenticated; } public SocketAddress getSocketAddress() { return conn.getSocketChannelWrapper().socket().getRemoteSocketAddress(); } public ClientType getClientType() { return ClientType.Native; } static class Entry implements Map.Entry { Object key; Object value; Entry(Object k, Object v) { key = k; value = v; } public Object getKey() { return key; } public Object getValue() { return value; } public Object setValue(Object value) { Object r = key; key = value; return r; } } }