ReferenceFactory.java

// Copyright (c) ZeroC, Inc.

package com.zeroc.Ice;

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

final class ReferenceFactory {
    public Reference create(Identity ident, String facet, Reference tmpl, EndpointI[] endpoints) {
        if (ident.name.isEmpty() && ident.category.isEmpty()) {
            return null;
        }

        return create(
            ident,
            facet,
            tmpl.getMode(),
            tmpl.getCompress(),
            tmpl.getProtocol(),
            tmpl.getEncoding(),
            endpoints,
            null,
            null);
    }

    public Reference create(Identity ident, String facet, Reference tmpl, String adapterId) {
        if (ident.name.isEmpty() && ident.category.isEmpty()) {
            return null;
        }

        return create(
            ident,
            facet,
            tmpl.getMode(),
            tmpl.getCompress(),
            tmpl.getProtocol(),
            tmpl.getEncoding(),
            null,
            adapterId,
            null);
    }

    public Reference create(Identity ident, ConnectionI fixedConnection) {
        if (ident.name.isEmpty() && ident.category.isEmpty()) {
            return null;
        }

        //
        // Create new reference
        //
        return new FixedReference(
            _instance,
            _communicator,
            ident,
            "", // Facet
            fixedConnection.endpoint().datagram() ? Reference.ModeDatagram : Reference.ModeTwoway,
            Optional.empty(),
            Util.Protocol_1_0,
            _instance.defaultsAndOverrides().defaultEncoding,
            fixedConnection,
            Duration.ofMillis(-1),
            null);
    }

    public Reference copy(Reference r) {
        Identity ident = r.getIdentity();
        if (ident.name.isEmpty() && ident.category.isEmpty()) {
            return null;
        }
        return r.clone();
    }

