Автономное клиентское приложение JBoss EJB — как получить соединение JNDI — отличное имя

Существует множество разочаровывающе некорректной (лучшее описание — «близко, но не сигара») информации, касающейся удаленного доступа к компонентам JBoss EJB из автономного приложения. Я уже больше суток бьюсь головой об эту стену, но безрезультатно.

Я пытаюсь перенести EJB из WebLogic в JBoss, который вызывается автономным приложением, работающим на другом сервере.

Я был здесь, здесь и в нескольких других местах безуспешно ищу различные "решения" моей проблемы. Я попытался прочитать официальная документация, которая требует, чтобы я установил "быстрый запуск" на основе Maven, который может соответствовать или не соответствовать моей ситуации, и который я пока решил не использовать. (Мой проект не построен с помощью Maven, он использует Gradle, но я достаточно уверен, что мне удалось развернуть все нужные зависимости).

У меня есть EJB с отслеживанием состояния, развернутый в WAR внутри EAR (предыдущая реализация развертывания его просто в WAR не помогла).

Я настраиваю клиента таким образом:

        public InitialContext createInitialContext() throws NamingException {
            Properties prop = new Properties();
            prop.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");

            prop.put(Context.INITIAL_CONTEXT_FACTORY,
                    "org.jboss.naming.remote.client.InitialContextFactory");

            prop.put(Context.PROVIDER_URL, purl);
            prop.put(Context.SECURITY_PRINCIPAL, "myusername");
            prop.put(Context.SECURITY_CREDENTIALS, "mypassword");
            prop.put("jboss.naming.client.ejb.context", false);

            return new InitialContext(prop);            
        }

        public void closeContext(Context context) throws NamingException {
            if (context != null) {
                context.close();
            }
        }

        private String getJndiName(
                String prefix, 
                String appName,
                String moduleName,
                String distinctName,
                String beanName,
                Class viewClass, 
                boolean stateful) 
        {
            StringBuilder builder = new StringBuilder();
            if (prefix != null && prefix.length() > 0) {
                builder.append(prefix).append(':');
            }
            builder.append(appName)
                    .append('/')
                    .append(moduleName)
                    .append('/')
                    .append(distinctName)
                    .append('/')
                    .append(beanName).append('!')
                    .append(viewClass.getName());
            if (stateful) {
                builder.append("?stateful");
            }
            return builder.toString();
        }
        public Object lookup(Context context) throws NamingException {

            final String prefix = "ejb"; 
            final String appName = "myearname";
            final String moduleName = "mywarname";
            final String distinctName = "";
            final String beanName = "MyBean";
            final Class viewClass = MyBeanInterface.class;


            String jndi = getJndiName(prefix, appName, moduleName, distinctName, beanName, viewClass, true);


            return context.lookup(jndi);
        }

Обратите внимание, что «отличное имя» не указано, поскольку оно не требуется. "различное имя" предполагается быть необязательным: все это вызывается:

                MyBeanInterface sstatus = null;
                try {
                    ctx = createInitialContext();
                    sstatus = (MyBeanInterface) lookup(ctx);

                } catch (Exception ex) {
                     ...
                }

При вызове этого кода выдается следующее сообщение об ошибке:

Caused by: java.lang.IllegalStateException: EJBCLIENT000024: No EJB receiver available for handling [appName:SockTransport, moduleName:SockTransport, distinctName:] combination
        at org.jboss.ejb.client.EJBClientContext.requireEJBReceiver(EJBClientContext.java:873) ~[ttjd.jar:?]
        at org.jboss.ejb.client.EJBClient.createSessionWithPossibleRetries(EJBClient.java:222) ~[ttjd.jar:?]
        at org.jboss.ejb.client.EJBClient.createSession(EJBClient.java:202) ~[ttjd.jar:?]
        at org.jboss.ejb.client.naming.ejb.EjbNamingContext.doCreateProxy(EjbNamingContext.java:227) ~[ttjd.jar:?]
        at org.jboss.ejb.client.naming.ejb.EjbNamingContext.createEjbProxy(EjbNamingContext.java:204) ~[ttjd.jar:?]

