class RemoteSession implements Session {

    protected byte[] nodeID;
    protected JID address;

    // Cache content that never changes
    protected StreamID streamID;
    private Date creationDate;
    private String serverName;
    private String hostAddress;
    private String hostName;

    public RemoteSession(byte[] nodeID, JID address) {
        this.nodeID = nodeID;
        this.address = address;
    }

    public JID getAddress() {
        return address;
    }

    /**
     * Remote sessions are always authenticated. Otherwise, they won't be visibile to other
     * cluster nodes. When the session is closed it will no longer be visible to other nodes
     * so CLOSED is never returned.
     *
     * @return the authenticated status.
     */
    @Override
    public Status getStatus() {
        return Session.Status.AUTHENTICATED;
    }

    public StreamID getStreamID() {
        // Get it once and cache it since it never changes
        if (streamID == null) {
            ClusterTask task = getRemoteSessionTask(RemoteSessionTask.Operation.getStreamID);
            streamID = (StreamID) doSynchronousClusterTask(task);
        }
        return streamID;
    }

    public String getServerName() {
        if (serverName == null) {
            ClusterTask task = getRemoteSessionTask(RemoteSessionTask.Operation.getServerName);
            serverName = (String) doSynchronousClusterTask(task);
        }
        return serverName;
    }

    public Date getCreationDate() {
        // Get it once and cache it since it never changes
        if (creationDate == null) {
            ClusterTask task = getRemoteSessionTask(RemoteSessionTask.Operation.getCreationDate);
            creationDate = (Date) doSynchronousClusterTask(task);
        }
        return creationDate;
    }

    public Date getLastActiveDate() {
        ClusterTask<Object> task = getRemoteSessionTask(RemoteSessionTask.Operation.getLastActiveDate);
        return (Date) doSynchronousClusterTask(task);
    }

    public long getNumClientPackets() {
        ClusterTask<Object> task = getRemoteSessionTask(RemoteSessionTask.Operation.getNumClientPackets);
        final Object clusterTaskResult = doSynchronousClusterTask(task);
        return clusterTaskResult == null ? -1 : (Long) clusterTaskResult;
    }

    public long getNumServerPackets() {
        ClusterTask<Object> task = getRemoteSessionTask(RemoteSessionTask.Operation.getNumServerPackets);
        final Object clusterTaskResult = doSynchronousClusterTask(task);
        return clusterTaskResult == null ? -1 : (Long) clusterTaskResult;
    }

    public String getTLSProtocolName() {
        ClusterTask<Object> task = getRemoteSessionTask(RemoteSessionTask.Operation.getTLSProtocolName);
        return (String) doSynchronousClusterTask(task);
    }

    public String getCipherSuiteName() {
        ClusterTask<Object> task = getRemoteSessionTask(RemoteSessionTask.Operation.getCipherSuiteName);
        return (String) doSynchronousClusterTask(task);
    }

    public Certificate[] getPeerCertificates() {
        ClusterTask<Object> task = getRemoteSessionTask(RemoteSessionTask.Operation.getPeerCertificates);
        return (Certificate[]) doSynchronousClusterTask(task);
    }

    public Map<String,String> getSoftwareVersion() {
        ClusterTask<Object> task = getRemoteSessionTask(RemoteSessionTask.Operation.getSoftwareVersion);
        return (Map<String,String>) doSynchronousClusterTask(task);
    }

    public void process(Packet packet) {
        doClusterTask(getProcessPacketTask(packet));
    }

    public void close() {
        doSynchronousClusterTask(getRemoteSessionTask(RemoteSessionTask.Operation.close));
    }

    public boolean isClosed() {
        ClusterTask<Object> task = getRemoteSessionTask(RemoteSessionTask.Operation.isClosed);
        final Object clusterTaskResult = doSynchronousClusterTask(task);
        return clusterTaskResult == null ? false : (Boolean) clusterTaskResult;
    }

    @Deprecated // Remove in Openfire 4.9 or later.
    public boolean isSecure() {
        return isEncrypted();
    }

    public boolean isEncrypted() {
        ClusterTask<Object> task = getRemoteSessionTask(RemoteSessionTask.Operation.isEncrypted);
        final Object clusterTaskResult = doSynchronousClusterTask(task);
        return clusterTaskResult == null ? false : (Boolean) clusterTaskResult;
    }

    public String getHostAddress() throws UnknownHostException {
        if (hostAddress == null) {
            ClusterTask<Object> task = getRemoteSessionTask(RemoteSessionTask.Operation.getHostAddress);
            hostAddress = (String) doSynchronousClusterTask(task);
        }
        return hostAddress;
    }

    public String getHostName() throws UnknownHostException {
        if (hostName == null) {
            ClusterTask<Object> task = getRemoteSessionTask(RemoteSessionTask.Operation.getHostName);
            hostName = (String) doSynchronousClusterTask(task);
        }
        return hostName;
    }

    public void deliverRawText(String text) {
        doClusterTask(getDeliverRawTextTask(text));
    }

    public boolean validate() {
        ClusterTask<Object> task = getRemoteSessionTask(RemoteSessionTask.Operation.validate);
        final Object clusterTaskResult = doSynchronousClusterTask(task);
        return clusterTaskResult == null ? false : (Boolean) clusterTaskResult;
    }

    abstract RemoteSessionTask getRemoteSessionTask(RemoteSessionTask.Operation operation);
    abstract ClusterTask getDeliverRawTextTask(String text);
    abstract ClusterTask getProcessPacketTask(Packet packet);

    /**
     * Invokes a task on the remote cluster member synchronously and returns the result of
     * the remote operation.
     *
     * @param task        the ClusterTask object to be invoked on a given cluster member.
     * @return result of remote operation.
     */
    protected Object doSynchronousClusterTask(ClusterTask<Object> task) {
        ClusterNodeInfo info = CacheFactory.getClusterNodeInfo(nodeID);
        Object result = null;
        if (info == null && task instanceof RemoteSessionTask) { // clean up invalid session
            Session remoteSession = ((RemoteSessionTask)task).getSession();
            if (remoteSession instanceof ClientSession) {
                SessionManager.getInstance().removeSession(null, remoteSession.getAddress(), false, false);
            }
        } else {
            result = (info == null) ? null : CacheFactory.doSynchronousClusterTask(task, nodeID);
        }
        return result;
    }

    /**
     * Invokes a task on the remote cluster member in an asynchronous fashion.
     *
     * @param task the task to be invoked on the specified cluster member.
     */
    protected void doClusterTask(ClusterTask task) {<FILL_FUNCTION_BODY>}

    @Override
    public final Locale getLanguage() {
        return Locale.getDefault();
    }
}

        ClusterNodeInfo info = CacheFactory.getClusterNodeInfo(nodeID);
        if (info == null && task instanceof RemoteSessionTask) { // clean up invalid session
            Session remoteSession = ((RemoteSessionTask)task).getSession();
            if (remoteSession instanceof ClientSession) {
                SessionManager.getInstance().removeSession(null, remoteSession.getAddress(), false, false);
            }
        } else {
            CacheFactory.doClusterTask(task, nodeID);
        }
    
<no_super_class>