LocatorInfo.java

// Copyright (c) ZeroC, Inc.

package com.zeroc.Ice;

import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

final class LocatorInfo {
    interface GetEndpointsCallback {
        void setEndpoints(EndpointI[] endpoints, boolean cached);

        void setException(LocalException ex);
    }

    private static class RequestCallback {
        public void response(LocatorInfo locatorInfo, ObjectPrx proxy) {
            EndpointI[] endpoints = null;
            if (proxy != null) {
                Reference r = ((_ObjectPrxI) proxy)._getReference();
                if (_ref.isWellKnown()
                    && !Protocol.isSupported(_ref.getEncoding(), r.getEncoding())) {
                    //
                    // If a well-known proxy and the returned proxy encoding isn't supported, we're
                    // done: there's
                    // no compatible endpoint we can use.
                    //
                } else if (!r.isIndirect()) {
                    endpoints = r.getEndpoints();
                } else if (_ref.isWellKnown() && !r.isWellKnown()) {
                    //
                    // We're resolving the endpoints of a well-known object and the proxy returned
                    // by the locator is an indirect proxy. We now need to resolve the endpoints
                    // of this indirect proxy.
                    //
                    if (_ref.getInstance().traceLevels().location >= 1) {
                        locatorInfo.trace(
                            "retrieved adapter for well-known object from locator, "
                                + "adding to locator cache",
                            _ref,
                            r);
                    }
                    locatorInfo.getEndpoints(r, _ref, _ttl, _callback);
                    return;
                }
            }

            if (_ref.getInstance().traceLevels().location >= 1) {
                locatorInfo.getEndpointsTrace(_ref, endpoints, false);
            }
            if (_callback != null) {
                _callback.setEndpoints(endpoints == null ? new EndpointI[0] : endpoints, false);
            }
        }

        public void exception(LocatorInfo locatorInfo, Exception exc) {
            try {
                locatorInfo.getEndpointsException(_ref, exc); // This throws.
            } catch (LocalException ex) {
                if (_callback != null) {
                    _callback.setException(ex);
                }
            }
        }

        RequestCallback(Reference ref, Duration ttl, GetEndpointsCallback cb) {
            _ref = ref;
            _ttl = ttl;
            _callback = cb;
        }

        final Reference _ref;
        final Duration _ttl;
        final GetEndpointsCallback _callback;
    }

    private abstract class Request {
        public void addCallback(
                Reference ref, Reference wellKnownRef, Duration ttl, GetEndpointsCallback cb) {
            RequestCallback callback = new RequestCallback(ref, ttl, cb);
            synchronized (this) {
                if (!_response && _exception == null) {
                    _callbacks.add(callback);
                    if (wellKnownRef != null) {
                        // This request is to resolve the endpoints of a cached well-known object ref.
                        _wellKnownRefs.add(wellKnownRef);
                    }
                    if (!_sent) {
                        _sent = true;
                        send();
                    }
                    return;
                }
            }

            if (_response) {
                callback.response(_locatorInfo, _proxy);
            } else {
                assert (_exception != null);
                callback.exception(_locatorInfo, _exception);
            }
        }

        Request(LocatorInfo locatorInfo, Reference ref) {
            _locatorInfo = locatorInfo;
            _ref = ref;
            _sent = false;
            _response = false;
        }

        protected void response(ObjectPrx proxy) {
            synchronized (this) {
                _locatorInfo.finishRequest(_ref, _wellKnownRefs, proxy, false);
                _response = true;
                _proxy = proxy;
                notifyAll();
            }
            for (RequestCallback callback : _callbacks) {
                callback.response(_locatorInfo, proxy);
            }
        }

        protected void exception(Exception ex) {
            synchronized (this) {
                _locatorInfo.finishRequest(_ref, _wellKnownRefs, null, ex instanceof UserException);
                _exception = ex;
                notifyAll();
            }
            for (RequestCallback callback : _callbacks) {
                callback.exception(_locatorInfo, ex);
            }
        }

        protected abstract void send();

        protected final LocatorInfo _locatorInfo;
        protected final Reference _ref;

        private List<RequestCallback> _callbacks = new ArrayList<>();
        private List<Reference> _wellKnownRefs = new ArrayList<>();
        private boolean _sent;
        private boolean _response;
        private ObjectPrx _proxy;
        private Exception _exception;
    }

    private class ObjectRequest extends Request {
        public ObjectRequest(LocatorInfo locatorInfo, Reference reference) {
            super(locatorInfo, reference);
            assert (reference.isWellKnown());
        }

