/*
* 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.datasource.msgbus;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.Requires;
import org.krakenapps.datasource.DataConverter;
import org.krakenapps.datasource.DataConverterRegistry;
import org.krakenapps.datasource.DataSource;
import org.krakenapps.datasource.DataSourceEventListener;
import org.krakenapps.datasource.DataSourceRegistry;
import org.krakenapps.msgbus.Message;
import org.krakenapps.msgbus.Request;
import org.krakenapps.msgbus.Response;
import org.krakenapps.msgbus.Session;
import org.krakenapps.msgbus.handler.CallbackType;
import org.krakenapps.msgbus.handler.MsgbusMethod;
import org.krakenapps.msgbus.handler.MsgbusPlugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component(name = "data-source-plugin")
@MsgbusPlugin
public class DataSourcePlugin {
private Logger logger = LoggerFactory.getLogger(DataSourcePlugin.class.getName());
@Requires
private DataSourceRegistry dataSourceRegistry;
@Requires
private DataConverterRegistry dataConverterRegistry;
private ConcurrentMap<Session, DataSession> callbacks;
public DataSourcePlugin() {
callbacks = new ConcurrentHashMap<Session, DataSession>();
}
@MsgbusMethod
public void getChildren(Request req, Response resp) {
String path = req.getString("path");
path = "/watchcat/" + req.getOrgId() + path;
Collection<Entry<String, DataSource>> children = dataSourceRegistry.getChildren(path);
List<Object> result = new ArrayList<Object>(children.size());
for (Entry<String, DataSource> pair : children) {
result.add(marshalDataSource(pair.getKey(), pair.getValue()));
}
resp.put("keys", dataSourceRegistry.getSubKeys(path));
resp.put("sources", result);
}
@MsgbusMethod
public void getDataSources(Request req, Response resp) {
String query = req.getString("query");
if (!query.startsWith("/"))
query = "/" + query;
query = getDataPath(req.getOrgId(), query);
Collection<Entry<String, DataSource>> dataSources = dataSourceRegistry.query(query);
List<Object> result = new ArrayList<Object>(dataSources.size());
for (Entry<String, DataSource> pair : dataSources) {
result.add(marshalDataSource(pair.getKey(), pair.getValue()));
}
resp.put("sources", result);
}
@MsgbusMethod
public void getData(Request req, Response resp) {
String path = getDataPath(req.getOrgId(), req.getString("path"));
DataSource dataSource = dataSourceRegistry.getDataSource(path);
if (dataSource == null)
throw new DataSourceNotFoundException(path);
resp.put("path", getUserDataPath(dataSource.getPath()));
resp.put("type", dataSource.getType());
resp.put("data", dataSource.getData());
}
/**
* User should not know his organization id for security reasons. Remove
* /watchcat/org_id segments.
*/
private static String getUserDataPath(String path) {
String[] tokens = path.split("/");
if (tokens.length <= 3)
return path;
if (!tokens[1].equals("watchcat"))
return path;
StringBuilder sb = new StringBuilder();
for (int i = 3; i < tokens.length; i++) {
sb.append("/");
sb.append(tokens[i]);
}
return sb.toString();
}
private String getDataPath(int orgId, String path) {
return "/watchcat/" + orgId + path;
}
@MsgbusMethod
public void checkBindingName(Request req, Response resp) {
DataSession session = getDataSession(req);
String name = req.getString("name");
resp.put("available", session.isNameAvailable(name));
}
@SuppressWarnings("unchecked")
@MsgbusMethod
public void bindDataSources(Request req, Response resp) {
String callback = req.getString("callback");
List<Object> array = (List<Object>) req.get("bindings");
DataSession updater = getDataSession(req);
for (Object item : array) {
Map<String, Object> o = (Map<String, Object>) item;
String name = (String) o.get("name");
String path = getDataPath(req.getOrgId(), (String) o.get("path"));
String converterName = (String) o.get("converter");
DataSource source = dataSourceRegistry.getDataSource(path);
if (source == null) {
logger.warn("watchcat datasource plugin: bind failed, data source [{}] not found", path);
continue;
}
DataConverter converter = dataConverterRegistry.getDataConverter(converterName);
updater.bind(new DataBinding(name, Integer.parseInt(req.getSource()), source, converter, callback));
logger.trace("watchcat datasource plugin: bind datasource [{}]", path);
}
}
@SuppressWarnings("unchecked")
@MsgbusMethod
public void unbindDataSources(Request req, Response resp) {
List<String> array = (List<String>) req.get("bindings");
DataSession updater = getDataSession(req);
for (String name : array) {
updater.unbind(name);
logger.trace("watchcat datasource plugin: release datasource binding [{}]", name);
}
}
private DataSession getDataSession(Request req) {
Session session = req.getSession();
DataSession listener = new DataSession(session);
DataSession old = callbacks.putIfAbsent(session, listener);
if (old != null)
listener = old;
return listener;
}
@MsgbusMethod(type = CallbackType.SessionClosed)
public void clearDataSources(Session session) {
logger.info("watchcat datasource plugin: session closed, clear data session [{}]", session.getId());
DataSession updater = callbacks.remove(session);
if (updater == null)
return;
updater.clear();
}
private Map<String, Object> marshalDataSource(String name, DataSource source) {
Map<String, Object> m = new HashMap<String, Object>();
String[] tokens = source.getPath().split("/");
StringBuilder sb = new StringBuilder();
for (int i = 3; i < tokens.length; i++) {
sb.append("/");
sb.append(tokens[i]);
}
String path = sb.toString();
m.put("name", name);
m.put("path", path);
m.put("type", source.getType());
return m;
}
private static class DataSession implements DataSourceEventListener {
private Logger logger = LoggerFactory.getLogger(DataSourcePlugin.class.getName());
private Session session;
private ConcurrentMap<String, DataBinding> bindings;
private ConcurrentMap<DataSource, String> names;
public DataSession(Session session) {
this.session = session;
this.bindings = new ConcurrentHashMap<String, DataBinding>();
this.names = new ConcurrentHashMap<DataSource, String>();
}
public boolean isNameAvailable(String name) {
return !bindings.containsKey(name);
}
public void bind(DataBinding binding) {
if (binding == null)
throw new IllegalArgumentException("binding must be not null");
if (binding.source == null)
throw new IllegalArgumentException("binding source must be not null");
// put data
if (bindings.putIfAbsent(binding.name, binding) != null)
throw new IllegalStateException("duplicated datasource binding name");
names.put(binding.source, binding.name);
binding.source.addListener(this);
}
public void unbind(String name) {
DataBinding binding = bindings.remove(name);
if (binding == null)
return;
names.remove(binding.source);
binding.source.removeListener(this);
}
public void clear() {
for (String name : bindings.keySet()) {
DataBinding binding = bindings.get(name);
binding.source.removeListener(this);
}
this.bindings.clear();
this.names.clear();
}
@Override
public void onUpdate(DataSource source, Object oldData, Object newData) {
String bindingName = names.get(source);
if (bindingName == null) {
logger.warn("watchcat datasource plugin: binding name not found for [{}]", source.getPath());
return;
}
logger.trace("watchcat datasource plugin: new data from binding [{}]", bindingName);
DataBinding binding = bindings.get(bindingName);
if (binding == null) {
logger.warn("watchcat datasource plugin: binding not found for [{}]", bindingName);
return;
}
if (binding.converter != null)
newData = binding.converter.convert(newData);
Message m = new Message();
m.setSession(session.getId());
m.setType(Message.Type.Trap);
m.setMethod(binding.callback);
m.setTarget(Integer.toString(binding.processId));
m.getParameters().put("source", getUserDataPath(source.getPath()));
m.getParameters().put("binding", bindingName);
m.getParameters().put("type", source.getType());
m.getParameters().put("data", newData);
session.send(m);
}
}
private static class DataBinding {
public String name;
public DataSource source;
public DataConverter converter;
public String callback;
public int processId;
public DataBinding(String name, int processId, DataSource source, DataConverter converter, String callback) {
this.processId = processId;
this.name = name;
this.source = source;
this.converter = converter;
this.callback = callback;
}
}
}