/*
* Copyright 2004 - 2008 Christian Sprajc. All rights reserved.
*
* This file is part of PowerFolder.
*
* PowerFolder is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation.
*
* PowerFolder is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PowerFolder. If not, see <http://www.gnu.org/licenses/>.
*
* $Id$
*/
package de.dal33t.powerfolder.light;
import java.io.IOException;
import java.io.InvalidClassException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.net.InetSocketAddress;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import de.dal33t.powerfolder.ConfigurationEntry;
import de.dal33t.powerfolder.Constants;
import de.dal33t.powerfolder.Controller;
import de.dal33t.powerfolder.Member;
import de.dal33t.powerfolder.util.ExternalizableUtil;
import de.dal33t.powerfolder.util.Reject;
import de.dal33t.powerfolder.util.Util;
import de.dal33t.powerfolder.util.db.InetSocketAddressUserType;
import de.dal33t.powerfolder.util.intern.Internalizer;
import de.dal33t.powerfolder.util.net.NetworkUtil;
/**
* Member information class. contains all important informations about a member
*
* @author <a href="mailto:totmacher@powerfolder.com">Christian Sprajc </a>
* @version $Revision: 1.13 $
*/
@TypeDef(name = "socketAddressType", typeClass = InetSocketAddressUserType.class)
@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class MemberInfo implements Serializable {
private static final long serialVersionUID = 100L;
public static Internalizer<MemberInfo> INTERNALIZER;
public static final String PROPERTYNAME_NICK = "nick";
public static final String PROPERTYNAME_ID = "id";
public static final String PROPERTYNAME_CONNECT_ADDRESS = "connectAddress";
// some identification marks
public String nick;
// The world wide unique id / logical address
@Id
public String id;
/**
* The network Id of this node. #1373
*
* @see ConfigurationEntry#NETWORK_ID
*/
public String networkId;
// last know address
@Type(type = "socketAddressType")
private InetSocketAddress connectAddress;
/**
* The last time a successful (physical) connection was possible. Just the
* initial handshake. Node might not have been completely connected
* afterwards because handshake was not completed, e.g. because
* uninteresting.
*/
private Date lastConnectTime;
// flag if peer was connected at remote side
public boolean isConnected;
public boolean isSupernode;
// Transient caches
private transient Boolean hasNullIP;
/**
* The cached hash info.
*/
private transient int hash;
private MemberInfo() {
// NOP - only for hibernate
}
public MemberInfo(String nick, String id, String networkId) {
this.nick = nick;
this.id = id;
if (networkId != null) {
this.networkId = networkId;
} else {
this.networkId = ConfigurationEntry.NETWORK_ID.getDefaultValue();
}
}
// Setter/Getter **********************************************************
public void setConnectAddress(InetSocketAddress newConnectAddress) {
if (Util.equals(connectAddress, newConnectAddress)) {
// System.err.println("Skipping set of connect addres");
return;
}
this.connectAddress = newConnectAddress;
// Clear cache
hasNullIP = null;
}
public InetSocketAddress getConnectAddress() {
return this.connectAddress;
}
public String getId() {
return id;
}
public String getNick() {
return nick;
}
public Date getLastConnectTime() {
return lastConnectTime;
}
public void setLastConnectNow() {
lastConnectTime = new Date();
}
/**
* WARNING: FOR TESTS ONLY.
*/
public void setLastConnectTime(Date date) {
lastConnectTime = date;
}
// Logic ******************************************************************
/**
* @param member
* @return true if this memberinfo is equal to the memberinfo of the member.
*/
public boolean matches(Member member) {
if (member == null) {
return false;
}
return Util.equals(member.getId(), id);
}
/**
* @param searchString
* @return if this member matches the search string or if it equals the IP
* nick contains the search String
*/
public boolean matches(String searchString) {
if (connectAddress != null && connectAddress.getAddress() != null) {
String theIp = connectAddress.getAddress().getHostAddress();
if (theIp != null && theIp.equals(searchString)) {
return true;
}
}
return nick.toLowerCase().indexOf(searchString.toLowerCase()) >= 0;
}
/**
* @param controller
* @return if this member is a friend
*/
public boolean isFriend(Controller controller) {
Reject.ifNull(controller, "Controller is null");
Member node = getNode(controller, false);
return node != null && node.isFriend();
}
/**
* @param controller
* @return true if this node is on the same network = same network Id.
*/
public boolean isOnSameNetwork(Controller controller) {
Reject.ifNull(controller, "Controller is null");
return controller.getNodeManager().getNetworkId().equals(networkId);
}
/**
* @param controller
* the controller
* @return if this member is invalid
*/
public boolean isInvalid(Controller controller) {
if (isFriend(controller)) {
// Friends are never invalid
return false;
}
// Check for valid address
// #1334
// if (connectAddress == null || connectAddress.getAddress() == null) {
// return true;
// }
// Check for timeout
if (lastConnectTime == null) {
return true;
}
if (System.currentTimeMillis() - lastConnectTime.getTime() >= Constants.NODE_TIME_TO_INVALIDATE)
{
return true;
}
if (lastConnectTime.getTime() > (System.currentTimeMillis() + Constants.NODE_TIME_MAX_IN_FUTURE))
{
// The last connect time lies to much in the future, not
// possible!
return true;
}
return false;
// #1334
// if (hasNullIP == null) {
// if (NULL_IP != null) {
// // Using advanced check
// hasNullIP = Boolean.valueOf(NULL_IP.equals(connectAddress
// .getAddress()));
// } else {
// // Fallback, this works
// byte[] addr = connectAddress.getAddress().getAddress();
// hasNullIP = Boolean.valueOf((addr[0] & 0xff) == 0
// && (addr[1] & 0xff) == 0 && (addr[2] & 0xff) == 0
// && (addr[3] & 0xff) == 0);
// }
// }
// return hasNullIP.booleanValue();
}
public boolean hasNullIP() {
if (hasNullIP == null) {
hasNullIP = NetworkUtil.isNullIP(connectAddress.getAddress());
}
return hasNullIP.booleanValue();
}
/**
* Returns the full node/member for this member info. Return null if member
* is not longer known to the NodeManager. If flagged with addIfNessesary a
* new node will be created if not available @ NodeManager. Calling with
* addIfNessesary = true always returns a valid node (Member).
*
* @param controller
* @param addIfNessesary
* if a new node should be added if not available @ nodemanager
* @return the node OR null if node was not found.
*/
public Member getNode(Controller controller, boolean addIfNessesary) {
Reject.ifNull(controller, "Controller is null");
Member node = controller.getNodeManager().getNode(this);
if (node == null && addIfNessesary) {
node = controller.getNodeManager().addNode(this);
}
return node;
}
/*
* General
*/
public MemberInfo intern() {
if (INTERNALIZER == null) {
// No actual intern
return this;
}
return INTERNALIZER.intern(this);
}
@Override
public int hashCode() {
if (hash == 0) {
// Cache the hashcode
hash = hashCode0();
}
return hash;
}
public int hashCode0() {
return id != null ? id.hashCode() : 0;
}
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof MemberInfo) {
MemberInfo other = (MemberInfo) obj;
return Util.equals(id, other.id);
}
return false;
}
public String toString() {
return "Member '" + nick + "' (con. at " + connectAddress + ")";
}
// Serialization optimization *********************************************
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException
{
in.defaultReadObject();
// this.id = id.intern();
// this.nick = nick.intern();
if (this.networkId == null) {
this.networkId = ConfigurationEntry.NETWORK_ID.getDefaultValue();
} else {
// this.networkId = networkId.intern();
}
}
private static final long extVersionUID = 100L;
public static MemberInfo readExt(ObjectInput in) throws IOException,
ClassNotFoundException
{
MemberInfo memberInfo = new MemberInfo();
memberInfo.readExternal(in);
return memberInfo;
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException
{
long extUID = in.readLong();
if (extUID != extVersionUID) {
throw new InvalidClassException(this.getClass().getName(),
"Unable to read. extVersionUID(steam): " + extUID
+ ", expected: " + extVersionUID);
}
id = in.readUTF();
nick = in.readUTF();
networkId = ExternalizableUtil.readString(in);
if (networkId == null) {
networkId = ConfigurationEntry.NETWORK_ID.getDefaultValue();
}
connectAddress = ExternalizableUtil.readAddress(in);
lastConnectTime = ExternalizableUtil.readDate(in);
isConnected = in.readBoolean();
isSupernode = in.readBoolean();
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeLong(extVersionUID);
out.writeUTF(id);
out.writeUTF(nick);
ExternalizableUtil.writeString(out, networkId);
ExternalizableUtil.writeAddress(out, connectAddress);
ExternalizableUtil.writeDate(out, lastConnectTime);
out.writeBoolean(isConnected);
out.writeBoolean(isSupernode);
}
}