Communicator.java

// Copyright (c) ZeroC, Inc.

package com.zeroc.Ice;

import com.zeroc.Ice.Instrumentation.CommunicatorObserver;
import com.zeroc.Ice.SSL.SSLEngineFactory;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;

/**
 * The central object in Ice. Its responsibilities include:
 * - creating and managing outgoing connections
 * - executing callbacks in its client thread pool
 * - creating and destroying object adapters
 * - loading plug-ins
 * - managing properties (configuration), retries, logging, instrumentation, and more.
 * You create a communicator with {@code Ice.initialize}, and it's usually the first object you create when programming
 * with Ice. You can create multiple communicators in a single program, but this is not common.
 *
 * @see Logger
 * @see ObjectAdapter
 * @see Properties
 */
public final class Communicator implements AutoCloseable {

    private final Instance _instance;

    /**
     * Destroy the communicator. This Java-only method overrides close in java.lang.AutoCloseable
     * and does not throw any exception.
     *
     * @see #destroy
     */
    public void close() {
        _instance.destroy(
            false); // Don't allow destroy to be interrupted if called from try with statement.
    }

    /**
     * Destroys this communicator. This method calls {@link #shutdown} implicitly. Calling {@link
     * #destroy} destroys all object adapters, and closes all outgoing connections. {@code destroy} waits for all
     * outstanding dispatches to complete before returning. This includes "bidirectional dispatches" that execute on
     * outgoing connections.
     *
     * @see #shutdown
     * @see ObjectAdapter#destroy
     */
    public void destroy() {
        _instance.destroy(true); // Destroy is interruptible when call explicitly.
    }

    /**
     * Shuts down this communicator. This method calls {@link ObjectAdapter#deactivate} on all object adapters
     * created by this communicator. Shutting down a communicator has no effect on outgoing
     * connections.
     *
     * @see #destroy
     * @see #waitForShutdown
     * @see ObjectAdapter#deactivate
     */
    public void shutdown() {
        try {
            _instance.objectAdapterFactory().shutdown();
        } catch (CommunicatorDestroyedException ex) {
            // Ignore
        }
    }

    /**
     * Waits for shutdown to complete. This method calls {@link ObjectAdapter#waitForDeactivate} on all object adapters
     * created by this communicator. In a client application that does not accept incoming connections, this
     * method returns as soon as another thread calls {@link #shutdown} or {@link #destroy} on this communicator.
     *
     * @see #shutdown
     * @see #destroy
     * @see ObjectAdapter#waitForDeactivate
     */
    public void waitForShutdown() {
        try {
            _instance.objectAdapterFactory().waitForShutdown();
        } catch (CommunicatorDestroyedException ex) {
            // Ignore
        }
    }

    /**
     * Checks whether or not {@link #shutdown} was called on this communicator.
     *
     * @return {@code true} if {@link #shutdown} was called on this communicator, {@code false} otherwise.
     * @see #shutdown
     */
    public boolean isShutdown() {
        try {
            return _instance.objectAdapterFactory().isShutdown();
        } catch (CommunicatorDestroyedException ex) {
            return true;
        }
    }

    /**
     * Converts a stringified proxy into a proxy.
     *
     * @param str The stringified proxy to convert into a proxy.
     * @return The proxy, or null if {@code str} is an empty string.
     * @throws ParseException Thrown when {@code str} is not a valid proxy string.
     * @see #proxyToString
     */
    public ObjectPrx stringToProxy(String str) {
        var ref = _instance.referenceFactory().create(str, null);
        return ref == null ? null : new _ObjectPrxI(ref);
    }

    /**
     * Converts a proxy into a string.
     *
     * @param proxy The proxy to convert into a stringified proxy.
     * @return The stringified proxy, or an empty string if {@code proxy} is null.
     * @see #stringToProxy
     */
    public String proxyToString(ObjectPrx proxy) {
        return proxy == null ? "" : proxy._getReference().toString();
    }

    /**
     * Converts a set of proxy properties into a proxy. The "base" name supplied in the {@code property} argument
     * refers to a property containing a stringified proxy, such as {@code MyProxy=id:tcp -h localhost -p 10000}.
     * Additional properties configure local settings for the proxy.
     *
     * @param property The base property name.
     * @return The proxy, or null if the property is not set.
     */
    public ObjectPrx propertyToProxy(String property) {
        String proxy = _instance.initializationData().properties.getProperty(property);
        var ref = _instance.referenceFactory().create(proxy, property);
        return ref == null ? null : new _ObjectPrxI(ref);
    }

