/* * Copyright 2015 Evgeny Dolganov (evgenij.dolganov@gmail.com). * * 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 och.comp.chats; import static java.util.Collections.*; import static och.comp.chats.ChatsAccListenerStub.*; import static och.util.Util.*; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; import och.api.exception.chat.NoAvailableOperatorException; import och.api.exception.chat.NoChatException; import och.api.exception.chat.NoPreviousOperatorPositionException; import och.api.exception.chat.NotActiveOperatorException; import och.api.exception.chat.OperatorPositionAlreadyExistsException; import och.api.model.chat.ChatLog; import och.api.model.chat.ChatOperator; import och.api.model.chat.ChatUpdateData; import och.api.model.chat.ChatUser; import och.api.model.chat.Message; import och.api.model.chat.config.AccConfig; import och.api.model.chat.config.AccConfigRead; import och.api.model.chat.config.Key; import och.api.model.client.ClientSession; import och.comp.chats.model.Chat; import och.comp.chats.model.Chat.AddClientCommentRes; import och.comp.chats.model.InitChatData; public class ChatsAcc implements AccConfigRead { private ReentrantReadWriteLock rw = new ReentrantReadWriteLock(); private Lock read = rw.readLock(); private Lock write = rw.writeLock(); //model private HashMap<Long, ChatOperator> operatorsById = new HashMap<>(); private HashSet<Long> activeOperators = new HashSet<>(); private HashMap<String, Chat> chatsById = new HashMap<>(); private HashMap<String, Chat> chatsByClientSession = new HashMap<>(); private HashMap<Long, Set<Chat>> chatsByOperator = new HashMap<>(); private AccConfig config; //external public Date lastScannedToArc; private String id; private ChatsAccListener chatsListener; public ChatsAcc() { this(null, null, null, null); } public ChatsAcc(String id, Collection<ChatOperator> initOperators, AccConfig config, ChatsAccListener chatsListener) { this.id = id; this.chatsListener = chatsListener == null? STUB_INSTANCE : chatsListener; this.config = config != null? config : new AccConfig(); if( ! isEmpty(initOperators)){ for (ChatOperator operator : initOperators) { operatorsById.put(operator.id, operator); } } } public String getId(){ return id; } public void putConfig(Key key, Object val){ AccConfig state; write.lock(); try { config.putVal(key, val); state = config.clone(); }finally { write.unlock(); } chatsListener.onConfigSetted(id, state); } @Override public String getStrVal(Key key) { read.lock(); try { return config.getStrVal(key); }finally { read.unlock(); } } @Override public Boolean getBoolVal(Key key) { read.lock(); try { return config.getBoolVal(key); }finally { read.unlock(); } } @Override public Integer getIntVal(Key key) { read.lock(); try { return config.getIntVal(key); }finally { read.unlock(); } } public void putOperator(ChatOperator operator){ if(operator == null) return; operator = operator.clone(); List<ChatOperator> newState; write.lock(); try { //if new data is not full - try to restore full data from old ChatOperator old = operatorsById.get(operator.id); if(old != null){ if( ! hasText(operator.email)) operator.email = old.email; } operatorsById.put(operator.id, operator); newState = getOperatorsState(); }finally { write.unlock(); } chatsListener.onOperatorsUpdate(id, newState); } public void removeOperator(long operatorId){ List<ChatOperator> newState; write.lock(); try { operatorsById.remove(operatorId); activeOperators.remove(operatorId); chatsByOperator.remove(operatorId); newState = getOperatorsState(); }finally { write.unlock(); } chatsListener.onOperatorsUpdate(id, newState); } public boolean setActiveOperator(long operatorId){ write.lock(); try { if( ! operatorsById.containsKey(operatorId)) return false; activeOperators.add(operatorId); return true; } finally { write.unlock(); } } public boolean isOperator(long opId){ read.lock(); try { return operatorsById.containsKey(opId); } finally { read.unlock(); } } public boolean isActiveOperator(long operatorId){ read.lock(); try { return getActiveOperatorUnsafe(operatorId) != null; } finally { read.unlock(); } } private ChatOperator getActiveOperatorUnsafe(long operatorId){ if(activeOperators.contains(operatorId)){ return operatorsById.get(operatorId); } return null; } public boolean removeActiveOperator(long opId){ write.lock(); try { boolean done = activeOperators.remove(opId); chatsByOperator.remove(opId); return done; } finally { write.unlock(); } } public void putOperatorAndSetActive(ChatOperator op){ putOperator(op); setActiveOperator(op.id); } public int getActiveOperatorsCount(){ read.lock(); try { return activeOperators.size(); }finally { read.unlock(); } } public void checkHasActiveOperators() throws NoAvailableOperatorException { read.lock(); try { checkHasOperators(); } finally { read.unlock(); } } public ChatOperator getOperator(long opId){ read.lock(); try { ChatOperator operator = operatorsById.get(opId); ChatOperator clone = operator == null? null : operator.clone(); return clone; } finally { read.unlock(); } } public List<ChatOperator> getRegistredOperators(){ read.lock(); try { ArrayList<ChatOperator> out = new ArrayList<>(); for (ChatOperator op : operatorsById.values()) { ChatOperator clone = op.clone(); out.add(clone); } return out; } finally { read.unlock(); } } public InitChatData initActiveChat(ClientSession client) throws NoAvailableOperatorException { String sessionId = client.sessionId; boolean isNew = false; //read-write pattern read.lock(); Chat chat = chatsByClientSession.get(sessionId); if(chat == null){ read.unlock(); write.lock(); try { chat = chatsByClientSession.get(sessionId); if(chat == null){ checkHasOperators(); chatsListener.checkCanCreateChat(id, client); Chat newChat = new Chat(client, chatsListener); chatsById.put(newChat.id, newChat); chatsByClientSession.put(sessionId, newChat); chat = newChat; isNew = true; chatsListener.onChatCreated(id, newChat, client); } read.lock(); }finally { write.unlock(); } } try { checkHasOperators(); return new InitChatData(chat.id, isNew); }finally { read.unlock(); } } public ChatLog initAngGetActiveChat(ClientSession client) throws NoAvailableOperatorException { String chatId = initActiveChat(client).id; return getActiveChatById(chatId); } public ChatLog getActiveChat(ClientSession client){ return getActiveChat(client, null); } public ChatLog getActiveChat(ClientSession client, ChatUpdateData updateData){ Chat chat = null; String sessionId = client.sessionId; read.lock(); try { chat = chatsByClientSession.get(sessionId); }finally{ read.unlock(); } return chat == null? null : chat.toLog(updateData); } public ChatLog getActiveChatById(String id){ Chat chat = null; read.lock(); try { chat = chatsById.get(id); }finally{ read.unlock(); } return chat == null? null : chat.toLog(); } public ChatOperator addOperator(String chatId, long operatorId, int operatorIndex) throws NotActiveOperatorException, NoChatException, OperatorPositionAlreadyExistsException, NoPreviousOperatorPositionException { write.lock(); try { ChatOperator operator = getActiveOperatorUnsafe(operatorId); if( operator == null) throw new NotActiveOperatorException(operatorId); Chat chat = chatsById.get(chatId); if(chat == null) throw new NoChatException(); int operatorsCount = chat.getOperatorsCount(); if(operatorIndex < operatorsCount) throw new OperatorPositionAlreadyExistsException(operatorsCount); if(operatorIndex > operatorsCount) throw new NoPreviousOperatorPositionException(operatorsCount); chat.addOperator(operator); putToSetMap(chatsByOperator, operator.id, chat); ChatOperator clone = operator.clone(); chatsListener.onChatOperatorAdded(id, chat, operator); return clone; }finally{ write.unlock(); } } public boolean updateOperatorContact(long operatorId, String email) { List<ChatOperator> newState; write.lock(); try { ChatOperator operator = operatorsById.get(operatorId); if(operator == null) return false; operator.email = email; newState = getOperatorsState(); }finally{ write.unlock(); } chatsListener.onOperatorsUpdate(id, newState); return true; } public Collection<ChatLog> getActiveChatsByOperator(long operatorId){ ArrayList<Chat> chats = null; read.lock(); try { Set<Chat> set = chatsByOperator.get(operatorId); chats = set == null? null : new ArrayList<>(set); }finally{ read.unlock(); } if(chats == null) return null; ArrayList<ChatLog> out = new ArrayList<>(); for(Chat chat : chats) out.add(chat.toLog()); return out; } public Collection<ChatLog> getAllActiveChats(){ return getAllActiveChats(null); } public Collection<ChatLog> getAllActiveChats(Map<String, ChatUpdateData> fromIndexes){ if(isEmpty(fromIndexes)) fromIndexes = emptyMap(); ArrayList<Chat> chats = null; read.lock(); try { chats = new ArrayList<>(chatsById.values()); }finally{ read.unlock(); } ArrayList<ChatLog> out = new ArrayList<>(); for(Chat chat : chats) { ChatUpdateData updateData = fromIndexes.get(chat.id); out.add(chat.toLog(updateData)); } return out; } public ChatLog getActiveChat(String chatId, ChatUpdateData updateData){ Chat chat = null; read.lock(); try { chat = chatsById.get(chatId); }finally{ read.unlock(); } if(chat == null) return null; return chat.toLog(updateData); } public String closeChat(ClientSession client){ return closeChat(client.sessionId); } public String closeChat(String sessionId){ Chat chat = null; write.lock(); try { chat = chatsByClientSession.remove(sessionId); if(chat == null) return null; chatsById.remove(chat.id); for(Set<Chat> set : chatsByOperator.values()) set.remove(chat); }finally { write.unlock(); } chatsListener.onChatClose(id, chat); return chat.id; } public void closeAllChats() { Set<String> allSessions; write.lock(); try { allSessions = new HashSet<>(chatsByClientSession.keySet()); }finally { write.unlock(); } for (String sessionId : allSessions) { closeChat(sessionId); } } public boolean addComment(ClientSession client, String text){ String sessionId = client.sessionId; Chat chat = null; read.lock(); try { chat = chatsByClientSession.get(sessionId); }finally { read.unlock(); } if(chat == null) return false; AddClientCommentRes result = chat.addComment(client, text); if(result != null) chatsListener.onChatClientMsgAdded(id, chat, result); return result != null; } public boolean addComment(String chatId, long operatorId, String text){ Chat chat = null; ChatOperator operator = null; read.lock(); try { chat = chatsById.get(chatId); operator = operatorsById.get(operatorId); }finally { read.unlock(); } if(chat == null || operator == null) return false; Message result = chat.addComment(operator, text); if(result != null) chatsListener.onChatOperatorMsgAdded(id, chat, result); return result != null; } public <T extends ChatLog> Collection<T> fillOperators(Collection<T> col) { if(isEmpty(col)) return col; for (ChatLog log : col) { fillOperators(log); } return col; } public ChatLog fillOperators(ChatLog chatLog) { if(chatLog == null) return null; if( ! chatLog.hasUpdates()) return chatLog; read.lock(); try { HashMap<Long, String> operators = new HashMap<>(); List<ChatUser> users = chatLog.users; for (ChatUser user : users) { Long operatorId = user.operatorId; if(operatorId == null) continue; if(operators.containsKey(operatorId)) continue; ChatOperator operator = operatorsById.get(operatorId); if(operator == null) continue; operators.put(operatorId, operator.name); } if(operators.size() > 0){ chatLog.operators = operators; } return chatLog; }finally { read.unlock(); } } public boolean addChat(ClientSession client, Chat chat) { write.lock(); try { if(chatsById.containsKey(chat.id)) return false; String sessionId = client.sessionId; Chat curChat = chatsByClientSession.get(sessionId); if(curChat != null) return false; chatsByClientSession.put(sessionId, chat); chatsById.put(chat.id, chat); List<ChatUser> ops = chat.getOperators(); for(ChatUser op : ops){ Long opId = op.operatorId; if( ! operatorsById.containsKey(opId)) continue; putToSetMap(chatsByOperator, opId, chat); } return true; }finally{ write.unlock(); } } public boolean closeUnknownChat(Chat chat){ read.lock(); try { if(chatsById.containsKey(chat.id)) return false; }finally{ read.unlock(); } chatsListener.onChatClose(id, chat); return true; } private void checkHasOperators(){ if(activeOperators.size() == 0) throw new NoAvailableOperatorException(); } private List<ChatOperator> getOperatorsState(){ ArrayList<ChatOperator> out = new ArrayList<>(); Collection<ChatOperator> values = operatorsById.values(); for (ChatOperator operator : values) { ChatOperator clone = operator.clone(); out.add(clone); } return out; } }