    public Reference create(String s, String propertyPrefix) {
        if (s == null || s.isEmpty()) {
            return null;
        }

        final String delim = " \t\n\r";

        int beg;
        int end = 0;

        beg = StringUtil.findFirstNotOf(s, delim, end);
        if (beg == -1) {
            throw new ParseException("no non-whitespace characters found in proxy string '" + s + "'");
        }

        //
        // Extract the identity, which may be enclosed in single
        // or double quotation marks.
        //
        String idstr = null;
        end = StringUtil.checkQuote(s, beg);
        if (end == -1) {
            throw new ParseException("mismatched quotes around identity in proxy string '" + s + "'");
        } else if (end == 0) {
            end = StringUtil.findFirstOf(s, delim + ":@", beg);
            if (end == -1) {
                end = s.length();
            }
            idstr = s.substring(beg, end);
        } else {
            beg++; // Skip leading quote
            idstr = s.substring(beg, end);
            end++; // Skip trailing quote
        }

        if (beg == end) {
            throw new ParseException("no identity in proxy string '" + s + "'");
        }

        // Parsing the identity may raise ParseException.
        Identity ident = Util.stringToIdentity(idstr);

        if (ident.name.isEmpty()) {
            // An identity with an empty name and a non-empty category is illegal.
            if (ident.category.length() > 0) {
                throw new ParseException("The category of a null Ice object identity must be empty.");
            } else if (StringUtil.findFirstNotOf(s, delim, end) != -1) {
                throw new ParseException("invalid characters after identity in proxy string '" + s + "'");
            } else {
                return null;
            }
        }

        String facet = "";
        int mode = Reference.ModeTwoway;
        EncodingVersion encoding = _instance.defaultsAndOverrides().defaultEncoding;
        ProtocolVersion protocol = Util.Protocol_1_0;
        String adapter = "";

        while (true) {
            beg = StringUtil.findFirstNotOf(s, delim, end);
            if (beg == -1) {
                break;
            }

            if (s.charAt(beg) == ':' || s.charAt(beg) == '@') {
                break;
            }

            end = StringUtil.findFirstOf(s, delim + ":@", beg);
            if (end == -1) {
                end = s.length();
            }

            if (beg == end) {
                break;
            }

            String option = s.substring(beg, end);
            if (option.length() != 2 || option.charAt(0) != '-') {
                throw new ParseException(
                    "expected a proxy option but found '"
                        + option
                        + "' in proxy string '"
                        + s
                        + "'");
            }

            // Check for the presence of an option argument.
            // The argument may be enclosed in single or double quotation marks.
            String argument = null;
            int argumentBeg = StringUtil.findFirstNotOf(s, delim, end);
            if (argumentBeg != -1) {
                final char ch = s.charAt(argumentBeg);
                if (ch != '@' && ch != ':' && ch != '-') {
                    beg = argumentBeg;
                    end = StringUtil.checkQuote(s, beg);
                    if (end == -1) {
                        throw new ParseException(
                            "mismatched quotes around value for "
                                + option
                                + " option in proxy string '"
                                + s
                                + "'");
                    } else if (end == 0) {
                        end = StringUtil.findFirstOf(s, delim + ":@", beg);
                        if (end == -1) {
                            end = s.length();
                        }
                        argument = s.substring(beg, end);
                    } else {
                        beg++; // Skip leading quote
                        argument = s.substring(beg, end);
                        end++; // Skip trailing quote
                    }
                }
            }

            // If any new options are added here,
            // com.zeroc.Ice.Reference.toString() and its derived classes must be updated as well.
            switch (option.charAt(1)) {
                case 'f': {
                    if (argument == null) {
                        throw new ParseException("no argument provided for -f option in proxy string '" + s + "'");
                    }

                    try {
                        facet = StringUtil.unescapeString(argument, 0, argument.length(), "");
                    } catch (IllegalArgumentException ex) {
                        throw new ParseException("invalid facet in proxy string '" + s + "'", ex);
                    }

                    break;
                }

                case 't': {
                    if (argument != null) {
                        throw new ParseException(
                            "unexpected argument '"
                                + argument
                                + "' provided for -t option in proxy string '"
                                + s
                                + "'");
                    }
                    mode = Reference.ModeTwoway;
                    break;
                }

                case 'o': {
                    if (argument != null) {
                        throw new ParseException(
                            "unexpected argument '"
                                + argument
                                + "' provided for -o option in proxy string '"
                                + s
                                + "'");
                    }
                    mode = Reference.ModeOneway;
                    break;
                }

                case 'O': {
                    if (argument != null) {
                        throw new ParseException(
                            "unexpected argument '"
                                + argument
                                + "' provided for -O option in proxy string '"
                                + s
                                + "'");
                    }
                    mode = Reference.ModeBatchOneway;
                    break;
                }

                case 'd': {
                    if (argument != null) {
                        throw new ParseException(
                            "unexpected argument '"
                                + argument
                                + "' provided for -d option in proxy string '"
                                + s
                                + "'");
                    }
                    mode = Reference.ModeDatagram;
                    break;
                }

                case 'D': {
                    if (argument != null) {
                        throw new ParseException(
                            "unexpected argument '"
                                + argument
                                + "' provided for -D option in proxy string '"
                                + s
                                + "'");
                    }
                    mode = Reference.ModeBatchDatagram;
                    break;
                }

                case 's': {
                    if (argument != null) {
                        throw new ParseException(
                            "unexpected argument '"
                                + argument
                                + "' provided for -s option in proxy string '"
                                + s
                                + "'");
                    }
                    // Ignored. Only kept for backwards compatibility.
                    break;
                }

                case 'e': {
                    if (argument == null) {
                        throw new ParseException("no argument provided for -e option in in proxy string '" + s + "'");
                    }

                    try {
                        encoding = Util.stringToEncodingVersion(argument);
                    } catch (ParseException ex) {
                        throw new ParseException(
                            "invalid encoding version '"
                                + argument
                                + "' in proxy string '"
                                + s
                                + "'",
                            ex);
                    }
                    break;
                }

                case 'p': {
                    if (argument == null) {
                        throw new ParseException("no argument provided for -p option in proxy string '" + s + "'");
                    }

                    try {
                        protocol = Util.stringToProtocolVersion(argument);
                    } catch (ParseException ex) {
                        throw new ParseException(
                            "invalid protocol version '"
                                + argument
                                + "' in proxy string '"
                                + s
                                + "'",
                            ex);
                    }
                    break;
                }

                default: {
                    throw new ParseException("unknown option '" + option + "' in proxy string '" + s + "'");
                }
            }
        }

        if (beg == -1) {
            return create(ident, facet, mode, Optional.empty(), protocol, encoding, null, null, propertyPrefix);
        }

        ArrayList<EndpointI> endpoints = new ArrayList<>();

        if (s.charAt(beg) == ':') {
            ArrayList<String> unknownEndpoints = new ArrayList<>();
            end = beg;

            while (end < s.length() && s.charAt(end) == ':') {
                beg = end + 1;

                end = beg;
                while (true) {
                    end = s.indexOf(':', end);
                    if (end == -1) {
                        end = s.length();
                        break;
                    } else {
                        boolean quoted = false;
                        int quote = beg;
                        while (true) {
                            quote = s.indexOf('\"', quote);
                            if (quote == -1 || end < quote) {
                                break;
                            } else {
                                quote = s.indexOf('\"', ++quote);
                                if (quote == -1) {
                                    break;
                                } else if (end < quote) {
                                    quoted = true;
                                    break;
                                }
                                ++quote;
                            }
                        }
                        if (!quoted) {
                            break;
                        }
                        ++end;
                    }
                }

                String es = s.substring(beg, end);
                EndpointI endp = _instance.endpointFactoryManager().create(es, false);
                if (endp != null) {
                    endpoints.add(endp);
                } else {
                    unknownEndpoints.add(es);
                }
            }
            if (endpoints.isEmpty()) {
                assert (!unknownEndpoints.isEmpty());
                throw new ParseException(
                    "invalid endpoint '" + unknownEndpoints.get(0) + "' in '" + s + "'");
            } else if (!unknownEndpoints.isEmpty()
                && _instance
                .initializationData()
                .properties
                .getIcePropertyAsInt("Ice.Warn.Endpoints")
                > 0) {
                StringBuffer msg = new StringBuffer("Proxy contains unknown endpoints:");
                for (String e : unknownEndpoints) {
                    msg.append(" `");
                    msg.append(e);
                    msg.append("'");
                }
                _instance.initializationData().logger.warning(msg.toString());
            }

            EndpointI[] endp = new EndpointI[endpoints.size()];
            endpoints.toArray(endp);
            return create(
                ident,
                facet,
                mode,
                Optional.empty(),
                protocol,
                encoding,
                endp,
                null,
                propertyPrefix);
        } else if (s.charAt(beg) == '@') {
            beg = StringUtil.findFirstNotOf(s, delim, beg + 1);
            if (beg == -1) {
                throw new ParseException("missing adapter ID in proxy string '" + s + "'");
            }

            String adapterstr = null;
            end = StringUtil.checkQuote(s, beg);
            if (end == -1) {
                throw new ParseException(
                    "mismatched quotes around adapter ID in proxy string '" + s + "'");
            } else if (end == 0) {
                end = StringUtil.findFirstOf(s, delim, beg);
                if (end == -1) {
                    end = s.length();
                }
                adapterstr = s.substring(beg, end);
            } else {
                beg++; // Skip leading quote
                adapterstr = s.substring(beg, end);
                end++; // Skip trailing quote
            }

            if (end != s.length() && StringUtil.findFirstNotOf(s, delim, end) != -1) {
                throw new ParseException(
                    "invalid characters after adapter ID in proxy string '" + s + "'");
            }

            try {
                adapter = StringUtil.unescapeString(adapterstr, 0, adapterstr.length(), "");
            } catch (IllegalArgumentException ex) {
                throw new ParseException("invalid adapter ID in proxy string '" + s + "'", ex);
            }
            if (adapter.isEmpty()) {
                throw new ParseException("empty adapter ID in proxy string '" + s + "'");
            }
            return create(
                ident,
                facet,
                mode,
                Optional.empty(),
                protocol,
                encoding,
                null,
                adapter,
                propertyPrefix);
        }

        throw new ParseException("malformed proxy string '" + s + "'");
    }

