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.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;

/**
 * Communicator is 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.
 * A communicator is 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;

    /**
     * Constructs a communicator with the specified options.
     *
     * @param initData the options for the new communicator
     */
    public Communicator(InitializationData initData) {
        _instance = new Instance();
        _instance.initialize(this, initData.clone());

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

    /**
     * Constructs a communicator with default options.
     */
    public Communicator() {
        this(new InitializationData());
    }

    /**
     * Constructs a communicator, using Ice properties parsed from command-line arguments.
     * This constructor uses {@code args} to create the {@link Properties} of the new communicator.
     *
     * @param args the command-line arguments
     * @param remainingArgs if non-null, the remaining command-line arguments after parsing Ice properties
     */
    public Communicator(String[] args, List<String> remainingArgs) {
        this(createInitializationData(args, remainingArgs));
    }

    /**
     * Constructs a communicator, using Ice properties parsed from command-line arguments.
     * This constructor uses {@code args} to create the {@link Properties} of the new communicator.
     *
     * @param args the command-line arguments
     */
    public Communicator(String[] args) {
        this(args, null);
    }

    /**
     * Closes this communicator. This method calls {@link #shutdown} implicitly. Calling this method destroys
     * all object adapters, and closes all outgoing connections. This method waits for all outstanding dispatches
     * to complete before returning. This includes "bidirectional dispatches" that execute on outgoing connections.
     *
     * @see ObjectAdapter#destroy
     */
    public void close() {
        // Don't allow destroy to be interrupted if called from try with statement.
        _instance.destroy(false);
    }

    /**
     * Destroys this communicator. It's an alias for {@link #close}.
     */
    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 #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 #close} on this communicator.
     *
     * @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 if {@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 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.
     *
     * @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
     * @throws IllegalArgumentException if the provided name is empty and sslEngineFactory is non-null
     * @see #createObjectAdapterWithEndpoints
     * @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 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 {@code Ice.SSL} configuration properties are
     *     ignored, and any SSL configuration must be done through the {@code SSLEngineFactory}. Pass {@code null}
     *     if the object adapter does not use secure endpoints, or if the ssl transport is configured
     *     through {@code Ice.SSL} configuration properties. Passing {@code null} is equivalent to
     *     calling {@link #createObjectAdapterWithEndpoints(String, String)}.
     * @return the new object adapter
     * @see #createObjectAdapter
     * @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 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
     * @throws CommunicatorDestroyedException if the communicator has been destroyed
     * @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.
     *
     * @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
     * @throws CommunicatorDestroyedException if the communicator has been destroyed
     * @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 {@code 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 {@code 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
     * @throws CommunicatorDestroyedException if this communicator has been destroyed
     * @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},
     * this method 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 it is 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 if this method 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. {@code 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 {@code 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,
     * {@code 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 if 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 if 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();
    }

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

    private static InitializationData createInitializationData(String[] args, List<String> remainingArgs) {
        var properties = new Properties(args, remainingArgs);
        var initData = new InitializationData();
        initData.properties = properties;
        return initData;
    }
}