воскресенье, 8 мая 2016 г.

Использование WebSocket в ADF (Using WebSocket in ADF)

Начиная с JDeveloper 12.1.3.0  в ADF появилась поддержка Java API for WebSockets.
WebSocket — это протокол связи поверх TCP-соединения, предназначенный для обмена сообщениями между браузером и веб-сервером в режиме реального времени.Он позволяет пересылать любые данные, на любой домен, безопасно и почти без лишнего сетевого трафика.

Для демонстрации WebSocket в ADF создадим приложение,   в котором будем вводить сообщение в inputtext.

1. Создайте Fusion Middleware  приложение
2. В это приложение  добавьте проект Web Socket Project





3. После этого, зайдите в свойства созданного проекта и добавьте библиотеку WebSocket.



4. Теперь создайте Java класс для обработки WebSocket событий. Что бы класс был сконфигурирован для работы с WebSocket, пропишите в нем аннотацию ServerEndpoint( например: @ServerEndpoint( value ="/message") )
Аннотация подчеркнётся   красным цветом, что бы исправить ошибку, используйте помощник и выберите в нем "Configure Project for Web Socket ..."



Теперь нужно добавить реализацию в созданный класс. В соответствии  со спецификацией новый экземпляр класса будет создаваться для каждого соединения WebSocket. И что бы сохранить все активные сессии, мы добавим очередь:
final static Queue<Session> queue = new ConcurrentLinkedQueue<>();

Так же нужно добавить три метода с аннотациями OnOpen(открытие соединения) , OnClose(закрытие соединение), OnError (ошибка)  :

@OnOpen public void open(Session session) {
queue.add(session);
}
@OnClose
public void closedConnection(Session session) {
queue.remove(session);
}
@OnError
public void error(Session session, Throwable t) {
queue.remove(session);
t.printStackTrace();
}


Что бы этот класс был доступен в проекте ViewController, зайдите в свойства WebSocet проекта  и переименуйте Java EE Web Application Name вместе с Java EE Web Context Root


Создайте Deployment профиль (Jar)


Добавьте этот профиль в зависимости ViewConroller проекта


5. Класс для обработки событий на стороне сервера создан, теперь нужно установить соединение на стороне клиента и отправлять данные на сервер. Для этого  создайте js скрипт во ViewController и добавьте следующий код :


var wsUri = "ws://"; var socketendpoint = "/WebSocket/message"; //где Websocket название Java EE в //проекте Websocket, а message - название ServerEndpoint
var websocket;
function getWSUri() {
return wsUri + "localhost:7101" + socketendpoint;
}
//Подключение к WebSocket
function connectSocket() {
if ('WebSocket' in window){
websocket = new WebSocket(getWSUri());
     console.log('socket opened !');
} else {
console.log('websocket not supported...!')
}
}
//добавления события, что бы при загрузке страницы выполнялся метод connectSocket
window.addEventListener("load", connectSocket, false);

Теперь нужно подключить этот скрипт на страницу. Для этого используется тэг af:resource (например: <af:resource type="javascript" source="../resources/js/socket.js" />)
Если запустить приложение, то произойдет подключение к WebSocket. Осталось добавить какую то имплементацию. В моем случае в  input text будет вводиться слово,а в output text будет выводиться его инверсия(обратный порядок символов у введенного текста)


На jspx страницу я добавил inputText ( для ввода текста) и outputText. Обратите внимание что у  inputText есть clientListener для вызова js функции при изменении  значения, а у outputtext есть свойство clientComponent="true", это свойство необходимо для того, что бы возможно было работать с outputText из JS.

<af:panelGroupLayout id="pgl2" layout="vertical"> <af:inputText label="Enter text:" id="message">
<af:clientListener method="processInverse" type="valueChange"/>
</af:inputText>
<af:outputText value="" id="inverseText" clientComponent="true"/>
</af:panelGroupLayout>

Теперь нужно немного изменить скрипт созданный ранее, а именно: изменить метод connectSocket и добавить функции для обработки коллбэков websocket

//Подключение к WebSocket function connectSocket() {
if ('WebSocket' in window){
websocket = new WebSocket(getWSUri());
websocket.onmessage = onMessage;
websocket.onerror = onError;
websocket.onclose = onClose;
console.log('socket opened !');
} else {
console.log('websocket not supported...!')
}
}
//функция для обработки ошибок function onError(evt) { console.log('error :' + evt);
}
//функция вызывающаяся при закрытии соединения
function onClose(evt) {
console.log('websocket closed :' + evt.code + ":" + evt.reason);
}
//функция для обработки полученных данных (в моем случае данные с сервера
//просто записываются в outputText)
function onMessage(evt) {
console.log("on message ->" + event.data)
inverseText = AdfPage.PAGE.findComponentByAbsoluteId("inverseText");
inverseText.setValue("Reverse:".concat(event.data))
}
//функция вызывающаяся при изменении inputText. Измененное значение при помощи //websocket.send() отправляет данных на сервер
function processInverse(actionEvent) {
websocket.send(actionEvent.getSource().getValue())
}

Также нужно изменить созданный ранее Java класс, туда нужно добавить метод с аннотацией @OnMessage  (обработка отправленных данных с клиента). При помощи session.getBasicRemote().sendText() данные можно отправить обратно клиенту.
@OnMessage public void processMessage(Session session, String message) {
try {
session.getBasicRemote().sendText(new StringBuilder(message).reverse().toString());
} catch (IOException e) {
System.out.println("IOException:"+e);
}
}


Готово.

Рабочее приложение на github: https://github.com/JealousyM/websocket-adf

Примеры использования WebSocket в ADF(на английском):
http://andrejusb.blogspot.com.by/2015/05/adf-and-two-way-websocket-communication
http://andrejusb.blogspot.com.by/2015/04/websocket-integration-with-adf-for-ppr.html
http://andrejusb.blogspot.com.by/2016/02/oracle-jet-and-websocket-integration.html