    /**
     * Converts a proxy into a set of proxy properties.
     *
     * @param proxy The proxy.
     * @param prefix The base property name.
     * @return The property set.
     */
    public Map<String, String> proxyToProperty(ObjectPrx proxy, String prefix) {
        return proxy == null
            ? new HashMap<>()
            : proxy._getReference().toProperty(prefix);
    }

    /**
     * Converts an identity into a string.
     *
     * @param ident The identity to convert into a string.
     * @return The "stringified" identity.
     */
    public String identityToString(Identity ident) {
        return Util.identityToString(ident, _instance.toStringMode());
    }

    /**
     * Creates a new object adapter. The endpoints for the object adapter are taken from the property
     * {@code name.Endpoints}.
     *
     * <p>It is legal to create an object adapter with the empty string as its name. Such an object adapter is
     * accessible via bidirectional connections or by collocated invocations.
     *
     * @param name The object adapter name.
     * @return The new object adapter.
     * @see #createObjectAdapterWithEndpoints
     * @see ObjectAdapter
     * @see Properties
     */
    public ObjectAdapter createObjectAdapter(String name) {
        return createObjectAdapter(name, null);
    }

    /**
     * Creates a new object adapter. The endpoints for the object adapter are taken from the property
     * {@code name.Endpoints}.
     *
     * <p>It is legal to create an object adapter with the empty string as its name. Such an object adapter is
     * accessible via bidirectional connections or by collocated invocations.
     *
     * <p>It is an error to pass a non-null sslEngineFactory when the name is empty, this raises
     * IllegalArgumentException.
     *
     * @param name The object adapter name.
     * @param sslEngineFactory The SSL engine factory used by the server-side ssl transport of the
     *     new object adapter. When set to a non-null value all Ice.SSL configuration properties are
     *     ignored, and any SSL configuration must be done through the SSLEngineFactory. Pass null
     *     if the object adapter does not use secure endpoints, or if the ssl transport is
     *     configured through Ice.SSL configuration properties. Passing null is equivalent to
     *     calling {@link #createObjectAdapterWithEndpoints(String, String)}.
     * @return The new object adapter.
     * @see #createObjectAdapterWithEndpoints
     * @see ObjectAdapter
     * @see Properties
     */
    public ObjectAdapter createObjectAdapter(String name, SSLEngineFactory sslEngineFactory) {
        if (name.isEmpty() && sslEngineFactory != null) {
            throw new IllegalArgumentException(
                "name cannot be empty when using an SSLEngineFactory");
        }
        return _instance.objectAdapterFactory().createObjectAdapter(name, null, sslEngineFactory);
    }

    /**
     * Creates a new object adapter with endpoints. This method sets the property
     * {@code name.Endpoints}, and then calls {@link #createObjectAdapter}. It is provided
     * as a convenience method. Calling this method with an empty name will result in a UUID
     * being generated for the name.
     *
     * @param name The object adapter name.
     * @param endpoints The endpoints of the object adapter.
     * @return The new object adapter.
     * @see #createObjectAdapter
     * @see ObjectAdapter
     * @see Properties
     */
    public ObjectAdapter createObjectAdapterWithEndpoints(String name, String endpoints) {
        return createObjectAdapterWithEndpoints(name, endpoints, null);
    }

    /**
     * Creates a new object adapter with endpoints. This method sets the property
     * {@code name.Endpoints}, and then calls {@link #createObjectAdapter}. It is provided
     * as a convenience method. Calling this method with an empty name will result in a UUID
     * being generated for the name.
     *
     * @param name The object adapter name.
     * @param endpoints The endpoints of the object adapter.
     * @param sslEngineFactory The SSL engine factory used by the server-side ssl transport of the
     *     new object adapter. When set to a non-null value all Ice.SSL configuration properties are
     *     ignored, and any SSL configuration must be done through the SSLEngineFactory. Pass null
     *     if the object adapter does not use secure endpoints, or if the ssl transport is
     *     configured through Ice.SSL configuration properties. Passing null is equivalent to
     *     calling {@link #createObjectAdapterWithEndpoints(String, String)}.
     * @return The new object adapter.
     * @see #createObjectAdapter
     * @see ObjectAdapter
     * @see Properties
     */
    public ObjectAdapter createObjectAdapterWithEndpoints(
            String name, String endpoints, SSLEngineFactory sslEngineFactory) {
        if (name.isEmpty()) {
            name = UUID.randomUUID().toString();
        }

        getProperties().setProperty(name + ".Endpoints", endpoints);
        return _instance.objectAdapterFactory().createObjectAdapter(name, null, sslEngineFactory);
    }