Используя приведенный выше код, имя JNDI, которое я предоставляю, — ejb:myearname/mywarname//MyBean!com.whatever.my.package.MyBeanInterface. Обратите внимание на двойную косую черту, вызванную отсутствием отдельного имени. Я могу и переделал этот код, чтобы он производил вместо ejb:myearname/mywarname/MyBean!com.whatever.my.package.MyBeanInterface, и это не имеет значения.

Честно говоря, я думаю, что это сообщение об ошибке является отвлекающим маневром. Я подозреваю, что есть какая-то другая проблема с моей настройкой, которая не обнаруживается и не ломается в этом интерфейсе. Я не думаю, что отдельное имя или его отсутствие имеют какое-либо отношение к проблеме. Я думаю, что это просто то, как они регистрируют объект, который нельзя найти.

Прежде чем я пойду по пути выяснения того, как добавить бесполезное «отличное имя» в, возможно, тщетной попытке сделать JBOSS счастливым, может кто-нибудь рискнуть предположить, в чем может быть реальная проблема?

ОБНОВЛЕНИЕ:

Предложения @Steve_C весьма показательны, но я до сих пор не заставил их работать. Он упустил несколько моментов из первоначального создания контекста:

  • Context.URL_PKG_PREFIXES
  • Контекст.INITIAL_CONTEXT_FACTORY
  • "jboss.именование.клиент.ejb.контекст"

но они были упомянуты в ресурс, который он цитировал - кстати, очень кстати.

