![JAR search and dependency download from the Maven repository](/logo.png)
org.ow2.easybeans.component.smartclient.server.SmartClientEndPointComponent Maven / Gradle / Ivy
/**
* EasyBeans
* Copyright (C) 2006 Bull S.A.S.
* Contact: [email protected]
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
* --------------------------------------------------------------------------
* $Id: SmartClientEndPointComponent.java 5369 2010-02-24 14:58:19Z benoitf $
* --------------------------------------------------------------------------
*/
package org.ow2.easybeans.component.smartclient.server;
import static org.ow2.easybeans.component.smartclient.api.ProtocolConstants.PROTOCOL_VERSION;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.ow2.easybeans.component.api.EZBComponentException;
import org.ow2.easybeans.component.itf.EZBSmartComponent;
import org.ow2.easybeans.component.itf.RegistryComponent;
import org.ow2.easybeans.component.smartclient.api.Message;
import org.ow2.easybeans.component.smartclient.api.ProtocolConstants;
import org.ow2.easybeans.component.smartclient.message.ChannelAttachment;
import org.ow2.easybeans.component.smartclient.message.ClassAnswer;
import org.ow2.easybeans.component.smartclient.message.ClassNotFound;
import org.ow2.easybeans.component.smartclient.message.ClassRequest;
import org.ow2.easybeans.component.smartclient.message.ProviderURLAnswer;
import org.ow2.easybeans.component.smartclient.message.ResourceAnswer;
import org.ow2.easybeans.component.smartclient.message.ResourceNotFound;
import org.ow2.easybeans.component.smartclient.message.ResourceRequest;
/**
* This endpoint receives the request from clients, handle them and send an
* answer.
* For example, it send the bytecode for a given class.
* @author Florent Benoit
*/
public class SmartClientEndPointComponent implements EZBSmartComponent, Runnable {
/**
* Use the JDK logger (to avoid any dependency).
*/
private static Logger logger = Logger.getLogger(SmartClientEndPointComponent.class.getName());
/**
* Maximum length of messages that we accept.
*/
private static final int MAX_LENGTH_INCOMING_MSG = 500;
/**
* Default port number.
*/
private static final int DEFAULT_PORT_NUMBER = 2503;
/**
* Buffer length.
*/
private static final int BUF_APPEND = 1000;
/**
* Listening port number.
*/
private int portNumber = DEFAULT_PORT_NUMBER;
/**
* Nio Selector.
*/
private Selector selector = null;
/**
* The selection key of the server (accepting clients).
*/
private SelectionKey serverkey = null;
/**
* Server socket channel (listening).
*/
private ServerSocketChannel server = null;
/**
* Waiting ?
*/
private boolean waitingSelector = true;
/**
* Link to the RMI component used to get the provider URL.
*/
private RegistryComponent registryComponent = null;
/**
* The class loader to use for the resource lookup. If null, Thread's
* context class loader is used.
*/
private ClassLoader classLoader = null;
/**
* Init method.
This method is called before the start method.
* @throws EZBComponentException if the initialization has failed.
*/
public void init() throws EZBComponentException {
// Creates a new selector
try {
this.selector = Selector.open();
} catch (IOException e) {
throw new EZBComponentException("Cannot open a new selector.", e);
}
// Server socket
try {
this.server = ServerSocketChannel.open();
} catch (IOException e) {
throw new EZBComponentException("Cannot open a new server socket channel.", e);
}
// no blocking
try {
this.server.configureBlocking(false);
} catch (IOException e) {
throw new EZBComponentException("Cannot configure the server socket with non-blocking mode.", e);
}
}
/**
* Start method.
This method is called after the init method.
* @throws EZBComponentException if the start has failed.
*/
public void start() throws EZBComponentException {
// port number listener
try {
this.server.socket().bind(new java.net.InetSocketAddress(this.portNumber));
} catch (IOException e) {
throw new EZBComponentException("Cannot listen on the port number '" + this.portNumber
+ "', maybe the port is already used.", e);
}
// registering
try {
this.serverkey = this.server.register(this.selector, SelectionKey.OP_ACCEPT);
} catch (ClosedChannelException e) {
throw new EZBComponentException("Cannot register the current selector as an accepting selector waiting clients.", e);
}
// now wait clients
this.waitingSelector = true;
// infinite loop
new Thread(this).start();
logger.info("SmartClient Endpoint listening on port '" + this.portNumber + "'.");
}
/**
* Stop method.
This method is called when component needs to be
* stopped.
* @throws EZBComponentException if the stop is failing.
*/
public void stop() throws EZBComponentException {
// break infinite loop
this.waitingSelector = false;
this.selector.wakeup();
try {
this.server.socket().close();
} catch (IOException e) {
logger.log(Level.WARNING, "Unable to close the socket.", e);
}
}
/**
* Infinite loop (until the end of the component) that handle the selectors.
*/
public void handleSelectors() {
// infinite loop
while (this.waitingSelector) {
// wait new stuff
int updatedKeys = 0;
try {
updatedKeys = this.selector.select();
} catch (IOException e) {
logger.log(Level.SEVERE, "Selector has been closed, stopping listener", e);
this.waitingSelector = false;
}
// No update, then go ahead
if (updatedKeys == 0) {
continue;
}
// Get selected keys
Set selectedKeys = this.selector.selectedKeys();
for (Iterator itSelectedKeys = selectedKeys.iterator(); itSelectedKeys.hasNext();) {
SelectionKey selectionKey = itSelectedKeys.next();
itSelectedKeys.remove(); // remove it has it was handled
// Server key ?
if (selectionKey == this.serverkey) {
// New client ?
if (selectionKey.isAcceptable()) {
try {
handleAccept();
} catch (Exception e) {
// all exception (including runtime)
logger.log(Level.SEVERE, "Unable to accept a new connection.", e);
}
}
} else if (selectionKey.isReadable()) {
// get request from client
try {
handleRead(selectionKey);
} catch (Exception e) {
// all exception (including runtime)
logger.log(Level.SEVERE, "Unable to read data from the client.", e);
}
} else if (selectionKey.isWritable()) {
// answer to the client
try {
handleWrite(selectionKey);
} catch (Exception e) {
// all exception (including runtime)
logger.log(Level.SEVERE, "Unable to write data to the client.", e);
}
}
}
}
}
/**
* Handle a new client that is being connected.
* @throws IOException if cannot accept the client
*/
private void handleAccept() throws IOException {
// new incoming connection
SocketChannel client = this.server.accept();
// Non blocking client
client.configureBlocking(false);
// Register client (with an empty channel attachment)
client.register(this.selector, SelectionKey.OP_READ, new ChannelAttachment());
}
/**
* Handle all read operations on channels.
* @param selectionKey the selected key.
* @throws IOException if cannot read from the channel.
*/
private void handleRead(final SelectionKey selectionKey) throws IOException {
// Get the client channel that has data to read
SocketChannel client = (SocketChannel) selectionKey.channel();
// current bytecode read
ChannelAttachment channAttachment = (ChannelAttachment) selectionKey.attachment();
ByteBuffer channBuffer = channAttachment.getByteBuffer();
// Read again
int bytesread = client.read(channBuffer);
if (bytesread == -1) {
// close (as the client has been disconnected)
selectionKey.cancel();
client.close();
}
// Client send data, analyze data
// Got header ?
if (channBuffer.position() >= Message.HEADER_SIZE) {
// Yes, got header
// Check if it is a protocol that we manage
byte version = channBuffer.get(0);
if (version != PROTOCOL_VERSION) {
selectionKey.cancel();
client.close();
throw new IllegalStateException("Invalid protocol version : waiting '" + PROTOCOL_VERSION + "', got '" + version
+ "'.");
}
// Get operation asked by client
byte opCode = channBuffer.get(1);
// Length
int length = channBuffer.getInt(2);
if (length < 0) {
selectionKey.cancel();
client.close();
throw new IllegalStateException("Invalid length for client '" + length + "'.");
}
if (length > MAX_LENGTH_INCOMING_MSG) {
selectionKey.cancel();
client.close();
throw new IllegalStateException("Length too big, max length = '" + MAX_LENGTH_INCOMING_MSG + "', current = '"
+ length + "'.");
}
// Correct header and correct length ?
if (channBuffer.position() >= Message.HEADER_SIZE + length) {
// set the limit (specified in the length), else we have a
// default buffer limit
channBuffer.limit(Message.HEADER_SIZE + length);
// duplicate this buffer
ByteBuffer dataBuffer = channBuffer.duplicate();
// skip header (already analyzed)
dataBuffer.position(Message.HEADER_SIZE);
// Switch on operations :
try {
switch (opCode) {
case ProtocolConstants.CLASS_REQUEST:
handleReadClassRequest(selectionKey, dataBuffer);
break;
case ProtocolConstants.RESOURCE_REQUEST:
handleReadResourceRequest(selectionKey, dataBuffer);
break;
case ProtocolConstants.PROVIDER_URL_REQUEST:
handleReadProviderURLRequest(selectionKey, dataBuffer);
break;
default:
// nothing to do
}
} catch (Exception e) {
// clean
selectionKey.cancel();
client.close();
throw new IllegalStateException("Cannot handle request with opCode '" + opCode + "'.", e);
}
}
}
}
/**
* Handle the client's request asking for a class.
* @param selectionKey key for exchanging with the client.
* @param dataBuffer the buffer that contains request.
* @throws IOException if operation fails
*/
private void handleReadClassRequest(final SelectionKey selectionKey, final ByteBuffer dataBuffer) throws IOException {
// Build object (from input)
ClassRequest classRequest = new ClassRequest(dataBuffer);
String className = classRequest.getClassName();
// Answer to the client (go in write mode)
selectionKey.interestOps(SelectionKey.OP_WRITE);
String encodedClassName = className.replaceAll("\\.", "/").concat(".class");
// Find the resource from the classloader
InputStream inputStream = findClassLoader().getResourceAsStream(encodedClassName);
if (inputStream == null) {
ClassNotFound classNotFound = new ClassNotFound(className);
selectionKey.attach(classNotFound.getMessage());
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Class '" + className + "' not found");
}
return;
}
byte[] bytecode = null;
try {
// Get bytecode of the class
bytecode = readClass(inputStream);
} finally {
inputStream.close();
}
// Create answer object
ClassAnswer classAnswer = new ClassAnswer(className, bytecode);
// Attach the answer on the key
selectionKey.attach(classAnswer.getMessage());
}
/**
* Handle the client's request asking for a resource.
* @param selectionKey key for exchanging with the client.
* @param dataBuffer the buffer that contains request.
* @throws IOException if operation fails
*/
private void handleReadResourceRequest(final SelectionKey selectionKey, final ByteBuffer dataBuffer) throws IOException {
// Build object (from input)
ResourceRequest resourceRequest = new ResourceRequest(dataBuffer);
String resourceName = resourceRequest.getResourceName();
// Answer to the client
selectionKey.interestOps(SelectionKey.OP_WRITE);
// Find the resource from the classloader
URL url = findClassLoader().getResource(resourceName);
if (url == null) {
ResourceNotFound resourceNotFound = new ResourceNotFound(resourceName);
selectionKey.attach(resourceNotFound.getMessage());
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Resource '" + resourceName + "' not found");
}
return;
}
InputStream inputStream = url.openStream();
byte[] bytes = null;
try {
// Get bytecode of the class
bytes = readClass(inputStream);
} finally {
inputStream.close();
}
// Create answer object
ResourceAnswer resourceAnswer = new ResourceAnswer(resourceName, bytes);
// Attach the answer on the key
selectionKey.attach(resourceAnswer.getMessage());
}
/**
* Handle the client's request asking for the default provider URL.
* @param selectionKey key for exchanging with the client.
* @param dataBuffer the buffer that contains request.
* @throws IOException if operation fails
*/
private void handleReadProviderURLRequest(final SelectionKey selectionKey, final ByteBuffer dataBuffer) throws IOException {
// Answer to the client (go in write mode)
selectionKey.interestOps(SelectionKey.OP_WRITE);
String providerURL = this.registryComponent.getProviderURL();
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Provider URL asked by client : '" + providerURL + "'.");
}
// Create answer object
ProviderURLAnswer providerURLAnswer = new ProviderURLAnswer(providerURL);
// Attach the answer on the key
selectionKey.attach(providerURLAnswer.getMessage());
}
/**
* Handle all write operations on channels.
* @param selectionKey the selected key.
* @throws IOException if cannot write to the channel.
*/
private void handleWrite(final SelectionKey selectionKey) throws IOException {
SocketChannel channel = (SocketChannel) selectionKey.channel();
// Write the data that was attached on the selection key
ByteBuffer buffer = (ByteBuffer) selectionKey.attachment();
if (buffer.hasRemaining()) {
channel.write(buffer);
} else {
// finished to write, close
channel.close();
}
}
/**
* Gets the bytes from the given input stream.
* @param is given input stream.
* @return the array of bytes for the given input stream.
* @throws IOException if class cannot be read.
*/
private static byte[] readClass(final InputStream is) throws IOException {
if (is == null) {
throw new IOException("Given input stream is null");
}
byte[] b = new byte[is.available()];
int len = 0;
while (true) {
int n = is.read(b, len, b.length - len);
if (n == -1) {
if (len < b.length) {
byte[] c = new byte[len];
System.arraycopy(b, 0, c, 0, len);
b = c;
}
return b;
}
len += n;
if (len == b.length) {
byte[] c = new byte[b.length + BUF_APPEND];
System.arraycopy(b, 0, c, 0, len);
b = c;
}
}
}
/**
* Launch the thread looking at the selectors.
*/
public void run() {
handleSelectors();
}
/**
* Sets the port number of the smart endpoint.
* @param portNumber the port for listening
*/
public void setPortNumber(final int portNumber) {
this.portNumber = portNumber;
}
/**
* Sets the registry component.
* @param registryComponent the given component.
*/
public void setRegistryComponent(final RegistryComponent registryComponent) {
this.registryComponent = registryComponent;
}
/**
* Gets the port number of the smart endpoint.
* @return the port number
*/
public int getPortNumber() {
return this.portNumber;
}
/**
* Sets the class loader to use for the resource lookup.
* @param classLoader the class loader to use for the resource lookup.
*/
public void setClassLoader(final ClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* The class loader to use for the resource lookup. If null, Thread's
* context class loader is used.
* @return the class loader to use for the resource lookup.
*/
public ClassLoader getClassLoader() {
return this.classLoader;
}
/**
* The class loader to use for the resource lookup. If the user didn't set
* any class loader, the thread's context class loader is returned.
* @return the class loader to use for the resource lookup.
*/
private ClassLoader findClassLoader() {
if (this.classLoader != null) {
return this.classLoader;
} else {
return Thread.currentThread().getContextClassLoader();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy