/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.net.socket;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.teiid.client.security.ILogon;
import org.teiid.client.security.InvalidSessionException;
import org.teiid.client.security.LogonException;
import org.teiid.client.security.LogonResult;
import org.teiid.client.util.ExceptionUtil;
import org.teiid.client.util.ResultsFuture;
import org.teiid.core.util.PropertiesUtils;
import org.teiid.designer.runtime.version.spi.TeiidServerVersion.Version;
import org.teiid.gss.MakeGSS;
import org.teiid.net.CommunicationException;
import org.teiid.net.ConnectionException;
import org.teiid.net.HostInfo;
import org.teiid.net.ServerConnection;
import org.teiid.net.TeiidURL;
import org.teiid.runtime.client.Messages;
import org.teiid.runtime.client.TeiidClientException;
/**
* Represents a client connection that maintains session state and allows for service fail over.
* Implements a sticky random selection policy.
*/
public class SocketServerConnection implements ServerConnection {
private static final int FAILOVER_PING_INTERVAL = 1000;
private SocketServerInstanceFactory connectionFactory;
private ServerDiscovery serverDiscovery;
private static Logger log = Logger.getLogger("org.teiid.client.sockets"); //$NON-NLS-1$
private boolean secure;
private Properties connProps;
private SocketServerInstance serverInstance;
private LogonResult logonResult;
private Map<HostInfo, LogonResult> logonResults = new ConcurrentHashMap<HostInfo, LogonResult>();
private ILogon logon;
private boolean closed;
private boolean failOver;
private long lastPing = System.currentTimeMillis();
private int pingFailOverInterval = FAILOVER_PING_INTERVAL;
public SocketServerConnection(
SocketServerInstanceFactory connectionFactory, boolean secure,
ServerDiscovery serverDiscovery, Properties connProps) throws CommunicationException, ConnectionException {
this.connectionFactory = connectionFactory;
this.serverDiscovery = serverDiscovery;
this.connProps = connProps;
this.secure = secure;
//ILogon that is allowed to failover
this.logon = this.getService(ILogon.class);
this.failOver = Boolean.valueOf(connProps.getProperty(TeiidURL.CONNECTION.AUTO_FAILOVER)).booleanValue();
selectServerInstance(false);
}
/**
* Implements a sticky random selection policy
* TODO: make this customizable
* TODO: put more information on hostinfo as to process response time, last successful connect, etc.
* @throws ConnectionException
*/
public synchronized SocketServerInstance selectServerInstance(boolean logoff)
throws CommunicationException, ConnectionException {
if (closed) {
throw new CommunicationException(Messages.gs(Messages.TEIID.TEIID20016));
}
if (this.serverInstance != null && (!failOver || this.serverInstance.isOpen())) {
return this.serverInstance;
}
List<HostInfo> hostKeys = new ArrayList<HostInfo>(this.serverDiscovery.getKnownHosts(logonResult, null));
boolean discoverHosts = true;
closeServerInstance();
List<HostInfo> hostCopy = new ArrayList<HostInfo>(hostKeys);
int knownHosts = hostKeys.size();
while (hostKeys.size() > 0) {
HostInfo hostInfo = this.serverDiscovery.selectNextInstance(hostKeys);
Exception ex = null;
try {
if (!hostInfo.isResolved()) {
hostInfo = new HostInfo(hostInfo.getHostName(), new InetSocketAddress(hostInfo.getInetAddress(), hostInfo.getPortNumber()));
}
ILogon newLogon = connect(hostInfo);
if (this.logonResult == null) {
try {
logon(newLogon, logoff);
this.serverDiscovery.connectionSuccessful(hostInfo);
if (discoverHosts) {
List<HostInfo> updatedHosts = this.serverDiscovery.getKnownHosts(logonResult, this.serverInstance);
if (updatedHosts.size() > 1 && new HashSet<HostInfo>(updatedHosts).size() > new HashSet<HostInfo>(hostCopy).size()) {
hostKeys = updatedHosts;
closeServerInstance();
discoverHosts = false;
continue;
}
}
} catch (LogonException e) {
// Propagate the original message as it contains the message we want
// to give to the user
throw new ConnectionException(e);
} catch (TeiidClientException e) {
if (e.getCause() instanceof CommunicationException) {
throw (CommunicationException)e.getCause();
}
throw new CommunicationException(e, Messages.gs(Messages.TEIID.TEIID20018));
}
}
return this.serverInstance;
} catch (IOException e) {
ex = e;
} catch (SingleInstanceCommunicationException e) {
ex = e;
}
this.serverDiscovery.markInstanceAsBad(hostInfo);
if (knownHosts == 1) { //just a single host, use the exception
if (ex instanceof UnknownHostException) {
throw new SingleInstanceCommunicationException(ex, Messages.gs(Messages.TEIID.TEIID20019, hostInfo.getHostName()));
}
throw new SingleInstanceCommunicationException(ex,Messages.gs(Messages.TEIID.TEIID20020, hostInfo.getHostName(), String.valueOf(hostInfo.getPortNumber()), ex.getMessage()));
}
log.log(Level.FINE, "Unable to connect to host", ex); //$NON-NLS-1$
}
throw new CommunicationException(Messages.gs(Messages.TEIID.TEIID20021, hostCopy.toString()));
}
private void logon(ILogon newLogon, boolean logoff) throws LogonException, TeiidClientException, CommunicationException {
SocketServerInstance instance = this.serverInstance;
updateConnectionProperties(connProps, instance.getLocalAddress(), true);
LogonResult newResult = null;
// - if gss
if (connProps.contains(TeiidURL.CONNECTION.JAAS_NAME)) {
newResult = MakeGSS.authenticate(newLogon, connProps);
} else {
newResult = newLogon.logon(connProps);
}
AuthenticationType type = (AuthenticationType) newResult.getProperty(ILogon.AUTH_TYPE);
if (type != null) {
//server has issued an additional challange
if (type == AuthenticationType.GSS) {
newResult = MakeGSS.authenticate(newLogon, connProps);
} else {
throw new LogonException(Messages.gs(Messages.TEIID.TEIID20034, type));
}
}
if (logoff) {
if ("07.03".compareTo(this.serverInstance.getServerVersion()) <= 0 || //$NON-NLS-1$
"7.3".compareTo(this.serverInstance.getServerVersion()) <= 0) { //$NON-NLS-1$
//just remove the current instance - the server has already logged off the current user
LogonResult old = this.logonResults.remove(this.serverInstance.getHostInfo());
this.connectionFactory.disconnected(this.serverInstance, old.getSessionToken());
}
logoffAll();
}
this.logonResult = newResult;
this.logonResults.put(instance.getHostInfo(), this.logonResult);
this.connectionFactory.connected(instance, this.logonResult.getSessionToken());
}
public static void updateConnectionProperties(Properties connectionProperties, InetAddress addr, boolean setMac) {
if (addr == null) {
return;
}
String address = addr.getHostAddress();
Object old = connectionProperties.put(TeiidURL.CONNECTION.CLIENT_IP_ADDRESS, address);
if (old == null || !address.equals(old)) {
connectionProperties.put(TeiidURL.CONNECTION.CLIENT_HOSTNAME, addr.getCanonicalHostName());
if (setMac) {
try {
NetworkInterface ni = NetworkInterface.getByInetAddress(addr);
if (ni != null && ni.getHardwareAddress() != null) {
StringBuilder sb = new StringBuilder();
for (byte b : ni.getHardwareAddress()) {
sb.append(PropertiesUtils.toHex(b >> 4));
sb.append(PropertiesUtils.toHex(b));
}
connectionProperties.put(TeiidURL.CONNECTION.CLIENT_MAC, sb.toString());
}
} catch (SocketException e) {
connectionProperties.remove(TeiidURL.CONNECTION.CLIENT_MAC);
}
} else {
connectionProperties.remove(TeiidURL.CONNECTION.CLIENT_MAC);
}
}
}
private ILogon connect(HostInfo hostInfo) throws CommunicationException,
IOException {
hostInfo.setSsl(secure);
this.serverInstance = connectionFactory.getServerInstance(hostInfo);
this.logonResult = logonResults.get(hostInfo);
ILogon newLogon = this.serverInstance.getService(ILogon.class);
if (this.logonResult != null) {
try {
newLogon.assertIdentity(logonResult.getSessionToken());
} catch (TeiidClientException e) {
// session is no longer valid
disconnect();
}
}
return newLogon;
}
public <T> T getService(Class<T> iface) {
return iface.cast(Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] {iface}, new SocketServerInstanceImpl.RemoteInvocationHandler(iface, PropertiesUtils.getBooleanProperty(connProps, TeiidURL.CONNECTION.ENCRYPT_REQUESTS, false)) {
@Override
protected SocketServerInstance getInstance() throws CommunicationException {
if (failOver && System.currentTimeMillis() - lastPing > pingFailOverInterval) {
try {
ResultsFuture<?> future = selectServerInstance(false).getService(ILogon.class).ping();
future.get();
} catch (SingleInstanceCommunicationException e) {
closeServerInstance();
} catch (CommunicationException e) {
throw e;
} catch (InvalidSessionException e) {
disconnect();
closeServerInstance();
} catch (Exception e) {
closeServerInstance();
}
}
lastPing = System.currentTimeMillis();
try {
return selectServerInstance(false);
} catch (ConnectionException e) {
throw new CommunicationException(e);
}
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
try {
return super.invoke(proxy, method, args);
} catch (Exception e) {
if (ExceptionUtil.getExceptionOfType(e, InvalidSessionException.class) != null) {
disconnect();
}
throw e;
}
}
}));
}
public synchronized void close() {
if (this.closed) {
return;
}
if (this.serverInstance != null) {
logoff();
}
logoffAll();
this.closed = true;
this.serverDiscovery.shutdown();
}
private void logoffAll() {
for (Map.Entry<HostInfo, LogonResult> logonEntry : logonResults.entrySet()) {
try {
connect(logonEntry.getKey());
logoff();
} catch (Exception e) {
}
}
}
private void logoff() {
disconnect();
try {
//make a best effort to send the logoff
Future<?> writeFuture = this.serverInstance.getService(ILogon.class).logoff();
writeFuture.get(5000, TimeUnit.MILLISECONDS);
} catch (Exception e) {
//ignore
}
closeServerInstance();
}
private void disconnect() {
this.logonResults.remove(this.serverInstance.getHostInfo());
if (this.logonResult != null) {
this.connectionFactory.disconnected(this.serverInstance, this.logonResult.getSessionToken());
this.logonResult = null;
}
}
private synchronized ResultsFuture<?> isOpen() throws CommunicationException, InvalidSessionException, TeiidClientException {
if (this.closed) {
throw new CommunicationException(Messages.gs(Messages.TEIID.TEIID20023));
}
return logon.ping();
}
public boolean isOpen(long msToTest) {
try {
ResultsFuture<?> future = isOpen();
future.get(msToTest, TimeUnit.MILLISECONDS);
return true;
} catch (Throwable th) {
return false;
}
}
public LogonResult getLogonResult() {
return logonResult;
}
synchronized void closeServerInstance() {
if (this.serverInstance != null) {
this.serverInstance.shutdown();
this.serverInstance = null;
}
}
public boolean isSameInstance(ServerConnection otherService) throws CommunicationException {
if (!(otherService instanceof SocketServerConnection)) {
return false;
}
try {
return selectServerInstance(false).getHostInfo().equals(((SocketServerConnection)otherService).selectServerInstance(false).getHostInfo());
} catch (ConnectionException e) {
throw new CommunicationException(e);
}
}
public void cleanUp() {
if (this.serverInstance != null && this.logonResult != null && "08.02".compareTo(this.serverInstance.getServerVersion()) <= 0) { //$NON-NLS-1$
ILogon newLogon = this.serverInstance.getService(ILogon.class);
try {
newLogon.assertIdentity(null);
} catch (InvalidSessionException e) {
} catch (TeiidClientException e) {
}
}
closeServerInstance();
}
public void setFailOver(boolean failOver) {
this.failOver = failOver;
}
public void setFailOverPingInterval(int pingFailOverInterval) {
this.pingFailOverInterval = pingFailOverInterval;
}
@Override
public void authenticate() throws ConnectionException,
CommunicationException {
if (this.serverInstance == null) {
selectServerInstance(true); //this will trigger a logon with the new credentials
} else {
ILogon logonInstance = this.serverInstance.getService(ILogon.class);
try {
this.logon(logonInstance, true);
} catch (LogonException e) {
throw new ConnectionException(e);
} catch (TeiidClientException e) {
throw new CommunicationException(e);
}
}
}
@Override
public boolean supportsContinuous() {
return false;
}
@Override
public boolean isLocal() {
return false;
}
}