/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.component.ahc.ws;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayReader;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.AsyncHttpClientConfig;
import com.ning.http.client.AsyncHttpProvider;
import com.ning.http.client.providers.grizzly.GrizzlyAsyncHttpProvider;
import com.ning.http.client.websocket.WebSocket;
import com.ning.http.client.websocket.WebSocketByteListener;
import com.ning.http.client.websocket.WebSocketTextListener;
import com.ning.http.client.websocket.WebSocketUpgradeHandler;
import org.apache.camel.Consumer;
import org.apache.camel.Processor;
import org.apache.camel.Producer;
import org.apache.camel.component.ahc.AhcEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
*/
public class WsEndpoint extends AhcEndpoint {
private static final transient Logger LOG = LoggerFactory.getLogger(WsEndpoint.class);
// for using websocket streaming/fragments
private static final boolean GRIZZLY_AVAILABLE =
probeClass("com.ning.http.client.providers.grizzly.GrizzlyAsyncHttpProvider");
private WebSocket websocket;
private Set<WsConsumer> consumers;
private boolean useStreaming;
public WsEndpoint(String endpointUri, WsComponent component) {
super(endpointUri, component, null);
this.consumers = new HashSet<WsConsumer>();
}
private static boolean probeClass(String name) {
try {
Class.forName(name, true, WsEndpoint.class.getClassLoader());
return true;
} catch (Throwable t) {
return false;
}
}
@Override
public WsComponent getComponent() {
return (WsComponent) super.getComponent();
}
@Override
public Producer createProducer() throws Exception {
return new WsProducer(this);
}
@Override
public Consumer createConsumer(Processor processor) throws Exception {
return new WsConsumer(this, processor);
}
WebSocket getWebSocket() {
synchronized (this) {
if (websocket == null) {
try {
connect();
} catch (Exception e) {
LOG.error("Failed to connect", e);
}
}
}
return websocket;
}
void setWebSocket(WebSocket websocket) {
this.websocket = websocket;
}
/**
* @return the useStreaming
*/
public boolean isUseStreaming() {
return useStreaming;
}
/**
* @param useStreaming the useStreaming to set
*/
public void setUseStreaming(boolean useStreaming) {
this.useStreaming = useStreaming;
}
/* (non-Javadoc)
* @see org.apache.camel.component.ahc.AhcEndpoint#createClient(com.ning.http.client.AsyncHttpClientConfig)
*/
@Override
protected AsyncHttpClient createClient(AsyncHttpClientConfig config) {
AsyncHttpClient client;
if (config == null) {
config = new AsyncHttpClientConfig.Builder().build();
}
AsyncHttpProvider ahp = getAsyncHttpProvider(config);
if (ahp == null) {
client = new AsyncHttpClient(config);
} else {
client = new AsyncHttpClient(ahp, config);
}
return client;
}
public void connect() throws InterruptedException, ExecutionException, IOException {
websocket = getClient().prepareGet(getHttpUri().toASCIIString()).execute(
new WebSocketUpgradeHandler.Builder()
.addWebSocketListener(new WsListener()).build()).get();
}
@Override
protected void doStop() throws Exception {
if (websocket != null && websocket.isOpen()) {
websocket.close();
}
super.doStop();
}
void connect(WsConsumer wsConsumer) {
consumers.add(wsConsumer);
}
void disconnect(WsConsumer wsConsumer) {
consumers.remove(wsConsumer);
}
class WsListener implements WebSocketTextListener, WebSocketByteListener {
private ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
private StringBuffer textBuffer = new StringBuffer();
@Override
public void onOpen(WebSocket websocket) {
LOG.info("websocket opened");
}
@Override
public void onClose(WebSocket websocket) {
LOG.info("websocket closed");
}
@Override
public void onError(Throwable t) {
LOG.error("websocket on error", t);
}
@Override
public void onMessage(byte[] message) {
LOG.info("received message --> {}", message);
for (WsConsumer consumer : consumers) {
consumer.sendMessage(message);
}
}
@Override
public void onFragment(byte[] fragment, boolean last) {
if (LOG.isInfoEnabled()) {
LOG.info("received fragment({}) --> {}", last, fragment);
}
// for now, construct a memory based stream. In future, we provide a fragmented stream that can
// be consumed before the final fragment is added.
synchronized (byteBuffer) {
try {
byteBuffer.write(fragment);
} catch (IOException e) {
//ignore
}
if (last) {
//REVIST avoid using baos/bais that waste memory
byte[] msg = byteBuffer.toByteArray();
for (WsConsumer consumer : consumers) {
consumer.sendMessage(new ByteArrayInputStream(msg));
}
byteBuffer.reset();
}
}
}
@Override
public void onMessage(String message) {
LOG.info("received message --> {}", message);
for (WsConsumer consumer : consumers) {
consumer.sendMessage(message);
}
}
@Override
public void onFragment(String fragment, boolean last) {
if (LOG.isInfoEnabled()) {
LOG.info("received fragment({}) --> {}", last, fragment);
}
// for now, construct a memory based stream. In future, we provide a fragmented stream that can
// be consumed before the final fragment is added.
synchronized (textBuffer) {
textBuffer.append(fragment);
if (last) {
//REVIST avoid using sb/car that waste memory
char[] msg = new char[textBuffer.length()];
textBuffer.getChars(0, msg.length, msg, 0);
for (WsConsumer consumer : consumers) {
consumer.sendMessage(new CharArrayReader(msg));
}
textBuffer.setLength(0);
}
}
}
}
protected AsyncHttpProvider getAsyncHttpProvider(AsyncHttpClientConfig config) {
if (GRIZZLY_AVAILABLE) {
return new GrizzlyAsyncHttpProvider(config);
}
return null;
}
}