Итак, я добавил их, и мой метод createInitialContext теперь выглядит так:

    public InitialContext createInitialContext() throws NamingException {
        Properties prop = new Properties();
        prop.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
        prop.put(Context.INITIAL_CONTEXT_FACTORY,
                "org.jboss.naming.remote.client.InitialContextFactory");
        prop.put(Context.PROVIDER_URL, "http-remoting://{server-ip}:{server-port});
        prop.put("jboss.naming.client.ejb.context", true);
        return new InitialContext(prop);            
    }

Почему PROVIDER_URL необходим, если я уже указал server-ip и server-port в файле jboss-ejb-client.properties, остается загадкой, но это имеет значение.

После добавления этих трех элементов в исходную контекстную среду я получаю другое сообщение об ошибке (EJBCLIENT000025 вместо EJBCLIENT000024):

java.lang.IllegalStateException: EJBCLIENT000025: No EJB receiver available for handling [appName:SockTransport, moduleName:SockTransport, distinctName:] combination for invocation context org.jboss.ejb.client.EJBClientInvocationContext@67f639d3
        at org.jboss.ejb.client.EJBClientContext.requireEJBReceiver(EJBClientContext.java:798) ~[ttjd.jar:?]
        at org.jboss.ejb.client.ReceiverInterceptor.handleInvocation(ReceiverInterceptor.java:128) ~[ttjd.jar:?]
        at org.jboss.ejb.client.EJBClientInvocationContext.sendRequest(EJBClientInvocationContext.java:186) ~[ttjd.jar:?]
        at org.jboss.ejb.client.EJBInvocationHandler.sendRequestWithPossibleRetries(EJBInvocationHandler.java:255) ~[ttjd.jar:?]
        at org.jboss.ejb.client.EJBInvocationHandler.doInvoke(EJBInvocationHandler.java:200) ~[ttjd.jar:?]
        at org.jboss.ejb.client.EJBInvocationHandler.doInvoke(EJBInvocationHandler.java:183) ~[ttjd.jar:?]
    at org.jboss.ejb.client.EJBInvocationHandler.invoke(EJBInvocationHandler.java:146) ~[ttjd.jar:?]
    at com.sun.proxy.$Proxy20.create(Unknown Source) ~[?:?]

Я полагаю, это считается прогрессом, но я нахожу это более сложным, чем должно быть. Интересно, должны ли эти новые свойства быть в файле свойств, но в официальной документации довольно четко сказано, что это не так.


person Steve Cohen    schedule 10.01.2017    source источник


Ответы (2)


Самый гибкий поиск и вызов удаленного компонента WildFly/JBossEAP можно выполнить следующим образом:

Создайте файл jboss-ejb-client.properties, который должен находиться в пути к классам клиента:

remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED=false
remote.connections=default
remote.connection.default.host=<ip of jboss eap host>
remote.connection.default.port = 8080
remote.connection.default.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false

Сообщение об ошибке EJBCLIENT000024: No EJB receiver available for handling является признаком отсутствия jboss-ejb-client.properties файла.

Создайте InitialContext:

    Properties jndiProps = new Properties();
    jndiProps.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
    Context ctx = new InitialContext(jndiProps);

Обратите внимание, что никакие другие свойства не требуются.

Найдите компонент и назовите его:

    ServiceLogic beanRemoteInterface = (ServiceLogic) ctx.lookup("ejb:/WhizBangSessionEJB/WhizBangSessionEJB!com.whatever.hostinterface.ServiceLogic?stateful");
    String bar = beanRemoteInterface.sayHello();
    System.out.println("Remote Foo bean returned " + bar);

Обратите внимание на ?stateful в конце имени JNDI, которое требуется для компонентов EJB с отслеживанием состояния.

Выход:

Jan 11, 2017 11:07:46 PM org.jboss.ejb.client.EJBClient <clinit>
INFO: JBoss EJB Client version 2.1.4.Final
Jan 11, 2017 11:07:46 PM org.xnio.Xnio <clinit>
INFO: XNIO version 3.4.0.Final
Jan 11, 2017 11:07:46 PM org.xnio.nio.NioXnio <clinit>
INFO: XNIO NIO Implementation Version 3.4.0.Final
Jan 11, 2017 11:07:46 PM org.jboss.remoting3.EndpointImpl <clinit>
INFO: JBoss Remoting version 4.0.21.Final
Jan 11, 2017 11:07:46 PM org.jboss.ejb.client.remoting.VersionReceiver handleMessage
INFO: EJBCLIENT000017: Received server version 2 and marshalling strategies [river]
Jan 11, 2017 11:07:46 PM org.jboss.ejb.client.remoting.RemotingConnectionEJBReceiver associate
INFO: EJBCLIENT000013: Successful version handshake completed for receiver context EJBReceiverContext{clientContext=org.jboss.ejb.client.EJBClientContext@29ca901e, receiver=Remoting connection EJB receiver [connection=org.jboss.ejb.client.remoting.ConnectionPool$PooledConnection@5649fd9b,channel=jboss.ejb,nodename=steves-mbp]} on channel Channel ID ecac0ca6 (outbound) of Remoting connection 6536e911 to /192.168.12.6:8080 of endpoint "config-based-ejb-client-endpoint" <520a3426>
Remote Foo bean returned hello

Дополнительную информацию можно найти в Удаленные вызовы EJB через JNDI — клиентский API EJB или проект удаленного именования.

Дополнительный пример кода можно найти в репозитории QuickStart по адресу wildfly/quickstart/ejb. -удаленный

PS. Если вы действительно хотите установить distinct-name, вам нужно добавить файл jboss-ejb3.xml в банку EJB, содержащий:

<jboss:ejb-jar xmlns:jboss="http://www.jboss.com/xml/ns/javaee"
               xmlns="http://www.jboss.com/xml/ns/javaee"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-ejb3-2_0.xsd"
               version="3.1"
               impl-version="2.0">

    <distinct-name>something-distinct</distinct-name>
</jboss:ejb-jar>

Динамические свойства клиента EJB

Если вам нужно иметь возможность динамически предоставлять jboss-ejb-client.properties, то самым простым решением будет создание этого файла на лету, возможно, во время инициализации клиента.

  1. Установите системное свойство jboss.ejb.client.properties.file.path так, чтобы оно указывало на безопасное доступное для записи расположение файловой системы. Небезопасным примером может быть что-то вроде

    -Djboss.ejb.client.properties.file.path=/tmp/whizbang-ejb.properties

    or

    System.setProperty("jboss.ejb.client.properties.file.path", "/tmp/whizbang-ejb.properties");

  2. Создайте файл свойств с именем со строкой, определенной jboss.ejb.client.properties.file.path, в соответствии с форматом, описанным для файлов jboss-ejb-client.properties.

  3. Продолжить создание InitialContext

Существуют и другие альтернативы, которые предполагают взлом предоставленного кода jboss-ejb-client. Однако вы должны помнить, что это код LGPL, и вы и ваша компания должны сделать свои взломы общедоступными.

person Steve C    schedule 11.01.2017
comment
Спасибо, мне нужно будет попробовать. Несколько вещей, которые я не понимаю: 1. В чем разница между программной инициализацией начального контекста и поиском свойств и почему это важно (отсутствие файла дает ошибку)? 2. Есть ли где-нибудь краткий список всех этих номеров сообщений об ошибках? Какой смысл в таких цифрах, если их негде посмотреть, чтобы получить информацию? 3. При использовании синтаксиса ejb: кажется, что двойная косая черта для отсутствующего отдельного имени не требуется или даже не является допустимой. Поэтому многие примеры кода в Интернете неверны. Спасибо. - person Steve Cohen; 11.01.2017
comment
Дополнительные вопросы: 1. Будет ли это работать для поиска удаленного домашнего интерфейса и создания bean-компонента из этого? Это необходимо для меня, так как ejbCreate() выполняет жизненно важную логику инициализации. 2. Требуется ли суффикс с отслеживанием состояния для поиска домашнего интерфейса? - person Steve Cohen; 11.01.2017
comment
И, поскольку я читал исходный код клиента JBoss (org.jboss.naming.remote.client.InitialContextFactory.findClientProperties(), ошибка при поиске jboss-naming-client.properties не приводит к возникновению исключения. - person Steve Cohen; 11.01.2017
comment
Я не пытался помещать все реквизиты в файл свойств по нескольким причинам: 1) Это приложение закодировано таким образом, что информация об имени/порте сервера считывается из другого проприетарного файла конфигурации, а затем программно помещается в контекст оттуда. Я бы предпочел не переписывать этот беспорядок, поэтому я застрял с этим. 2) как указано выше, из анализа исходного кода я не считаю, что отсутствие этого файла является причиной ошибок. Во всяком случае, я программно поместил все эти свойства, которые вы упомянули, в InitialContext, и все равно получаю ошибки. - person Steve Cohen; 12.01.2017
comment
Если я уберу jboss-ejb-client.properties из пути к классу, я получу именно это сообщение об ошибке EJBCLIENT000024. Поиск домашнего интерфейса и вызов create на нем отлично работает для меня. Я пока ничего не знаю о ?stateful здесь. Помещение этих свойств в InitialContext не работает. - person Steve C; 12.01.2017
comment
Спасибо, еще раз @SteveC. Ладно, я попробую. Исходный код EJB-клиента JBoss очень плотный, и, возможно, я что-то упустил. Но как бы вы справились с моим требованием о необходимости программно применять имя хоста: порт? Могу ли я поместить некоторые реквизиты в файл и добавить другие перед использованием контекста? - person Steve Cohen; 12.01.2017
comment
Поспав на этом, я сам придумал файл свойств записи при инициализации приложения. Для меня это немного сложнее, поскольку на машине можно одновременно запускать более одного клиента, каждый из которых общается с другим сервером jboss, но это выполнимо - может быть, даже в сценарии, который запускает приложение. Можно даже изменить путь к классам для каждого запуска, чтобы найти его файл свойств. Еще раз спасибо. Где ты? Австралия? Все ваши ответы доходят до меня ночью. - person Steve Cohen; 12.01.2017
comment
Да, это только что стало Пятницей здесь. Существует механизм указания нескольких хостов в файле jboss-ejb-client.properties, описанный в вызовы EJB от удаленного клиента с использованием JNDI. Вы указываете пару дополнительных свойств в jndiProps для выбора целевого сервера. - person Steve C; 12.01.2017
comment
обновил мой вопрос с дополнительной информацией. До сих пор нет радости. - person Steve Cohen; 13.01.2017
comment
Вернемся к основам. Что произойдет, если вы предоставите именно то jboss-ejb-client.properties, которое я предлагаю (только с обновленным именем хоста), и предоставите только одно свойство Context.URL_PKG_PREFIXES при создании InitialContext? - person Steve C; 13.01.2017
comment
В этой конфигурации - javax.naming.NoInitialContextException: необходимо указать имя класса в свойстве среды или системы, либо в качестве параметра апплета, либо в файле ресурсов приложения: java.naming.factory.initial - person Steve Cohen; 13.01.2017
comment
Давайте продолжим это обсуждение в чате. - person Steve C; 13.01.2017
comment
Грр. Корпоративный брандмауэр блокирует чат. Но у меня есть другой компьютер. Я присоединюсь к чату оттуда. - person Steve Cohen; 13.01.2017
comment
Большое спасибо за @Steve_C: еще многое предстоит понять, но это работает - намного сложнее, чем должно быть. - person Steve Cohen; 13.01.2017

