/*
* Copyright 2010 NCHOVY
*
* Licensed 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.krakenapps.rpc.impl;
import java.net.InetSocketAddress;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import org.apache.felix.ipojo.annotations.Component;
import org.apache.felix.ipojo.annotations.Invalidate;
import org.apache.felix.ipojo.annotations.Provides;
import org.apache.felix.ipojo.annotations.Requires;
import org.apache.felix.ipojo.annotations.Validate;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.ssl.SslHandler;
import org.krakenapps.api.KeyStoreManager;
import org.krakenapps.confdb.Config;
import org.krakenapps.confdb.ConfigCollection;
import org.krakenapps.confdb.ConfigDatabase;
import org.krakenapps.confdb.ConfigIterator;
import org.krakenapps.confdb.ConfigService;
import org.krakenapps.confdb.Predicate;
import org.krakenapps.confdb.Predicates;
import org.krakenapps.rpc.RpcBindingProperties;
import org.krakenapps.rpc.RpcClient;
import org.krakenapps.rpc.RpcConnection;
import org.krakenapps.rpc.RpcAgent;
import org.krakenapps.rpc.RpcConnectionEventListener;
import org.krakenapps.rpc.RpcConnectionProperties;
import org.krakenapps.rpc.RpcPeerRegistry;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component(name = "rpc-agent")
@Provides
public class RpcAgentImpl implements RpcAgent {
private final Logger logger = LoggerFactory.getLogger(RpcAgentImpl.class.getName());
private BundleContext bc;
private RpcPeerRegistry peerRegistry;
private RpcHandler handler;
private RpcServiceTracker tracker;
private ConcurrentMap<RpcBindingProperties, Channel> bindings;
@Requires
private KeyStoreManager keyStoreManager;
@Requires
private ConfigService conf;
public RpcAgentImpl(BundleContext bc) {
this.bc = bc;
peerRegistry = new RpcPeerRegistryImpl(conf);
handler = new RpcHandler(getGuid(), peerRegistry);
tracker = new RpcServiceTracker(bc, handler);
bindings = new ConcurrentHashMap<RpcBindingProperties, Channel>();
}
@Validate
public void start() throws Exception {
try {
handler.start();
bc.addServiceListener(tracker);
// open configured bindings
ConfigDatabase db = conf.ensureDatabase("kraken-rpc");
ConfigIterator it = db.findAll(RpcBindingProperties.class);
try {
while (it.hasNext()) {
Config c = it.next();
RpcBindingProperties props = c.getDocument(RpcBindingProperties.class);
if (props.getKeyAlias() != null && props.getTrustAlias() != null)
bindSsl(props);
else
bind(props);
}
} finally {
it.close();
}
// register all auto-wiring RPC services.
tracker.scan();
} catch (Exception e) {
stop();
throw e;
}
}
@Invalidate
public void stop() {
for (RpcBindingProperties props : bindings.keySet())
unbind(props);
bc.removeServiceListener(tracker);
handler.stop();
}
@Override
public String getGuid() {
ConfigDatabase db = conf.ensureDatabase("kraken-rpc");
ConfigCollection col = db.ensureCollection("agent");
Config c = col.findOne(null);
if (c != null) {
@SuppressWarnings("unchecked")
Map<String, Object> doc = (Map<String, Object>) c.getDocument();
return (String) doc.get("guid");
}
Map<String, Object> doc = new HashMap<String, Object>();
String guid = UUID.randomUUID().toString();
doc.put("guid", guid);
col.add(doc);
return guid;
}
@Override
public Collection<RpcBindingProperties> getBindings() {
return new ArrayList<RpcBindingProperties>(bindings.keySet());
}
@Override
public void open(RpcBindingProperties props) {
if (bindings.containsKey(props))
throw new IllegalStateException("already opened: " + props);
if (props.getKeyAlias() != null && props.getTrustAlias() != null)
bindSsl(props);
else
bind(props);
ConfigDatabase db = conf.ensureDatabase("kraken-rpc");
db.add(props, "kraken-rpc", "opened " + props);
}
@Override
public void close(RpcBindingProperties props) {
ConfigDatabase db = conf.ensureDatabase("kraken-rpc");
Predicate p = Predicates.and(Predicates.field("addr", props.getHost()),
Predicates.field("port", props.getPort()));
Config c = db.findOne(RpcBindingProperties.class, p);
if (c != null)
db.remove(c, false, "kraken-rpc", "closed " + props);
unbind(props);
}
@Override
public RpcConnection connectSsl(RpcConnectionProperties props) {
RpcClient client = new RpcClient(handler);
return client.connectSsl(props);
}
@Override
public RpcConnection connect(RpcConnectionProperties props) {
RpcClient client = new RpcClient(handler);
return client.connect(props);
}
private Channel bindSsl(RpcBindingProperties props) {
ChannelFactory factory = new NioServerSocketChannelFactory(Executors.newCachedThreadPool(),
Executors.newCachedThreadPool());
final String keyAlias = props.getKeyAlias();
final String trustAlias = props.getTrustAlias();
ServerBootstrap bootstrap = new ServerBootstrap(factory);
bootstrap.setOption("child.tcpNoDelay", true);
bootstrap.setOption("child.keepAlive", true);
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
@Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
// ssl
TrustManagerFactory tmf = keyStoreManager.getTrustManagerFactory(trustAlias, "SunX509");
KeyManagerFactory kmf = keyStoreManager.getKeyManagerFactory(keyAlias, "SunX509");
TrustManager[] trustManagers = null;
KeyManager[] keyManagers = null;
if (tmf != null)
trustManagers = tmf.getTrustManagers();
if (kmf != null)
keyManagers = kmf.getKeyManagers();
SSLContext serverContext = SSLContext.getInstance("TLS");
serverContext.init(keyManagers, trustManagers, new SecureRandom());
SSLEngine engine = serverContext.createSSLEngine();
engine.setUseClientMode(false);
engine.setNeedClientAuth(true);
pipeline.addLast("ssl", new SslHandler(engine));
// decoder, encoder and handler
pipeline.addLast("decoder", new RpcDecoder());
pipeline.addLast("encoder", new RpcEncoder());
pipeline.addLast("handler", handler);
return pipeline;
}
});
InetSocketAddress address = new InetSocketAddress(props.getHost(), props.getPort());
Channel channel = bootstrap.bind(address);
bindings.put(props, channel);
logger.info("kraken-rpc: {} ssl port opened", address);
return channel;
}
private Channel bind(RpcBindingProperties props) {
ChannelFactory factory = new NioServerSocketChannelFactory(Executors.newCachedThreadPool(),
Executors.newCachedThreadPool());
ServerBootstrap bootstrap = new ServerBootstrap(factory);
ChannelPipeline pipeline = bootstrap.getPipeline();
// decoder, encoder and handler
pipeline.addLast("decoder", new RpcDecoder());
pipeline.addLast("encoder", new RpcEncoder());
pipeline.addLast("handler", handler);
bootstrap.setOption("child.tcpNoDelay", true);
bootstrap.setOption("child.keepAlive", true);
InetSocketAddress address = new InetSocketAddress(props.getHost(), props.getPort());
Channel channel = bootstrap.bind(address);
bindings.put(props, channel);
logger.info("kraken-rpc: {} port opened", address);
return channel;
}
private void unbind(RpcBindingProperties props) {
Channel channel = bindings.remove(props);
if (channel == null)
return;
logger.info("kraken-rpc: unbinding [{}]", props);
channel.unbind();
channel.close().awaitUninterruptibly();
}
@Override
public RpcConnection findConnection(int id) {
return handler.findConnection(id);
}
@Override
public Collection<RpcConnection> getConnections() {
return handler.getConnections();
}
@Override
public RpcPeerRegistry getPeerRegistry() {
return peerRegistry;
}
@Override
public void addConnectionListener(RpcConnectionEventListener listener) {
handler.addConnectionListener(listener);
}
@Override
public void removeConnectionListener(RpcConnectionEventListener listener) {
handler.removeConnectionListener(listener);
}
}