    /**
     * Creates a new object adapter with a router. This method creates a routed object adapter.
     * Calling this method with an empty name will result in a UUID being generated for the name.
     *
     * @param name The object adapter name.
     * @param router The router.
     * @return The new object adapter.
     * @see #createObjectAdapter
     * @see ObjectAdapter
     * @see Properties
     */
    public ObjectAdapter createObjectAdapterWithRouter(String name, RouterPrx router) {
        if (name.isEmpty()) {
            name = UUID.randomUUID().toString();
        }

        //
        // We set the proxy properties here, although we still use the proxy supplied.
        //
        Map<String, String> properties = proxyToProperty(router, name + ".Router");
        for (Map.Entry<String, String> p : properties.entrySet()) {
            getProperties().setProperty(p.getKey(), p.getValue());
        }

        return _instance.objectAdapterFactory().createObjectAdapter(name, router, null);
    }

    /**
     * Gets the object adapter that is associated by default with new outgoing connections created
     * by this communicator. This method returns null unless you set a non-null default object
     * adapter using {@link setDefaultObjectAdapter}.
     *
     * @return The object adapter associated by default with new outgoing connections.
     * @see Connection#getAdapter
     */
    public ObjectAdapter getDefaultObjectAdapter() {
        return _instance.outgoingConnectionFactory().getDefaultObjectAdapter();
    }

    /**
     * Sets the object adapter that will be associated with new outgoing connections created by this
     * communicator. This method has no effect on existing outgoing connections, or on incoming
     * connections.
     *
     * @param adapter The object adapter to associate with new outgoing connections.
     * @see Connection#setAdapter
     */
    public void setDefaultObjectAdapter(ObjectAdapter adapter) {
        _instance.outgoingConnectionFactory().setDefaultObjectAdapter(adapter);
    }

    /**
     * Gets the implicit context associated with this communicator.
     *
     * @return The implicit context associated with this communicator; returns null when the
     *     property {@code Ice.ImplicitContext} is not set or is set to {@code None}.
     */
    public ImplicitContext getImplicitContext() {
        return _instance.getImplicitContext();
    }

    /**
     * Gets the properties of this communicator.
     *
     * @return This communicator's properties.
     * @see Properties
     */
    public Properties getProperties() {
        return _instance.initializationData().properties;
    }

    /**
     * Gets the logger of this communicator.
     *
     * @return This communicator's logger.
     * @see Logger
     */
    public Logger getLogger() {
        return _instance.initializationData().logger;
    }

    /**
     * Adds a Slice loader to this communicator, after the Slice loader set in {@link InitializationData}
     * (if any) and after other Slice loaders added by this method.
     *
     * <p>This method is not thread-safe and should only be called right after the communicator is created.
     * It's provided for applications that cannot set the Slice loader in the {@link InitializationData} of the
     * communicator, such as IceBox services.</p>
     *
     * @param loader The Slice loader to add.
     */
    public void addSliceLoader(SliceLoader loader) {
        _instance.addSliceLoader(loader);
    }

    /**
     * Gets the observer object of this communicator.
     *
     * @return This communicator's observer object.
     */
    public CommunicatorObserver getObserver() {
        return _instance.initializationData().observer;
    }

    /**
     * Gets the default router of this communicator.
     *
     * @return The default router of this communicator.
     * @see #setDefaultRouter
     * @see Router
     */
    public RouterPrx getDefaultRouter() {
        return _instance.referenceFactory().getDefaultRouter();
    }

    /**
     * Sets the default router of this communicator. All newly created proxies will use this default router.
     * This method has no effect on existing proxies.
     *
     * @param router The new default router. Use null to remove the default router.
     * @see #getDefaultRouter
     * @see #createObjectAdapterWithRouter
     * @see Router
     */
    public void setDefaultRouter(RouterPrx router) {
        _instance.setDefaultRouter(router);
    }

    /**
     * Gets the default locator of this communicator.
     *
     * @return The default locator of this communicator.
     * @see #setDefaultLocator
     * @see Locator
     */
    public LocatorPrx getDefaultLocator() {
        return _instance.referenceFactory().getDefaultLocator();
    }

    /**
     * Sets the default locator of this communicator. All newly created proxies will use this default locator.
     * This method has no effect on existing proxies or object adapters.
     *
     * @param locator The new default locator. Use null to remove the default locator.
     * @see #getDefaultLocator
     * @see Locator
     * @see ObjectAdapter#setLocator
     */
    public void setDefaultLocator(LocatorPrx locator) {
        _instance.setDefaultLocator(locator);
    }

    /**
     * Gets the plug-in manager of this communicator.
     *
     * @return This communicator's plug-in manager.
     * @see PluginManager
     */
    public PluginManager getPluginManager() {
        return _instance.pluginManager();
    }