Прежде чем поделиться тем, что я узнал, я хочу поблагодарить @Steve_C, который сделал больше, чем по долгу службы, помогая мне, включая продолжительный сеанс чата. Если кому-то интересно, это не так. Я, кстати.

Не имея в виду неуважения к @Steve_C или его ответу (который я назвал полезным), здесь есть что сказать, как я узнал из очень болезненного опыта.

Вот некоторые вещи, которые я узнал:

1) Необходимо иметь файл jboss-ejb-client.properties.
2) Этот файл может быть расположен либо в пути к классам, либо указан в расположении, указанном следующим свойством System, которое я установил непосредственно перед вызовом конструктора InitialContext:

    System.setProperty("jboss.ejb.client.properties.file.path", "/path/to/properties/file");
    return new InitialContext(prop);    

3) Этот файл должен называть соединения:

remote.connections=conn1,conn2

4) Для каждого соединения, указанного в приведенном выше свойстве, записи хоста и порта должны храниться в файле свойств.

remote.connection.conn1.host=10.0.0.1
remote.connection.conn1.port=8080
remote.connection.conn2.host=10.0.0.2
remote.connection.conn2.port=8080

5) Для каждого названного соединения также должен быть указан какой-либо метод аутентификации, либо а)

remote.connection.conn1.username=user1
remote.connection.conn1.password=topSecret
remote.connection.conn2.username=user2
remote.connection.conn2.password=open_sesame