    public Reference create(Identity ident, InputStream s) {
        //
        // Don't read the identity here. Operations calling this
        // constructor read the identity, and pass it as a parameter.
        //

        if (ident.name.isEmpty() && ident.category.isEmpty()) {
            return null;
        }

        //
        // For compatibility with the old FacetPath.
        //
        String[] facetPath = s.readStringSeq();
        String facet;
        if (facetPath.length > 0) {
            if (facetPath.length > 1) {
                throw new MarshalException(
                    "Received invalid facet path with " + facetPath.length + " elements.");
            }
            facet = facetPath[0];
        } else {
            facet = "";
        }

        int mode = s.readByte();
        if (mode < 0 || mode > Reference.ModeLast) {
            throw new MarshalException("Received invalid proxy mode " + mode);
        }

        s.readBool(); // read and ignore secure field

        ProtocolVersion protocol;
        EncodingVersion encoding;
        if (!s.getEncoding().equals(Util.Encoding_1_0)) {
            protocol = ProtocolVersion.ice_read(s);
            encoding = EncodingVersion.ice_read(s);
        } else {
            protocol = Util.Protocol_1_0;
            encoding = Util.Encoding_1_0;
        }

        EndpointI[] endpoints = null;
        String adapterId = null;

        int sz = s.readSize();
        if (sz > 0) {
            endpoints = new EndpointI[sz];
            for (int i = 0; i < sz; i++) {
                endpoints[i] = _instance.endpointFactoryManager().read(s);
            }
        } else {
            adapterId = s.readString();
        }

        return create(
            ident,
            facet,
            mode,
            Optional.empty(),
            protocol,
            encoding,
            endpoints,
            adapterId,
            null);
    }