    /**
     * Flushes any pending batch requests of this communicator. This means all batch requests invoked
     * on fixed proxies for all connections associated with the communicator. Errors that occur
     * while flushing a connection are ignored.
     *
     * @param compressBatch Specifies whether or not the queued batch requests should be compressed
     *     before being sent over the wire.
     */
    public void flushBatchRequests(CompressBatch compressBatch) {
        _iceI_flushBatchRequestsAsync(compressBatch).waitForResponse();
    }

    /**
     * Flushes any pending batch requests of this communicator. This means all batch requests invoked
     * on fixed proxies for all connections associated with the communicator. Errors that occur
     * while flushing a connection are ignored.
     *
     * @param compressBatch Specifies whether or not the queued batch requests should be compressed
     *     before being sent over the wire.
     * @return A future that will be completed when the invocation completes.
     */
    public CompletableFuture<Void> flushBatchRequestsAsync(
            CompressBatch compressBatch) {
        return _iceI_flushBatchRequestsAsync(compressBatch);
    }

    private CommunicatorFlushBatch _iceI_flushBatchRequestsAsync(CompressBatch compressBatch) {
        //
        // This callback object receives the results of all invocations
        // of Connection.begin_flushBatchRequests.
        //
        var f = new CommunicatorFlushBatch(this, _instance);
        f.invoke(compressBatch);
        return f;
    }

    /**
     * Adds the Admin object with all its facets to the provided object adapter. If
     * {@code Ice.Admin.ServerId} is set and the provided object adapter has a {@link Locator},
     * createAdmin registers the Admin's Process facet with the {@link Locator}'s {@link
     * LocatorRegistry}.
     *
     * @param adminAdapter The object adapter used to host the Admin object; if null and
     *     {@code Ice.Admin.Endpoints} is set, this method uses the {@code Ice.Admin} object adapter, after creating and
     *     activating this adapter.
     * @param adminId The identity of the Admin object.
     * @return A proxy to the main ("") facet of the Admin object.
     * @throws InitializationException Thrown when createAdmin is called more than once.
     * @see #getAdmin
     */
    public ObjectPrx createAdmin(ObjectAdapter adminAdapter, Identity adminId) {
        return _instance.createAdmin(adminAdapter, adminId);
    }

    /**
     * Gets a proxy to the main facet of the Admin object. getAdmin also creates the Admin object and creates and
     * activates the {@code Ice.Admin} object adapter to host this Admin object if {@code Ice.Admin.Endpoints} is set.
     * The identity of the Admin object created by getAdmin is {@code {value of Ice.Admin.InstanceName}/admin}, or
     * {@code {UUID}/admin} when {@code Ice.Admin.InstanceName} is not set. If {@code Ice.Admin.DelayCreation} is
     * {@code 0} or not set, getAdmin is called by the communicator initialization, after initialization of all plugins.
     *
     * @return A proxy to the main ("") facet of the Admin object, or null if no Admin
     *     object is configured.
     * @see #createAdmin
     */
    public ObjectPrx getAdmin() {
        return _instance.getAdmin();
    }

    /**
     * Adds a new facet to the Admin object.
     *
     * @param servant The servant that implements the new Admin facet.
     * @param facet The name of the new Admin facet.
     * @throws AlreadyRegisteredException Thrown when a facet with the same name is already registered.
     */
    public void addAdminFacet(Object servant, String facet) {
        _instance.addAdminFacet(servant, facet);
    }

    /**
     * Removes a facet from the Admin object.
     *
     * @param facet The name of the Admin facet.
     * @return The servant associated with this Admin facet.
     * @throws NotRegisteredException Thrown when no facet with the given name is registered.
     */
    public Object removeAdminFacet(String facet) {
        return _instance.removeAdminFacet(facet);
    }

    /**
     * Returns a facet of the Admin object.
     *
     * @param facet The name of the Admin facet.
     * @return The servant associated with this Admin facet, or null if no facet is registered with
     *     the given name.
     */
    public Object findAdminFacet(String facet) {
        return _instance.findAdminFacet(facet);
    }

    /**
     * Returns a map of all facets of the Admin object.
     *
     * @return A collection containing all the facet names and servants of the Admin object.
     * @see #findAdminFacet
     */
    public Map<String, Object> findAllAdminFacets() {
        return _instance.findAllAdminFacets();
    }

    Communicator(InitializationData initData) {
        _instance = new Instance();
        _instance.initialize(this, initData);
    }

    void finishSetup() {
        try {
            _instance.finishSetup(this);
        } catch (RuntimeException ex) {
            _instance.destroy(false);
            throw ex;
        }
    }

    /**
     * Get the {@code Instance} object associated with this communicator.
     *
     * @return the {@code Instance} object associated with this communicator
     * @hidden
     */
    public Instance getInstance() {
        return _instance;
    }
}