or b)

remote.connection.conn1.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false
remote.connection.conn2.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false

что буквально указывает на то, что анонимные вызовы без пароля не запрещены. (Давайте послушаем двойное отрицание!) Я полагаю, что теоретически возможно иметь одно соединение с использованием пароля, а другое — с анонимным входом в систему, но я не могу представить, почему. Однако это делается, это должно быть указано соединение за соединением. Существует неправильный пример в Интернете, включающий оба

remote.connection.conn2.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false

и свойства имени пользователя/пароля для соединения. Чистый эффект этого заключается в том, что учетные данные могут быть неверными, и вы все равно получите логин. Я попробовал это и обнаружил, что это так.

6) Несмотря на видимость, указание java.naming.provider.url необходимо. Было бы неплохо, если бы JBoss мог понять это из упомянутых выше свойств хоста соединения и порта, но он не может! Для этого может быть или не быть веская причина, я просто не знаю.

Досадно, что это НЕЛЬЗЯ указать в файле свойств. Похоже, это ошибка в клиенте JBoss. Поскольку ":" эквивалентно "=" в спецификации файла свойств Java, это невозможно хранить там URL-адреса с нотацией http-remote:// или любые URL-адреса с косой чертой двоеточия. Двоеточие должно быть экранировано обратной косой чертой, но, очевидно, клиентский код JBoss не вызывает Properties.load() для правильного разрешения экранирования, а скорее пытается прочитать его построчно? Так что это должно быть указано в свойствах, переданных в создание InitialContext. Я попробовал оба способа и обнаружил, что указание его в коде работает, а указание в файле свойств - нет.

Таким образом, у нас есть неприятная ситуация, когда есть два метода предоставления данных в InitialContext: некоторые через файл свойств, а некоторые в исходной среде Hashtable, переданной конструктору InitialContext. Что-то нужно делать в одном месте, а что-то в другом. И ничего из этого не задокументировано должным образом.

person Steve Cohen    schedule 13.01.2017