/*
* 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.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.krakenapps.rpc.RpcAsyncCallback;
import org.krakenapps.rpc.RpcAsyncResult;
import org.krakenapps.rpc.RpcAsyncTable;
import org.krakenapps.rpc.RpcBlockingTable;
import org.krakenapps.rpc.RpcConnection;
import org.krakenapps.rpc.RpcException;
import org.krakenapps.rpc.RpcMessage;
import org.krakenapps.rpc.RpcSession;
import org.krakenapps.rpc.RpcSessionEventCallback;
import org.krakenapps.rpc.RpcSessionState;
import org.krakenapps.rpc.RpcWaitingCall;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RpcSessionImpl implements RpcSession {
private final Logger logger = LoggerFactory.getLogger(RpcSessionImpl.class.getName());
private int id;
private String serviceName;
private RpcConnection connection;
private Map<String, Object> props;
private RpcSessionState state = RpcSessionState.Opened;
/**
* To cancel all blocking calls at close()
*/
private Set<Integer> blockingCalls;
/**
* event callbacks
*/
private Set<RpcSessionEventCallback> callbacks;
public RpcSessionImpl(int id, String serviceName, RpcConnection connection) {
this.id = id;
this.serviceName = serviceName;
this.connection = connection;
this.props = new HashMap<String, Object>();
this.blockingCalls = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>());
this.callbacks = Collections.newSetFromMap(new ConcurrentHashMap<RpcSessionEventCallback, Boolean>());
}
@Override
public RpcSessionState getState() {
return state;
}
@Override
public int getId() {
return id;
}
@Override
public RpcConnection getConnection() {
return connection;
}
@Override
public String getServiceName() {
return serviceName;
}
@Override
public Object getProperty(String name) {
return props.get(name);
}
@Override
public void setProperty(String name, Object value) {
props.put(name, value);
}
@Override
public RpcAsyncResult call(String method, Object[] params, RpcAsyncCallback callback) {
verify();
// NOTE: how about return async future object for blocking?
RpcConnection conn = getConnection();
RpcAsyncTable table = conn.getAsyncTable();
int msgId = conn.nextMessageId();
RpcMessage msg = RpcMessage.newCall(msgId, getId(), method, params);
if (logger.isTraceEnabled())
logger.trace("kraken-rpc: async call [id={}, session={}, method={}]", new Object[] { msgId, getId(), method });
RpcAsyncResult result = new RpcAsyncResult(callback);
table.submit(msgId, result);
conn.send(msg);
return result;
}
@Override
public Object call(String method, Object... params) throws RpcException, InterruptedException {
return call(method, params, 0);
}
@Override
public Object call(String method, Object[] params, long timeout) throws RpcException, InterruptedException {
verify();
// send rpc call message
RpcConnection conn = getConnection();
if (!method.equals("peering-request") && !method.equals("authenticate"))
conn.waitPeering(timeout);
RpcBlockingTable table = conn.getBlockingTable();
int msgId = conn.nextMessageId();
RpcMessage msg = RpcMessage.newCall(msgId, getId(), method, params);
RpcWaitingCall call = table.set(msgId);
conn.send(msg);
if (logger.isTraceEnabled())
logger.trace("kraken-rpc: blocking call [id={}, session={}, method={}]", new Object[] { msgId, getId(), method });
// set blocking call
blockingCalls.add(msgId);
// wait response infinitely
RpcMessage message = null;
if (timeout == 0)
message = table.await(call);
else {
message = table.await(call, timeout);
}
blockingCalls.remove(msgId);
if (message == null)
throw new RpcException("rpc timeout: message " + msgId);
// response received
Object type = message.getHeader("type");
if (type.equals("rpc-error")) {
String cause = message.getString("cause");
if (logger.isDebugEnabled())
logger.debug("kraken-rpc: catching exception for id {}, method {}", msgId, method);
throw new RpcException(cause);
}
if (type.equals("rpc-ret")) {
if (logger.isDebugEnabled())
logger.debug("kraken-rpc: response for id {}, method {}", msgId, method);
return message.get("ret");
}
// unknown type of return message
throw new RpcException("unknown rpc message type: " + type);
}
@Override
public void post(String method, Object... params) {
verify();
// send rpc call message
RpcConnection conn = getConnection();
conn.waitPeering();
RpcMessage msg = RpcMessage.newPost(conn.nextMessageId(), getId(), method, params);
conn.send(msg);
}
@Override
public void close() {
state = RpcSessionState.Closed;
// cancel all blocking calls
RpcConnection conn = getConnection();
RpcBlockingTable table = conn.getBlockingTable();
for (Integer msgId : blockingCalls)
table.cancel(msgId);
blockingCalls.clear();
// invoke all session callbacks
for (RpcSessionEventCallback callback : callbacks) {
try {
callback.sessionClosed(this);
} catch (Exception e) {
logger.warn("kraken-rpc: session callback should not throw exception", e);
}
}
}
private void verify() {
if (state == RpcSessionState.Closed)
throw new IllegalStateException("session closed: " + id);
}
@Override
public void addListener(RpcSessionEventCallback callback) {
callbacks.add(callback);
}
@Override
public void removeListener(RpcSessionEventCallback callback) {
callbacks.remove(callback);
}
@Override
public String toString() {
return String.format("id=%d, service=%s, peer=%s", id, serviceName, connection.getRemoteAddress());
}
}