/*
* Copyright 2011 Future Systems, Inc
*
* 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.msgbus.impl;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.felix.ipojo.annotations.Component;
import org.apache.felix.ipojo.annotations.Provides;
import org.apache.felix.ipojo.annotations.Requires;
import org.krakenapps.msgbus.Message;
import org.krakenapps.msgbus.MessageBus;
import org.krakenapps.msgbus.PushApi;
import org.krakenapps.msgbus.PushCondition;
import org.krakenapps.msgbus.PushInterceptor;
import org.krakenapps.msgbus.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component(name = "msgbus-push-api")
@Provides
public class PushApiImpl implements PushApi {
private final Logger logger = LoggerFactory.getLogger(PushApiImpl.class.getName());
@Requires
private MessageBus msgbus;
// organization to sessions (id set)
private ConcurrentMap<String, Set<String>> orgSessionMap = new ConcurrentHashMap<String, Set<String>>();
// session to processes (id set)
private ConcurrentMap<String, Set<Binding>> pushBindingsMap = new ConcurrentHashMap<String, Set<Binding>>();
private ConcurrentMap<String, PushInterceptor> pushInterceptorsMap = new ConcurrentHashMap<String, PushInterceptor>();
private ConcurrentMap<PushCondition.Key, PushCondition> pushConditions = new ConcurrentHashMap<PushCondition.Key, PushCondition>();
@Override
public void subscribe(String orgDomain, int sessionId, int processId, String callback) {
Map<String, Object> m = new HashMap<String, Object>();
subscribe(orgDomain, Integer.toString(sessionId), processId, callback, m);
}
@Override
public void subscribe(String orgDomain, int sessionId, int processId, String callback, Map<String, Object> options) {
subscribe(orgDomain, Integer.toString(sessionId), processId, callback, options);
}
@Override
public void subscribe(String orgDomain, String sessionId, int processId, String callback,
Map<String, Object> options) {
Set<String> sessions = orgSessionMap.get(orgDomain);
if (sessions == null)
orgSessionMap.putIfAbsent(orgDomain, Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>()));
Set<Binding> bindings = pushBindingsMap.get(sessionId);
if (bindings == null)
pushBindingsMap.putIfAbsent(callback, Collections.newSetFromMap(new ConcurrentHashMap<Binding, Boolean>()));
sessions = orgSessionMap.get(orgDomain);
sessions.add(sessionId);
bindings = pushBindingsMap.get(callback);
Binding binding = new Binding(sessionId, processId);
bindings.add(binding);
PushCondition condition = new PushCondition(orgDomain, binding.sessionId, binding.processId, callback, options);
pushConditions.put(condition.getKey(), condition);
logger.trace("kraken msgbus: subscribe push, org [{}], session [{}], callback [{}]", new Object[] { orgDomain,
sessionId, callback });
}
@Override
public void unsubscribe(String orgDomain, int sessionId, int processId, String callback) {
unsubscribe(orgDomain, Integer.toString(sessionId), processId, callback);
}
@Override
public void unsubscribe(String orgDomain, String sessionId, int processId, String callback) {
logger.trace("kraken msgbus: unsubscribe push, org [{}], session [{}], callback [{}]", new Object[] {
orgDomain, sessionId, callback });
Set<Binding> bindings = pushBindingsMap.get(callback);
if (bindings != null) {
bindings.remove(new Binding(sessionId, processId));
if (bindings.isEmpty()) {
pushBindingsMap.remove(callback);
}
}
// clear condition
pushConditions.remove(new PushCondition.Key(orgDomain, sessionId, processId, callback));
}
@Override
public void addInterceptor(String callback, PushInterceptor interceptor) {
if (pushInterceptorsMap.putIfAbsent(callback, interceptor) != null)
throw new IllegalStateException("already added");
logger.trace("dom push api: add interceptor, callback [{}]", new Object[] { callback });
}
@Override
public void removeInterceptor(String callback) {
pushInterceptorsMap.remove(callback);
logger.trace("dom push api: remove interceptor, callback [{}]", new Object[] { callback });
}
@Override
public void push(String orgDomain, String callback, Map<String, Object> m) {
Set<String> sessions = orgSessionMap.get(orgDomain);
if (sessions == null)
return;
Set<Binding> bindings = pushBindingsMap.get(callback);
if (bindings == null)
return;
for (Binding binding : bindings) {
if (sessions.contains(binding.sessionId)) {
Message msg = createMessage(orgDomain, binding, callback, m);
if (logger.isTraceEnabled())
tracePush(orgDomain, callback, m, binding.sessionId, binding);
msgbus.send(msg);
}
}
}
@Override
public void push(Session session, String callback, Map<String, Object> m) {
Set<Binding> bindings = pushBindingsMap.get(callback);
if (bindings == null)
return;
for (Binding binding : bindings) {
if (binding.sessionId.equals(session.getGuid())) {
String orgDomain = session.getOrgDomain();
Message msg = createMessage(orgDomain, binding, callback, m);
if (logger.isTraceEnabled())
tracePush(orgDomain, callback, m, binding.sessionId, binding);
msgbus.send(msg);
}
}
}
private Message createMessage(String orgDomain, Binding binding, String callback, Map<String, Object> m) {
Message msg = new Message();
msg.setSession(binding.sessionId);
msg.setType(Message.Type.Trap);
msg.setMethod(callback);
msg.setTarget(Integer.toString(binding.processId));
Map<String, Object> params = null;
PushInterceptor interceptor = pushInterceptorsMap.get(callback);
if (interceptor != null) {
PushCondition.Key key = new PushCondition.Key(orgDomain, binding.sessionId, binding.processId, callback);
PushCondition condition = pushConditions.get(key);
params = interceptor.onPush(condition, m);
} else {
params = m;
}
for (String key : params.keySet()) {
msg.getParameters().put(key, params.get(key));
}
return msg;
}
private void tracePush(String orgDomain, String method, Map<String, Object> m, String sessionId, Binding binding) {
StringBuilder sb = new StringBuilder();
int i = 0;
for (String key : m.keySet()) {
if (i != 0)
sb.append(", ");
sb.append(key);
sb.append('=');
sb.append(m.get(key));
i++;
}
final String template = "kraken msgbus: push org [{}], session [{}], process [{}], callback [{}], msg [{}]";
logger.trace(template, new Object[] { orgDomain, sessionId, binding.processId, method, sb.toString() });
}
@Override
public void sessionClosed(String orgDomain, int sessionId) {
sessionClosed(orgDomain, Integer.toString(sessionId));
}
@Override
public void sessionClosed(String orgDoamin, String sessionId) {
logger.trace("kraken msgbus: push session closed, org [{}], session [{}]", orgDoamin, sessionId);
cleanSession(orgDoamin, sessionId);
}
private void cleanSession(String orgDomain, String sessionId) {
Set<String> bindingsToRemove = Collections.newSetFromMap(new HashMap<String, Boolean>());
for (Entry<String, Set<Binding>> entry : pushBindingsMap.entrySet()) {
Set<Binding> bindings = entry.getValue();
Set<Binding> toRemove = Collections.newSetFromMap(new HashMap<Binding, Boolean>());
for (Binding binding : bindings) {
if (binding.sessionId.equals(sessionId))
toRemove.add(binding);
}
bindings.removeAll(toRemove);
if (bindings.isEmpty())
bindingsToRemove.add(entry.getKey());
}
for (String callback : bindingsToRemove)
pushBindingsMap.remove(callback);
Set<String> sessions = orgSessionMap.get(orgDomain);
if (sessions != null)
sessions.remove(sessionId);
// clear all related conditions
for (PushCondition.Key key : pushConditions.keySet())
if (key.getOrgDomain() == orgDomain && key.getSessionId() == sessionId)
pushConditions.remove(key);
}
private static class Binding {
private String sessionId;
private int processId;
public Binding(String sessionId, int processId) {
this.processId = processId;
this.sessionId = sessionId;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + processId;
result = prime * result + ((sessionId == null) ? 0 : sessionId.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Binding other = (Binding) obj;
if (processId != other.processId)
return false;
if (sessionId == null) {
if (other.sessionId != null)
return false;
} else if (!sessionId.equals(other.sessionId))
return false;
return true;
}
}
}