blob: 25e0d9611daa26cdc04aae284ab200a92457931e [file] [log] [blame]
/*
* Copyright (c) 2008-2012, 2019, 2020 Eike Stepper (Loehne, Germany) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Eike Stepper - initial API and implementation
*/
package org.eclipse.spi.net4j;
import org.eclipse.net4j.buffer.IBuffer;
import org.eclipse.net4j.channel.ChannelException;
import org.eclipse.net4j.connector.ConnectorException;
import org.eclipse.net4j.connector.ConnectorState;
import org.eclipse.net4j.connector.IConnector;
import org.eclipse.net4j.connector.IConnectorStateEvent;
import org.eclipse.net4j.protocol.IProtocol;
import org.eclipse.net4j.util.ReflectUtil.ExcludeFromDump;
import org.eclipse.net4j.util.event.Event;
import org.eclipse.net4j.util.lifecycle.LifecycleUtil;
import org.eclipse.net4j.util.om.log.OMLogger;
import org.eclipse.net4j.util.om.trace.ContextTracer;
import org.eclipse.net4j.util.security.INegotiationContext;
import org.eclipse.net4j.util.security.INegotiator;
import org.eclipse.net4j.util.security.NegotiationException;
import org.eclipse.internal.net4j.bundle.OM;
import java.text.MessageFormat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* If the meaning of this type isn't clear, there really should be more of a description here...
*
* @author Eike Stepper
* @since 2.0
*/
public abstract class Connector extends ChannelMultiplexer implements InternalConnector
{
private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG_CONNECTOR, Connector.class);
private String userID;
private transient ConnectorState connectorState = ConnectorState.DISCONNECTED;
@ExcludeFromDump
private transient CountDownLatch finishedConnecting;
@ExcludeFromDump
private transient CountDownLatch finishedNegotiating;
@ExcludeFromDump
private transient INegotiationContext negotiationContext;
@ExcludeFromDump
private transient NegotiationException negotiationException;
public Connector()
{
}
@Override
public INegotiator getNegotiator()
{
return getConfig().getNegotiator();
}
@Override
public void setNegotiator(INegotiator negotiator)
{
getConfig().setNegotiator(negotiator);
}
public INegotiationContext getNegotiationContext()
{
return negotiationContext;
}
@Override
public boolean isClient()
{
return getLocation() == Location.CLIENT;
}
@Override
public boolean isServer()
{
return getLocation() == Location.SERVER;
}
@Override
public String getUserID()
{
return userID;
}
public void setUserID(String userID)
{
checkState(getState() != ConnectorState.CONNECTED, "Connector is already connected"); //$NON-NLS-1$
if (TRACER.isEnabled())
{
TRACER.format("Setting userID {0} for {1}", userID, this); //$NON-NLS-1$
}
this.userID = userID;
}
@Override
public ConnectorState getState()
{
return connectorState;
}
public void setState(ConnectorState newState) throws ConnectorException
{
ConnectorState oldState = getState();
if (newState != oldState)
{
if (TRACER.isEnabled())
{
TRACER.format("Setting state {0} (was {1}) for {2}", newState, oldState.toString().toLowerCase(), this); //$NON-NLS-1$
}
connectorState = newState;
switch (newState)
{
case DISCONNECTED:
if (finishedConnecting != null)
{
finishedConnecting.countDown();
finishedConnecting = null;
}
if (finishedNegotiating != null)
{
finishedNegotiating.countDown();
finishedNegotiating = null;
}
break;
case CONNECTING:
finishedConnecting = new CountDownLatch(1);
finishedNegotiating = new CountDownLatch(1);
// The concrete implementation must advance state to NEGOTIATING or CONNECTED
break;
case NEGOTIATING:
negotiationContext = createNegotiationContext();
finishedConnecting.countDown();
getNegotiator().negotiate(negotiationContext);
break;
case CONNECTED:
negotiationContext = null;
deferredActivate(true);
finishedConnecting.countDown();
finishedNegotiating.countDown();
break;
}
fireEvent(new ConnectorStateEvent(this, oldState, newState));
}
}
public boolean isDisconnected()
{
return connectorState == ConnectorState.DISCONNECTED;
}
public boolean isConnecting()
{
return connectorState == ConnectorState.CONNECTING;
}
public boolean isNegotiating()
{
return connectorState == ConnectorState.NEGOTIATING;
}
@Override
public boolean isConnected()
{
if (negotiationException != null)
{
throw new ConnectorException("Connector negotiation failed", negotiationException); //$NON-NLS-1$
}
return connectorState == ConnectorState.CONNECTED;
}
@Override
public void connectAsync() throws ConnectorException
{
try
{
activate();
}
catch (ConnectorException ex)
{
throw ex;
}
catch (Exception ex)
{
throw new ConnectorException(ex);
}
}
/**
* @since 4.0
*/
@Override
public void waitForConnection(long timeout) throws ConnectorException
{
long totalTimeout = timeout;
final long MAX_POLL_INTERVAL = 100L;
boolean withTimeout = timeout != NO_TIMEOUT;
try
{
if (TRACER.isEnabled())
{
TRACER.trace("Waiting for connection..."); //$NON-NLS-1$
}
for (;;)
{
long t = MAX_POLL_INTERVAL;
if (withTimeout)
{
t = Math.min(MAX_POLL_INTERVAL, timeout);
timeout -= MAX_POLL_INTERVAL;
}
if (t <= 0)
{
break;
}
if (finishedNegotiating == null)
{
break;
}
if (finishedNegotiating.await(t, TimeUnit.MILLISECONDS))
{
break;
}
}
if (!isConnected())
{
throw new ConnectorException("Connection timeout after " + totalTimeout + " milliseconds");
}
}
catch (ConnectorException ex)
{
setState(ConnectorState.DISCONNECTED);
throw ex;
}
catch (Exception ex)
{
setState(ConnectorState.DISCONNECTED);
throw new ConnectorException(ex);
}
}
/**
* @since 4.0
*/
@Override
public void connect(long timeout) throws ConnectorException
{
connectAsync();
waitForConnection(timeout);
}
/**
* @since 4.0
*/
@Override
public void connect() throws ConnectorException
{
connect(NO_TIMEOUT);
}
@Override
public void close()
{
LifecycleUtil.deactivate(this, OMLogger.Level.DEBUG);
}
@Override
public boolean isClosed()
{
return !isActive();
}
@Override
public short getBufferCapacity()
{
return getConfig().getBufferProvider().getBufferCapacity();
}
@Override
public IBuffer provideBuffer()
{
return getConfig().getBufferProvider().provideBuffer();
}
@Override
public void retainBuffer(IBuffer buffer)
{
getConfig().getBufferProvider().retainBuffer(buffer);
}
protected void leaveConnecting()
{
if (getNegotiator() == null)
{
setState(ConnectorState.CONNECTED);
}
else
{
setState(ConnectorState.NEGOTIATING);
}
}
@Override
protected abstract INegotiationContext createNegotiationContext();
protected NegotiationException getNegotiationException()
{
return negotiationException;
}
protected void setNegotiationException(NegotiationException negotiationException)
{
this.negotiationException = negotiationException;
}
@Override
protected void initChannel(InternalChannel channel, IProtocol<?> protocol)
{
super.initChannel(channel, protocol);
channel.setUserID(getUserID());
}
@Override
protected void deregisterChannelFromPeer(InternalChannel channel) throws ChannelException
{
}
@Override
public Location getLocation()
{
return null;
}
@Override
public String getURL()
{
return null;
}
/**
* @since 4.1
*/
@Override
public boolean isDeferredActivation()
{
return true;
}
@Override
protected void doBeforeOpenChannel(IProtocol<?> protocol)
{
super.doBeforeOpenChannel(protocol);
long timeout = getOpenChannelTimeout();
waitForConnection(timeout);
}
@Override
protected void doBeforeActivate() throws Exception
{
super.doBeforeActivate();
checkState(getConfig().getBufferProvider(), "getConfig().getBufferProvider()"); //$NON-NLS-1$
if (userID != null && getConfig().getNegotiator() == null)
{
throw new IllegalStateException("A user ID on this connector requires a negotiator"); //$NON-NLS-1$
}
}
@Override
protected void doActivate() throws Exception
{
super.doActivate();
setState(ConnectorState.CONNECTING);
}
@Override
protected void doDeactivate() throws Exception
{
setState(ConnectorState.DISCONNECTED);
super.doDeactivate();
}
/**
* @author Eike Stepper
*/
private static class ConnectorStateEvent extends Event implements IConnectorStateEvent
{
private static final long serialVersionUID = 1L;
private ConnectorState oldState;
private ConnectorState newState;
public ConnectorStateEvent(IConnector source, ConnectorState oldState, ConnectorState newState)
{
super(source);
this.oldState = oldState;
this.newState = newState;
}
@Override
public IConnector getSource()
{
return (IConnector)super.getSource();
}
@Override
public ConnectorState getOldState()
{
return oldState;
}
@Override
public ConnectorState getNewState()
{
return newState;
}
@Override
public String toString()
{
return MessageFormat.format("ConnectorStateEvent[source={0}, oldState={1}, newState={2}]", getSource(), //$NON-NLS-1$
getOldState(), getNewState());
}
}
}