/******************************************************************************
* Product: Adempiere ERP & CRM Smart Business Solution *
* Copyright (C) 2010 Heng Sin Low *
* This program is free software; you can redistribute it and/or modify it *
* under the terms version 2 of the GNU General Public License as published *
* by the Free Software Foundation. This program is distributed in the hope *
* that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
* See the GNU General Public License for more details. *
* You should have received a copy of the GNU General Public License along *
* with this program; if not, write to the Free Software Foundation, Inc., *
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
*****************************************************************************/
package org.adempiere.base.event;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
import org.adempiere.base.BaseActivator;
import org.adempiere.base.event.annotations.BaseEventHandler;
import org.compiere.model.PO;
import org.compiere.util.CLogger;
import org.compiere.util.Env;
import org.compiere.util.Ini;
import org.compiere.util.Util;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
/**
* Simple wrapper for the osgi event admin service.
* Usage: EventManager.getInstance().sendEvent/postEvent
* @author hengsin
*
*/
public class EventManager implements IEventManager {
private EventAdmin eventAdmin;
private static IEventManager instance = null;
private final static CLogger log = CLogger.getCLogger(EventManager.class);
private final static Object mutex = new Object();
private Map>> registrations = new HashMap>>();
private List blackListEventHandlers = null;
private Map> blackListTopicMap = null;
/**
* @param eventAdmin
*/
public void bindEventAdmin(EventAdmin eventAdmin) {
synchronized (mutex) {
if (instance == null) {
instance = this;
retrieveBlacklistHandlers();
mutex.notifyAll();
}
}
this.eventAdmin = eventAdmin;
}
private void retrieveBlacklistHandlers() {
blackListEventHandlers = new ArrayList();
blackListTopicMap = new HashMap>();
String path = Ini.getAdempiereHome();
File file = new File(path, "event.handlers.blacklist");
if (file.exists()) {
BufferedReader br = null;
try {
FileReader reader = new FileReader(file);
br = new BufferedReader(reader);
String s = null;
do {
s = br.readLine();
if (!Util.isEmpty(s)) {
s = s.trim();
s = s.replaceAll(" ", "");
if (s.endsWith("[*]")) {
blackListEventHandlers.add(s.substring(0, s.length()-3));
} else {
int topicStart = s.indexOf("[");
if (topicStart <= 0)
continue;
int topicEnd = s.indexOf("]", topicStart);
if (topicEnd <= 0)
continue;
String topicValue = s.substring(topicStart+1, topicEnd);
String className = s.substring(0, topicStart);
if (blackListEventHandlers.contains(className))
continue;
List topicList = blackListTopicMap.get(className);
if (topicList == null) {
topicList = new ArrayList();
blackListTopicMap.put(className, topicList);
}
String[] topics = topicValue.split("[,]");
for(String topic : topics) {
if (!topicList.contains(topic)) {
topicList.add(topic);
}
}
}
}
} while (s != null);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {}
}
}
}
}
/**
* @param eventAdmin
*/
public void unbindEventAdmin(EventAdmin eventAdmin) {
this.eventAdmin = null;
}
/**
* Get the singleton instance created by the osgi service framework
* @return EventManager
*/
public static IEventManager getInstance() {
synchronized (mutex) {
while (instance == null) {
try {
mutex.wait(10000);
} catch (InterruptedException e) {
}
}
}
return instance;
}
/* (non-Javadoc)
* @see org.adempiere.base.event.IEventManager#postEvent(org.osgi.service.event.Event)
*/
@Override
public boolean postEvent(Event event) {
if (eventAdmin != null) {
//copy current session context for restoration in asynchronous event thread
if (!event.containsProperty(EVENT_CONTEXT)) {
Map properties = new HashMap<>();
for (String key : event.getPropertyNames()) {
properties.put(key, event.getProperty(key));
}
properties.put(EVENT_CONTEXT, getCurrentSessionContext());
event = newEvent(event.getTopic(), properties, true);
}
eventAdmin.postEvent(event);
return true;
}
return false;
}
/* (non-Javadoc)
* @see org.adempiere.base.event.IEventManager#sendEvent(org.osgi.service.event.Event)
*/
@Override
public boolean sendEvent(Event event) {
if (eventAdmin != null) {
eventAdmin.sendEvent(event);
return true;
}
return false;
}
/* (non-Javadoc)
* @see org.adempiere.base.event.IEventManager#register(java.lang.String, org.osgi.service.event.EventHandler)
*/
@Override
public boolean register(String topic, EventHandler eventHandler) {
return register(topic, null, eventHandler);
}
/* (non-Javadoc)
* @see org.adempiere.base.event.IEventManager#register(java.lang.String[], org.osgi.service.event.EventHandler)
*/
@Override
public boolean register(String[] topics, EventHandler eventHandler) {
return register(topics, null, eventHandler);
}
/* (non-Javadoc)
* @see org.adempiere.base.event.IEventManager#register(java.lang.String, java.lang.String, org.osgi.service.event.EventHandler)
*/
@Override
public boolean register(String topic, String filter, EventHandler eventHandler) {
String[] topics = new String[] {topic};
return register(topics, filter, eventHandler);
}
/**
* @param topics List of event topic. If only some of the event topic is black listed,
* the method will return false and remove the black listed event topic from topics list.
* @param eventHandler
* @return true if eventhandler is black listed (i.e don't register the service) for topics
*/
private boolean isBlackListed(List topics, EventHandler eventHandler) {
String className = eventHandler.getClass().getName();
if (eventHandler instanceof BaseEventHandler beh) {
if (beh.getDelegateClass() != null)
className = beh.getDelegateClass().getName();
}
if (blackListEventHandlers != null && blackListEventHandlers.contains(className))
return true;
if (blackListTopicMap != null && !blackListTopicMap.isEmpty()) {
List blackListed = blackListTopicMap.get(className);
if (blackListed != null && !blackListed.isEmpty()) {
Iterator iterator = topics.iterator();
while(iterator.hasNext()) {
String topic = iterator.next();
if (blackListed.contains(topic))
iterator.remove();
}
}
}
return false;
}
/* (non-Javadoc)
* @see org.adempiere.base.event.IEventManager#register(java.lang.String[], java.lang.String, org.osgi.service.event.EventHandler)
*/
@Override
public boolean register(String[] topics, String filter, EventHandler eventHandler) {
BundleContext bundleContext = BaseActivator.getBundleContext();
if (bundleContext == null) {
log.severe("No bundle context. Topic="+Arrays.toString(topics));
return false;
}
//check black listed event topics
List topicList = Arrays.stream(topics).collect(Collectors.toCollection(ArrayList::new));
if (isBlackListed(topicList, eventHandler))
return false;
if (topicList.isEmpty())
return false;
if (topicList.size() != topics.length)
topics = topicList.toArray(new String[0]);
Dictionary d = new Hashtable();
d.put(EventConstants.EVENT_TOPIC, topics);
if (filter != null)
d.put(EventConstants.EVENT_FILTER, filter);
ServiceRegistration> registration = bundleContext.registerService(EventHandler.class.getName(), eventHandler, d);
synchronized(registrations) {
List> list = registrations.get(eventHandler);
if (list == null) {
list = new ArrayList>();
registrations.put(eventHandler, list);
}
list.add(registration);
}
return true;
}
/* (non-Javadoc)
* @see org.adempiere.base.event.IEventManager#unregister(org.osgi.service.event.EventHandler)
*/
@Override
public boolean unregister(EventHandler eventHandler) {
List> serviceRegistrations = null;
synchronized(registrations) {
serviceRegistrations = registrations.remove(eventHandler);
}
if (serviceRegistrations == null)
return false;
for (ServiceRegistration> registration : serviceRegistrations)
registration.unregister();
return true;
}
/**
* @param topic
* @param data
* @return new Event instance
*/
public static Event newEvent(String topic, Object data) {
return newEvent(topic, data, false);
}
/**
* Create new event instance. If copySessionContext is true, a copy of current session context is added as EVENT_CONTEXT property to event data.
* @param topic
* @param data
* @param copySessionContext true to copy current session context (usually for postEvent).
* @return new Event instance
*/
@SuppressWarnings("unchecked")
public static Event newEvent(String topic, Object data, boolean copySessionContext) {
Event event = null;
if (data instanceof Dictionary,?>) {
Dictionarydict = (Dictionary)data;
if (dict.get(EVENT_ERROR_MESSAGES) == null)
dict.put(EVENT_ERROR_MESSAGES, new ArrayList());
if (copySessionContext)
dict.put(EVENT_CONTEXT, getCurrentSessionContext());
event = new Event(topic, dict);
} else if (data instanceof Map, ?>) {
Map map = (Map)data;
if (!map.containsKey(EVENT_ERROR_MESSAGES))
map.put(EVENT_ERROR_MESSAGES, new ArrayList());
if (copySessionContext)
map.put(EVENT_CONTEXT, getCurrentSessionContext());
event = new Event(topic, map);
} else {
Map map = new HashMap(3);
map.put(EventConstants.EVENT_TOPIC, topic);
if (data != null) {
map.put(EVENT_DATA, data);
if (data instanceof PO po)
map.put(TABLE_NAME_PROPERTY, po.get_TableName());
}
map.put(EVENT_ERROR_MESSAGES, new ArrayList());
if (copySessionContext)
map.put(EVENT_CONTEXT, getCurrentSessionContext());
event = new Event(topic, map);
}
return event;
}
/**
* @return copy of current session context
*/
private static Properties getCurrentSessionContext() {
Properties context = new Properties();
Env.setContext(context, Env.AD_CLIENT_ID, Env.getAD_Client_ID(Env.getCtx()));
Env.setContext(context, Env.AD_ORG_ID, Env.getAD_Org_ID(Env.getCtx()));
Env.setContext(context, Env.AD_USER_ID, Env.getAD_User_ID(Env.getCtx()));
Env.setContext(context, Env.AD_ROLE_ID, Env.getAD_Role_ID(Env.getCtx()));
Env.setContext(context, Env.M_WAREHOUSE_ID, Env.getContext(Env.getCtx(), Env.M_WAREHOUSE_ID));
Env.setContext(context, Env.LANGUAGE, Env.getContext(Env.getCtx(), Env.LANGUAGE));
return context;
}
/**
*
* @param topic
* @param properties
* @return event object
*/
public static Event newEvent(String topic, EventProperty ...properties) {
Event event = null;
Map map = new HashMap(3);
if (properties != null) {
for(int i = 0; i < properties.length; i++) {
map.put(properties[i].name, properties[i].value);
}
if (!map.containsKey(EventConstants.EVENT_TOPIC))
map.put(EventConstants.EVENT_TOPIC, topic);
if (!map.containsKey(EVENT_ERROR_MESSAGES))
map.put(EVENT_ERROR_MESSAGES, new ArrayList());
}
event = new Event(topic, map);
return event;
}
}