/**
* BeanServer.java
*
* Copyright 2012 Niolex, Inc.
*
* Niolex 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.niolex.commons.remote;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.niolex.commons.stream.StreamUtil;
import org.apache.niolex.commons.util.SystemUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class will export beans to remote telnet, you can get, list and set properties.<br>
* We provided invoke directive for execute methods also.<br>
* We will accept at most 10 concurrent connections.
*
* @author <a href="mailto:xiejiyun@gmail.com">Xie, Jiyun</a>
* @version 1.0.1
* @since 2012-7-25
*/
public class BeanServer implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(BeanServer.class);
// The bean map.
private final ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<String, Object>();
// As the name indicates.
private ServerSocket listenerSocket;
// As the name indicates.
private volatile boolean isListening = false;
// As the name indicates.
private int port = 8597;
/**
* If the specified key is not already associated with a value, associate it with the given value.
* This is equivalent to:
* <pre>
* if (!map.containsKey(key))
* return map.put(key, value);
* else
* return map.get(key);</pre>
*
* except that the action is performed atomically.
*
* @param key key with which the specified value is associated
* @param value value to be associated with the specified key
* @return the previous value associated with the specified key,
* or <tt>null</tt> if there was no mapping for the key
* @throws NullPointerException if the specified key or value is null
*/
public Object putIfAbsent(String key, Object value) {
return map.putIfAbsent(key, value);
}
/**
* Removes the key (and its corresponding value) from this map. This method does nothing if the key
* is not in the map.
*
* @param key key with which the specified value is associated
* @return the previous value associated with key, or null if there was no mapping for key
*/
public Object remove(Object key) {
return map.remove(key);
}
/**
* Replaces the entry for a key only if currently mapped to a given value. This is equivalent to
* <pre>
* if (map.containsKey(key) && map.get(key).equals(oldValue)) {
* map.put(key, newValue);
* return true;
* } else return false;</pre>
* except that the action is performed atomically.
*
* @param key key with which the specified value is associated
* @param oldValue value expected to be associated with the specified key
* @param newValue value to be associated with the specified key
* @return true if the value is replaced
*/
public boolean replace(String key, Object oldValue, Object newValue) {
return map.replace(key, oldValue, newValue);
}
/**
* Start this bean server to listen to telnet request.
*
* @return true if server started successfully
*/
public boolean start() {
try {
listenerSocket = new ServerSocket(port);
// Setting the timeout for accept method. Avoid can not be shutting
// down since blocking thread when waiting accept.
listenerSocket.setSoTimeout(10000);
// Start Listening
isListening = true;
Thread s = new Thread(this, "BeanServer");
s.setDaemon(true);
s.start();
LOG.info("BeanServer started at port {}.", port);
return true;
} catch (IOException e) {
LOG.error("Can not start the bean server.", e);
}
return false;
}
/**
* Stop this bean server.
* This method will return immediately, but the server will continue to be up for a few seconds.
*/
public void stop() {
isListening = false;
}
/**
* Do the socket listening, process connection. We accept at most 10 connections.
*
* Override super method
* @see java.lang.Runnable#run()
*/
public void run() {
final AtomicInteger connectionNumber = new AtomicInteger();
while (isListening) {
try {
Socket socket = listenerSocket.accept();
// We accept at most 10 connections.
if (connectionNumber.incrementAndGet() > 10) {
connectionNumber.decrementAndGet();
StreamUtil.writeUTF8IgnoreException(socket.getOutputStream(), "Too many connections.\n");
SystemUtil.close(socket);
continue;
}
ConnectionWorker connection = new ConnectionWorker(socket, map, connectionNumber);
Thread c = new Thread(connection, "BeanServer.ConnectionWorker");
c.setDaemon(true);
c.start();
} catch (SocketTimeoutException e) {
// Ignore the timeout exception thrown by accept.
} catch (IOException e) {
LOG.error("Can not build the connection with this client.", e);
}
}
LOG.info("BeanServer stoped.");
}
/**
* Set the port for Bean Server to listen to.
*
* @param port the new port number
*/
public void setPort(int port) {
this.port = port;
}
}