        @Override
        protected void send() {
            try {
                _locatorInfo
                    .getLocator()
                    .findObjectByIdAsync(_ref.getIdentity())
                    .whenComplete(
                        (ObjectPrx proxy, Throwable ex) -> {
                            if (ex != null) {
                                if (ex instanceof LocalException) {
                                    exception((LocalException) ex);
                                } else if (ex instanceof UserException) {
                                    exception((UserException) ex);
                                } else {
                                    exception(new UnknownException(ex));
                                }
                            } else {
                                response(proxy);
                            }
                        });
            } catch (Exception ex) {
                exception(ex);
            }
        }
    }

    private class AdapterRequest extends Request {
        public AdapterRequest(LocatorInfo locatorInfo, Reference reference) {
            super(locatorInfo, reference);
            assert (reference.isIndirect());
        }

        @Override
        protected void send() {
            try {
                _locatorInfo
                    .getLocator()
                    .findAdapterByIdAsync(_ref.getAdapterId())
                    .whenComplete(
                        (ObjectPrx proxy, Throwable ex) -> {
                            if (ex != null) {
                                if (ex instanceof LocalException) {
                                    exception((LocalException) ex);
                                } else if (ex instanceof UserException) {
                                    exception((UserException) ex);
                                } else {
                                    exception(new UnknownException(ex));
                                }
                            } else {
                                response(proxy);
                            }
                        });
            } catch (Exception ex) {
                exception(ex);
            }
        }
    }

    LocatorInfo(LocatorPrx locator, LocatorTable table, boolean background) {
        _locator = locator;
        _table = table;
        _background = background;
    }

    public synchronized void destroy() {
        _locatorRegistry = null;
        _table.clear();
    }

    @Override
    public boolean equals(java.lang.Object obj) {
        if (this == obj) {
            return true;
        }

        if (obj instanceof LocatorInfo) {
            return _locator.equals(((LocatorInfo) obj)._locator);
        }

        return false;
    }

    @Override
    public int hashCode() {
        return _locator.hashCode();
    }

    public LocatorPrx getLocator() {
        //
        // No synchronization necessary, _locator is immutable.
        //
        return _locator;
    }

    public LocatorRegistryPrx getLocatorRegistry() {
        synchronized (this) {
            if (_locatorRegistry != null) {
                return _locatorRegistry;
            }
        }

        //
        // Do not make locator calls from within sync.
        //
        LocatorRegistryPrx locatorRegistry = _locator.getRegistry();
        if (locatorRegistry == null) {
            return null;
        }

        synchronized (this) {
            //
            // The locator registry can't be located. We use ordered
            // endpoint selection in case the locator returned a proxy
            // with some endpoints which are preferred to be tried first.
            //
            _locatorRegistry =
                locatorRegistry
                    .ice_locator(null)
                    .ice_endpointSelection(EndpointSelectionType.Ordered);
            return _locatorRegistry;
        }
    }

    public void getEndpoints(Reference ref, Duration ttl, GetEndpointsCallback callback) {
        getEndpoints(ref, null, ttl, callback);
    }

    public void getEndpoints(
            Reference ref, Reference wellKnownRef, Duration ttl, GetEndpointsCallback callback) {
        assert (ref.isIndirect());
        EndpointI[] endpoints = null;
        Holder<Boolean> cached = new Holder<>();
        if (!ref.isWellKnown()) {
            endpoints = _table.getAdapterEndpoints(ref.getAdapterId(), ttl, cached);
            if (!cached.value) {
                if (_background && endpoints != null) {
                    getAdapterRequest(ref).addCallback(ref, wellKnownRef, ttl, null);
                } else {
                    getAdapterRequest(ref).addCallback(ref, wellKnownRef, ttl, callback);
                    return;
                }
            }
        } else {
            Reference r = _table.getObjectReference(ref.getIdentity(), ttl, cached);
            if (!cached.value) {
                if (_background && r != null) {
                    getObjectRequest(ref).addCallback(ref, null, ttl, null);
                } else {
                    getObjectRequest(ref).addCallback(ref, null, ttl, callback);
                    return;
                }
            }

            if (!r.isIndirect()) {
                endpoints = r.getEndpoints();
            } else if (!r.isWellKnown()) {
                if (ref.getInstance().traceLevels().location >= 1) {
                    trace("found adapter for well-known object in locator cache", ref, r);
                }
                getEndpoints(r, ref, ttl, callback);
                return;
            }
        }

        assert (endpoints != null);
        if (ref.getInstance().traceLevels().location >= 1) {
            getEndpointsTrace(ref, endpoints, true);
        }
        if (callback != null) {
            callback.setEndpoints(endpoints, true);
        }
    }

    public void clearCache(Reference ref) {
        assert (ref.isIndirect());

        if (!ref.isWellKnown()) {
            EndpointI[] endpoints = _table.removeAdapterEndpoints(ref.getAdapterId());

            if (endpoints != null && ref.getInstance().traceLevels().location >= 2) {
                trace("removed endpoints for adapter from locator cache", ref, endpoints);
            }
        } else {
            Reference r = _table.removeObjectReference(ref.getIdentity());
            if (r != null) {
                if (!r.isIndirect()) {
                    if (ref.getInstance().traceLevels().location >= 2) {
                        trace(
                            "removed endpoints for well-known object from locator cache",
                            ref,
                            r.getEndpoints());
                    }
                } else if (!r.isWellKnown()) {
                    if (ref.getInstance().traceLevels().location >= 2) {
                        trace("removed adapter for well-known object from locator cache", ref, r);
                    }
                    clearCache(r);
                }
            }
        }
    }

    private void trace(String msg, Reference ref, EndpointI[] endpoints) {
        assert (ref.isIndirect());

        StringBuilder s = new StringBuilder(128);
        s.append(msg);
        s.append("\n");
        if (!ref.isWellKnown()) {
            s.append("adapter = ");
            s.append(ref.getAdapterId());
            s.append("\n");
        } else {
            s.append("well-known proxy = ");
            s.append(ref.toString());
            s.append("\n");
        }

        s.append("endpoints = ");
        final int sz = endpoints.length;
        for (int i = 0; i < sz; i++) {
            s.append(endpoints[i].toString());
            if (i + 1 < sz) {
                s.append(':');
            }
        }

        ref.getInstance()
            .initializationData()
            .logger
            .trace(ref.getInstance().traceLevels().locationCat, s.toString());
    }

    private void trace(String msg, Reference ref, Reference resolved) {
        assert (ref.isWellKnown());

        StringBuilder s = new StringBuilder(128);
        s.append(msg);
        s.append("\n");
        s.append("well-known proxy = ");
        s.append(ref.toString());
        s.append("\n");
        s.append("adapter = ");
        s.append(resolved.getAdapterId());

        ref.getInstance()
            .initializationData()
            .logger
            .trace(ref.getInstance().traceLevels().locationCat, s.toString());
    }

    private void getEndpointsException(Reference ref, Exception exc) {
        assert (ref.isIndirect());

        try {
            throw exc;
        } catch (AdapterNotFoundException ex) {
            final Instance instance = ref.getInstance();
            if (instance.traceLevels().location >= 1) {
                StringBuilder s = new StringBuilder(128);
                s.append("adapter not found\n");
                s.append("adapter = ");
                s.append(ref.getAdapterId());
                instance.initializationData()
                    .logger
                    .trace(instance.traceLevels().locationCat, s.toString());
            }

            throw new NotRegisteredException("object adapter", ref.getAdapterId());
        } catch (ObjectNotFoundException ex) {
            final Instance instance = ref.getInstance();
            if (instance.traceLevels().location >= 1) {
                StringBuilder s = new StringBuilder(128);
                s.append("object not found\n");
                s.append("object = ");
                s.append(Util.identityToString(ref.getIdentity(), instance.toStringMode()));
                instance.initializationData()
                    .logger
                    .trace(instance.traceLevels().locationCat, s.toString());
            }

            final String id = Util.identityToString(ref.getIdentity(), instance.toStringMode());
            throw new NotRegisteredException("object", id);
        } catch (NotRegisteredException ex) {
            throw ex;
        } catch (LocalException ex) {
            final Instance instance = ref.getInstance();
            if (instance.traceLevels().location >= 1) {
                StringBuilder s = new StringBuilder(128);
                s.append("couldn't contact the locator to retrieve endpoints\n");
                if (ref.getAdapterId().length() > 0) {
                    s.append("adapter = ");
                    s.append(ref.getAdapterId());
                    s.append("\n");
                } else {
                    s.append("well-known proxy = ");
                    s.append(ref.toString());
                    s.append("\n");
                }
                s.append("reason = ").append(ex);
                instance.initializationData()
                    .logger
                    .trace(instance.traceLevels().locationCat, s.toString());
            }
            throw ex;
        } catch (Exception ex) {
            assert false;
        }
    }