    public ReferenceFactory setDefaultRouter(RouterPrx defaultRouter) {
        if (_defaultRouter == null ? defaultRouter == null : _defaultRouter.equals(defaultRouter)) {
            return this;
        }

        ReferenceFactory factory = new ReferenceFactory(_instance, _communicator);
        factory._defaultLocator = _defaultLocator;
        factory._defaultRouter = defaultRouter;
        return factory;
    }

    public RouterPrx getDefaultRouter() {
        return _defaultRouter;
    }

    public ReferenceFactory setDefaultLocator(LocatorPrx defaultLocator) {
        if (_defaultLocator == null
            ? defaultLocator == null
            : _defaultLocator.equals(defaultLocator)) {
            return this;
        }

        ReferenceFactory factory = new ReferenceFactory(_instance, _communicator);
        factory._defaultRouter = _defaultRouter;
        factory._defaultLocator = defaultLocator;
        return factory;
    }

    public LocatorPrx getDefaultLocator() {
        return _defaultLocator;
    }

    //
    // Only for use by Instance
    //
    ReferenceFactory(Instance instance, Communicator communicator) {
        _instance = instance;
        _communicator = communicator;
    }

    private Reference create(
            Identity ident,
            String facet,
            int mode,
            Optional<Boolean> compress,
            ProtocolVersion protocol,
            EncodingVersion encoding,
            EndpointI[] endpoints,
            String adapterId,
            String propertyPrefix) {
        DefaultsAndOverrides defaultsAndOverrides = _instance.defaultsAndOverrides();

        //
        // Default local proxy options.
        //
        LocatorInfo locatorInfo = null;
        if (_defaultLocator != null) {
            if (!((_ObjectPrxI) _defaultLocator)._getReference().getEncoding().equals(encoding)) {
                locatorInfo =
                    _instance
                        .locatorManager()
                        .get(_defaultLocator.ice_encodingVersion(encoding));
            } else {
                locatorInfo = _instance.locatorManager().get(_defaultLocator);
            }
        }
        RouterInfo routerInfo = _instance.routerManager().get(_defaultRouter);
        boolean collocationOptimized = defaultsAndOverrides.defaultCollocationOptimization;
        boolean cacheConnection = true;
        EndpointSelectionType endpointSelection = defaultsAndOverrides.defaultEndpointSelection;
        Duration locatorCacheTimeout = defaultsAndOverrides.defaultLocatorCacheTimeout;
        Duration invocationTimeout = defaultsAndOverrides.defaultInvocationTimeout;
        Map<String, String> context = null;

        //
        // Override the defaults with the proxy properties if a property prefix is defined.
        //
        if (propertyPrefix != null && !propertyPrefix.isEmpty()) {
            Properties properties = _instance.initializationData().properties;

            Properties.validatePropertiesWithPrefix(
                propertyPrefix, properties, PropertyNames.ProxyProps);

            String property;

            property = propertyPrefix + ".Locator";
            LocatorPrx locator = LocatorPrx.uncheckedCast(_communicator.propertyToProxy(property));
            if (locator != null) {
                if (!((_ObjectPrxI) locator)._getReference().getEncoding().equals(encoding)) {
                    locatorInfo =
                        _instance.locatorManager().get(locator.ice_encodingVersion(encoding));
                } else {
                    locatorInfo = _instance.locatorManager().get(locator);
                }
            }

            property = propertyPrefix + ".Router";
            RouterPrx router = RouterPrx.uncheckedCast(_communicator.propertyToProxy(property));
            if (router != null) {
                if (propertyPrefix.endsWith(".Router")) {
                    String s =
                        "`"
                            + property
                            + "="
                            + properties.getProperty(property)
                            + "': cannot set a router on a router; setting ignored";
                    _instance.initializationData().logger.warning(s);
                } else {
                    routerInfo = _instance.routerManager().get(router);
                }
            }

            property = propertyPrefix + ".CollocationOptimized";
            collocationOptimized =
                properties.getPropertyAsIntWithDefault(property, collocationOptimized ? 1 : 0)
                    > 0;

            property = propertyPrefix + ".ConnectionCached";
            cacheConnection =
                properties.getPropertyAsIntWithDefault(property, cacheConnection ? 1 : 0) > 0;

            property = propertyPrefix + ".EndpointSelection";
            if (properties.getProperty(property).length() > 0) {
                String type = properties.getProperty(property);
                if ("Random".equals(type)) {
                    endpointSelection = EndpointSelectionType.Random;
                } else if ("Ordered".equals(type)) {
                    endpointSelection = EndpointSelectionType.Ordered;
                } else {
                    throw new ParseException(
                        "illegal value '"
                            + type
                            + "' in property '"
                            + property
                            + "'; expected 'Random' or 'Ordered'");
                }
            }

            property = propertyPrefix + ".LocatorCacheTimeout";
            locatorCacheTimeout =
                Duration.ofSeconds(
                    properties.getPropertyAsIntWithDefault(
                        property, (int) locatorCacheTimeout.toSeconds()));

            property = propertyPrefix + ".InvocationTimeout";
            invocationTimeout =
                Duration.ofMillis(
                    properties.getPropertyAsIntWithDefault(
                        property, (int) invocationTimeout.toMillis()));

            property = propertyPrefix + ".Context.";
            Map<String, String> contexts = properties.getPropertiesForPrefix(property);
            if (!contexts.isEmpty()) {
                context = new HashMap<>();
                for (Map.Entry<String, String> e : contexts.entrySet()) {
                    context.put(e.getKey().substring(property.length()), e.getValue());
                }
            }
        }

        //
        // Create new reference
        //
        return new RoutableReference(
            _instance,
            _communicator,
            ident,
            facet,
            mode,
            compress,
            protocol,
            encoding,
            endpoints,
            adapterId,
            locatorInfo,
            routerInfo,
            collocationOptimized,
            cacheConnection,
            endpointSelection,
            locatorCacheTimeout,
            invocationTimeout,
            context);
    }

    private final Instance _instance;
    private final Communicator _communicator;
    private RouterPrx _defaultRouter;
    private LocatorPrx _defaultLocator;
}