/*
* Copyright 2000-2006 JetBrains s.r.o.
*
* 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 jetbrains.communicator.p2p;
import com.intellij.util.ArrayUtil;
import jetbrains.communicator.core.*;
import jetbrains.communicator.core.commands.NamedUserCommand;
import jetbrains.communicator.core.dispatcher.AsyncMessageDispatcher;
import jetbrains.communicator.core.dispatcher.Message;
import jetbrains.communicator.core.transport.Transport;
import jetbrains.communicator.core.transport.WasAddedXmlMessage;
import jetbrains.communicator.core.transport.XmlMessage;
import jetbrains.communicator.core.users.*;
import jetbrains.communicator.ide.IDEFacade;
import jetbrains.communicator.ide.NullProgressIndicator;
import jetbrains.communicator.ide.ProgressIndicator;
import jetbrains.communicator.p2p.commands.AddOnlineUserP2PCommand;
import jetbrains.communicator.p2p.commands.SendXmlMessageP2PCommand;
import jetbrains.communicator.util.StringUtil;
import jetbrains.communicator.util.UIUtil;
import jetbrains.communicator.util.WaitFor;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.picocontainer.Disposable;
import org.picocontainer.MutablePicoContainer;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.util.*;
/**
* @author Kir Maximov
*/
@SuppressWarnings({"HardCodedStringLiteral"})
public class P2PTransport implements Transport, UserMonitorClient, Disposable {
private static final Logger LOG = Logger.getLogger(P2PTransport.class);
static final String CODE = "P2P";
static final int XML_RPC_PORT = MulticastPingThread.MULTICAST_PORT + 1;
private int myPort;
private P2PServer myP2PServer;
private UserMonitorThread myUserMonitorThread;
private final Map<User,OnlineUserInfo> myUser2Info = new HashMap<User, OnlineUserInfo>();
private final Map<User,OnlineUserInfo> myUser2InfoNew = new HashMap<User, OnlineUserInfo>();
private final Collection<User> myOnlineUsers =
Collections.synchronizedCollection(new HashSet<User>());
private final EventBroadcaster myEventBroadcaster;
private final IDEtalkListener myUserAddedCallbackListener;
private final AsyncMessageDispatcher myAsyncMessageDispatcher;
private final UserModel myUserModel;
private UserPresence myOwnPresence;
public P2PTransport(AsyncMessageDispatcher asyncMessageDispatcher, UserModel userModel) throws IOException {
this(asyncMessageDispatcher, userModel, UserMonitorThread.WAIT_USER_RESPONSES_TIMEOUT);
}
public P2PTransport(AsyncMessageDispatcher asyncMessageDispatcher, UserModel userModel, long waitUserResponsesTimeout) throws IOException {
myEventBroadcaster = userModel.getBroadcaster();
myAsyncMessageDispatcher = asyncMessageDispatcher;
myUserModel = userModel;
myUserAddedCallbackListener = new TransportUserListener(this) {
protected void processAfterChange(UserEvent event) {
event.accept(new EventVisitor() {
@Override public void visitUserAdded(UserEvent.Added event) {
super.visitUserAdded(event);
sendUserAddedCallback(event.getUser());
}
});
}
};
myOwnPresence = new UserPresence(true);
startup(waitUserResponsesTimeout);
Runtime.getRuntime().addShutdownHook(new IDETalkShutdownHook());
}
class IDETalkShutdownHook extends Thread {
public IDETalkShutdownHook() {
super("IDE Talk shutdown hook");
setDaemon(true);
}
@Override
public void run() {
dispose();
}
}
private void initializeXmlRpcPort() {
int port = XML_RPC_PORT;
if (NetworkUtil.isPortBusy(port)) {
ServerSocket socket = null;
try {
socket = new ServerSocket(0);
port = socket.getLocalPort();
} catch (IOException e) {
final String msg = "Unable to get free port for IDEtalk: " + e.getMessage();
LOG.warn(msg);
LOG.debug(msg, e);
port = -1;
}
finally {
try { if (socket != null) socket.close(); } catch (IOException e) { }
}
}
myPort = port;
}
private void startup(long waitUserResponsesTimeout) throws IOException {
myUserMonitorThread = new UserMonitorThread(this, waitUserResponsesTimeout);
initializeXmlRpcPort();
if (myPort >= 0) {
myP2PServer = new P2PServer(myPort, new P2PCommand[] {
new SendXmlMessageP2PCommand(myEventBroadcaster, this),
new AddOnlineUserP2PCommand(myUserMonitorThread),
});
LOG.info("Internal Web server is bound to port " + myPort);
myUserMonitorThread.start();
myUserMonitorThread.triggerFindNow();
new WaitFor() { protected boolean condition() { return myUserMonitorThread.isRunning(); } };
myEventBroadcaster.addListener(myUserAddedCallbackListener);
}
}
IDEFacade getIdeFacade() {
return myAsyncMessageDispatcher.getIdeFacade();
}
public void dispose() {
try {
myEventBroadcaster.removeListener(myUserAddedCallbackListener);
myUserMonitorThread.shutdown();
}
catch (Throwable e) {
LOG.info(e);
}
final P2PServer p2PServer = myP2PServer;
if (p2PServer != null) {
try {
p2PServer.shutdown();
}
catch (Throwable e) {
LOG.info(e);
}
}
myOnlineUsers.clear();
}
public void initializeProject(final String projectName, MutablePicoContainer projectLevelContainer) {
getIdeFacade().runOnPooledThread(new Runnable() {
public void run() {
User[] users = findUsers(new NullProgressIndicator());
Set<User> ourUsers = new HashSet<User>();
for (User user : users) {
if (Arrays.asList(user.getProjects()).contains(projectName)) {
ourUsers.add(user);
}
}
if (canAddUsers(projectName, ourUsers)) {
for (User user : ourUsers) {
if (!myUserModel.hasUser(user)) {
user.setGroup(projectName, myUserModel);
myUserModel.addUser(user);
}
}
}
}
});
}
boolean canAddUsers(String projectName, Collection<User> users) {
if (users.size() == 0) return false;
return hasGroup(projectName) || hasNoUsersFrom(users);
}
private boolean hasNoUsersFrom(Collection<User> users) {
for (User user : users) {
if (myUserModel.hasUser(user)) {
return false;
}
}
return true;
}
private boolean hasGroup(String projectName) {
return Arrays.asList(myUserModel.getGroups()).contains(projectName);
}
public Class<? extends NamedUserCommand> getSpecificFinderClass() {
return null;
}
public String getName() {
return CODE;
}
public User[] findUsers(ProgressIndicator progressIndicator) {
myUserMonitorThread.findNow(progressIndicator);
return myOnlineUsers.toArray(new User[myOnlineUsers.size()]);
}
public boolean isOnline() {
return myOwnPresence.isOnline();
}
public boolean hasIDEtalkClient(User user) {
return true;
}
public UserPresence getUserPresence(User user) {
boolean online = myOnlineUsers.contains(user);
if (online) {
return getNotNullOnlineInfo(user).getPresence();
}
return new UserPresence(false);
}
public InetAddress getAddress(User p2PUser) {
return getNotNullOnlineInfo(p2PUser).getAddress();
}
public boolean isSelf(User user) {
InetAddress address = getAddress(user);
return StringUtil.getMyUsername().equals(user.getName()) && NetworkUtil.isOwnAddress(address);
}
public String getIconPath(UserPresence userPresence) {
return UIUtil.getIcon(userPresence, "/ideTalk/user.png", "/ideTalk/user_dnd.png");
}
public int getPort(User user) {
return getNotNullOnlineInfo(user).getPort();
}
public String[] getProjects(User user) {
Collection<String> projects = getNotNullOnlineInfo(user).getProjects();
return ArrayUtil.toStringArray(projects);
}
@NotNull
private OnlineUserInfo getNotNullOnlineInfo(User user) {
OnlineUserInfo result = myUser2Info.get(user);
if (result == null) {
result = new OnlineUserInfo(null, -1, new HashSet<String>(), new UserPresence(false));
}
return result;
}
public String getAddressString(User user) {
return getAddress(user).getHostAddress() + ':' + getPort(user);
}
public void sendXmlMessage(User user, XmlMessage message) {
Message msg = SendXmlMessageP2PCommand.createNetworkMessage(message);
if (message.needsResponse()) {
myAsyncMessageDispatcher.sendNow(user, msg);
}
else {
myAsyncMessageDispatcher.sendLater(user, msg);
}
}
public UserPresence getOwnPresence() {
return myOwnPresence;
}
public void setOwnPresence(UserPresence userPresence) {
if (!myOwnPresence.isOnline() && userPresence.isOnline()) {
try {
startup(myUserMonitorThread.getWaitUserResponsesTimeout());
} catch (IOException e) {
LOG.error(e.getMessage() + " server port " + myPort, e);
}
} else if (myOwnPresence.isOnline() && !userPresence.isOnline()) {
dispose();
}
if (selfBecomeAvailable(userPresence)) {
notifyUsersAboutOnlineImmediately();
}
myOwnPresence = userPresence;
}
private boolean selfBecomeAvailable(UserPresence userPresence) {
return
myOwnPresence.getPresenceMode() != PresenceMode.AVAILABLE &&
userPresence.getPresenceMode() == PresenceMode.AVAILABLE;
}
private void notifyUsersAboutOnlineImmediately() {
for (User user : myUserModel.getAllUsers()) {
if (user.getTransportCode().equals(getName())) {
user.sendXmlMessage(new BecomeAvailableXmlMessage());
}
}
}
public synchronized void setOnlineUsers(Collection<User> onlineUsers) {
assert onlineUsers != null;
removeOfflineUsers_And_UpdateOldOnlineUsers(onlineUsers);
addNewOnlineUsers(onlineUsers);
myUser2InfoNew.clear();
}
public void setAvailable(String remoteUser) {
final User user = myUserModel.findUser(remoteUser, getName());
if (user != null) {
UserPresence oldPresence = getNotNullOnlineInfo(user).getPresence();
final UserPresence newPresence = new UserPresence(true);
myEventBroadcaster.doChange(new UserEvent.Updated(user, "presence", oldPresence, newPresence), new Runnable() {
public void run() {
makeUserOnline(user);
}
});
}
}
private void makeUserOnline(User user) {
OnlineUserInfo onlineInfo = getNotNullOnlineInfo(user);
onlineInfo.setPresence(new UserPresence(true));
myUser2Info.put(user, onlineInfo);
}
public synchronized User createUser(String remoteUsername, @NotNull OnlineUserInfo onlineUserInfo) {
User user = myUserModel.createUser(remoteUsername, CODE);
myUser2InfoNew.put(user, onlineUserInfo);
return user;
}
void flushCurrentUsers() {
myUserMonitorThread.flushOnlineUsers();
}
UserMonitorThread getUserMonitorThread() {
return myUserMonitorThread;
}
protected void sendUserAddedCallback(User user) {
sendXmlMessage(user, new WasAddedXmlMessage());
}
public int getPort() {
return myPort;
}
private void addNewOnlineUsers(Collection<User> onlineUsers) {
for (final User user : onlineUsers) {
if (!myOnlineUsers.contains(user) && myUser2InfoNew.containsKey(user)) {
myEventBroadcaster.doChange(new UserEvent.Online(user), new Runnable() {
public void run() {
myOnlineUsers.add(user);
myUser2Info.put(user, myUser2InfoNew.get(user));
}
});
}
}
}
private void removeOfflineUsers_And_UpdateOldOnlineUsers(Collection onlineUsers) {
for (final Iterator<User> it = myOnlineUsers.iterator(); it.hasNext();) {
final User user = it.next();
if (!onlineUsers.contains(user)) { // User was removed
myEventBroadcaster.doChange(new UserEvent.Offline(user), new Runnable() {
public void run() {
it.remove();
myUser2Info.remove(user);
}
});
}
else { // User already exists
UserPresence oldPresence = getNotNullOnlineInfo(user).getPresence();
final OnlineUserInfo onlineUserInfo = myUser2InfoNew.get(user);
if (onlineUserInfo == null) return;
UserPresence newPresence = onlineUserInfo.getPresence();
if (!newPresence.equals(oldPresence)) {
myEventBroadcaster.doChange(new UserEvent.Updated(user, "presence", oldPresence, newPresence), new Runnable() {
public void run() {
myUser2Info.put(user, onlineUserInfo);
}
});
}
else {
myUser2Info.put(user, onlineUserInfo);
}
}
}
}
public static P2PTransport getInstance() {
return (P2PTransport) Pico.getInstance().getComponentInstanceOfType(P2PTransport.class);
}
}