    private void getEndpointsTrace(Reference ref, EndpointI[] endpoints, boolean cached) {
        if (endpoints != null && endpoints.length > 0) {
            if (cached) {
                if (ref.isWellKnown()) {
                    trace("found endpoints for well-known proxy in locator cache", ref, endpoints);
                } else {
                    trace("found endpoints for adapter in locator cache", ref, endpoints);
                }
            } else {
                if (ref.isWellKnown()) {
                    trace(
                        "retrieved endpoints for well-known proxy from locator, adding to locator cache",
                        ref,
                        endpoints);
                } else {
                    trace(
                        "retrieved endpoints for adapter from locator, adding to locator cache",
                        ref,
                        endpoints);
                }
            }
        } else {
            final Instance instance = ref.getInstance();
            StringBuilder s = new StringBuilder(128);
            s.append("no endpoints configured for ");
            if (ref.getAdapterId().length() > 0) {
                s.append("adapter\n");
                s.append("adapter = ");
                s.append(ref.getAdapterId());
                s.append("\n");
            } else {
                s.append("well-known object\n");
                s.append("well-known proxy = ");
                s.append(ref.toString());
                s.append("\n");
            }
            instance.initializationData()
                .logger
                .trace(instance.traceLevels().locationCat, s.toString());
        }
    }

    private synchronized Request getAdapterRequest(Reference ref) {
        if (ref.getInstance().traceLevels().location >= 1) {
            Instance instance = ref.getInstance();
            StringBuilder s = new StringBuilder(128);
            s.append("searching for adapter by id\n");
            s.append("adapter = ");
            s.append(ref.getAdapterId());
            instance.initializationData()
                .logger
                .trace(instance.traceLevels().locationCat, s.toString());
        }

        Request request = _adapterRequests.get(ref.getAdapterId());
        if (request != null) {
            return request;
        }
        request = new AdapterRequest(this, ref);
        _adapterRequests.put(ref.getAdapterId(), request);
        return request;
    }

    private synchronized Request getObjectRequest(Reference ref) {
        if (ref.getInstance().traceLevels().location >= 1) {
            Instance instance = ref.getInstance();
            StringBuilder s = new StringBuilder(128);
            s.append("searching for well-known object\n");
            s.append("well-known proxy = ");
            s.append(ref.toString());
            instance.initializationData()
                .logger
                .trace(instance.traceLevels().locationCat, s.toString());
        }

        Request request = _objectRequests.get(ref.getIdentity());
        if (request != null) {
            return request;
        }
        request = new ObjectRequest(this, ref);
        _objectRequests.put(ref.getIdentity(), request);
        return request;
    }

    private void finishRequest(
            Reference ref,
            List<Reference> wellKnownRefs,
            ObjectPrx proxy,
            boolean notRegistered) {
        if (proxy == null || ((_ObjectPrxI) proxy)._getReference().isIndirect()) {
            //
            // Remove the cached references of well-known objects for which we tried
            // to resolved the endpoints if these endpoints are empty.
            //
            for (Reference r : wellKnownRefs) {
                _table.removeObjectReference(r.getIdentity());
            }
        }

        if (!ref.isWellKnown()) {
            if (proxy != null && !((_ObjectPrxI) proxy)._getReference().isIndirect()) {
                // Cache the adapter endpoints.
                _table.addAdapterEndpoints(
                    ref.getAdapterId(), ((_ObjectPrxI) proxy)._getReference().getEndpoints());
            } else if (notRegistered) {
                // If the adapter isn't registered anymore, remove it from the cache.
                _table.removeAdapterEndpoints(ref.getAdapterId());
            }

            synchronized (this) {
                assert (_adapterRequests.get(ref.getAdapterId()) != null);
                _adapterRequests.remove(ref.getAdapterId());
            }
        } else {
            if (proxy != null && !((_ObjectPrxI) proxy)._getReference().isWellKnown()) {
                // Cache the well-known object reference.
                _table.addObjectReference(ref.getIdentity(), ((_ObjectPrxI) proxy)._getReference());
            } else if (notRegistered) {
                // If the well-known object isn't registered anymore, remove it from the cache.
                _table.removeObjectReference(ref.getIdentity());
            }

            synchronized (this) {
                assert (_objectRequests.get(ref.getIdentity()) != null);
                _objectRequests.remove(ref.getIdentity());
            }
        }
    }

    private final LocatorPrx _locator;
    private LocatorRegistryPrx _locatorRegistry;
    private final LocatorTable _table;
    private final boolean _background;

    private final Map<String, Request> _adapterRequests = new HashMap<>();
    private final Map<Identity, Request> _objectRequests = new HashMap<>();
}