class ClientSessionConnection extends VirtualConnection {

    private String connectionManagerName;
    private String serverName;
    private ConnectionMultiplexerManager multiplexerManager;
    private String hostName;
    private String hostAddress;

    public ClientSessionConnection(String connectionManagerName, String hostName, String hostAddress) {
        this.connectionManagerName = connectionManagerName;
        multiplexerManager = ConnectionMultiplexerManager.getInstance();
        serverName = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
        this.hostName = hostName;
        this.hostAddress = hostAddress;
    }

    /**
     * Delivers the packet to the Connection Manager that in turn will forward it to the
     * target user. Connection Managers may have one or many connections to the server so
     * just get any connection to the Connection Manager (uses a random) and use it.<p>
     *
     * If the packet to send does not have a TO attribute then wrap the packet with a
     * special IQ packet. The wrapper IQ packet will be sent to the Connection Manager
     * and the stream ID of this Client Session will be used for identifying that the wrapped
     * packet must be sent to the connected user. Since some packets can be exchanged before
     * the user has a binded JID we need to use the stream ID as the unique identifier.
     *
     * @param packet the packet to send to the user.
     */
    @Override
    public void deliver(Packet packet) {
        StreamID streamID = session.getStreamID();
        ConnectionMultiplexerSession multiplexerSession =
                multiplexerManager.getMultiplexerSession(connectionManagerName,streamID);
        if (multiplexerSession != null) {
            // Wrap packet so that the connection manager can figure out the target session
            Route wrapper = new Route(streamID);
            wrapper.setFrom(serverName);
            wrapper.setTo(connectionManagerName);
            wrapper.setChildElement(packet.getElement().createCopy());
            // Deliver wrapper
            multiplexerSession.process(wrapper);
            session.incrementServerPacketCount();
        }
    }

    /**
     * Delivers the stanza to the Connection Manager that in turn will forward it to the
     * target user. Connection Managers may have one or many connections to the server so
     * just get any connection to the Connection Manager (uses a random) and use it.<p>
     *
     * The stanza to send wrapped with a special IQ packet. The wrapper IQ packet will be
     * sent to the Connection Manager and the stream ID of this Client Session will be used
     * for identifying that the wrapped stanza must be sent to the connected user.
     *
     * @param text the stanza to send to the user.
     */
    @Override
    public void deliverRawText(String text) {
        StreamID streamID = session.getStreamID();
        ConnectionMultiplexerSession multiplexerSession =
                multiplexerManager.getMultiplexerSession(connectionManagerName,streamID);
        if (multiplexerSession != null) {
            // Wrap packet so that the connection manager can figure out the target session
            final Element route = DocumentHelper.createElement("route");
            route.addAttribute("from", serverName);
            route.addAttribute("to", connectionManagerName);
            route.addAttribute("streamid", streamID.getID());
            route.addText(text);
            // Deliver the wrapped stanza
            multiplexerSession.deliverRawText(route.asXML());
        }
    }

    @Override
    public Optional<String> getTLSProtocolName() {
        return Optional.of("unknown");
    }

    @Override
    public Optional<String> getCipherSuiteName() {
        return Optional.of("unknown");
    }

    @Override
    public ConnectionConfiguration getConfiguration()
    {
        // Here, a client-to-server configuration is mocked. It is likely not used, as actual connection handling takes
        // place at the connection manager.
        final ConnectionManager connectionManager = XMPPServer.getInstance().getConnectionManager();
        return connectionManager.getListener( ConnectionType.SOCKET_C2S, true ).generateConnectionConfiguration();
    }

    public byte[] getAddress() throws UnknownHostException {
        if (hostAddress != null) {
            return InetAddress.getByName(hostAddress).getAddress();
        }
        return null;
    }

    @Override
    public String getHostAddress() throws UnknownHostException {
        if (hostAddress != null) {
            return hostAddress;
        }
        // Return IP address of the connection manager that the client used to log in
        ConnectionMultiplexerSession multiplexerSession =
                multiplexerManager.getMultiplexerSession(connectionManagerName);
        if (multiplexerSession != null) {
            return multiplexerSession.getHostAddress();
        }
        return null;
    }

    @Override
    public String getHostName() throws UnknownHostException {
        if (hostName != null) {
            return hostName;
        }
        // Return IP address of the connection manager that the client used to log in
        ConnectionMultiplexerSession multiplexerSession =
                multiplexerManager.getMultiplexerSession(connectionManagerName);
        if (multiplexerSession != null) {
            return multiplexerSession.getHostName();
        }
        return null;
    }

    @Override
    public void systemShutdown() {
        // Do nothing since a system-shutdown error will be sent to the Connection Manager
        // that in turn will send a system-shutdown to connected clients. This is an
        // optimization to reduce number of packets being sent from the server.
    }

    /**
     * If the Connection Manager or the Client requested to close the connection then just do
     * nothing. But if the server originated the request to close the connection then we need
     * to send to the Connection Manager a packet letting him know that the Client Session needs
     * to be terminated.
     *
     * @param error If non-null, this error will be sent to the peer before the connection is disconnected.
     */
    @Override
    public void closeVirtualConnection(@Nullable final StreamError error) {<FILL_FUNCTION_BODY>}
}

        // Figure out who requested the connection to be closed
        StreamID streamID = session.getStreamID();
        if (multiplexerManager.getClientSession(connectionManagerName, streamID) == null) {
            // Client or Connection manager requested to close the session
            // Do nothing since it has already been removed and closed
        }
        else {
            ConnectionMultiplexerSession multiplexerSession =
                    multiplexerManager.getMultiplexerSession(connectionManagerName,streamID);
            if (multiplexerSession != null) {
                // Server requested to close the client session so let the connection manager
                // know that he has to finish the client session
                IQ closeRequest = new IQ(IQ.Type.set);
                closeRequest.setFrom(serverName);
                closeRequest.setTo(connectionManagerName);
                Element child = closeRequest.setChildElement("session",
                        "http://jabber.org/protocol/connectionmanager");
                child.addAttribute("id", streamID.getID());
                child.addElement("close");
                if (error != null) {
                    // Note that this is ignored by all Connection Manager implementations at the time of writing.
                    child.add(error.getElement());
                }
                multiplexerSession.process(closeRequest);
            }
        }
    
<methods>public non-sealed void <init>() ,public void addCompression() ,public void close(StreamError, boolean) ,public abstract void closeVirtualConnection(StreamError) ,public java.security.cert.Certificate[] getLocalCertificates() ,public org.jivesoftware.openfire.PacketDeliverer getPacketDeliverer() ,public java.security.cert.Certificate[] getPeerCertificates() ,public boolean isClosed() ,public boolean isCompressed() ,public boolean isEncrypted() ,public boolean isInitialized() ,public boolean isSecure() ,public boolean isUsingSelfSignedCertificate() ,public void setUsingSelfSignedCertificate(boolean) ,public void startCompression() ,public void startTLS(boolean, boolean) throws java.lang.Exception,public boolean validate() <variables>private static final Logger Log,private final AtomicReference<org.jivesoftware.openfire.Connection.State> state