/*
* Copyright (C) 2004-2008 Jive Software. All rights reserved.
*
* 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.jivesoftware.xmpp.workgroup.interceptor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.BeanUtils;
import org.jivesoftware.util.ClassUtils;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.xmpp.workgroup.Workgroup;
import org.jivesoftware.xmpp.workgroup.WorkgroupManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
import org.xmpp.packet.Packet;
/**
* Base class for fastpath packet interceptors.
*
* @author Gaston Dombiak
*/
public abstract class InterceptorManager {
private static final Logger Log = LoggerFactory.getLogger(InterceptorManager.class);
private List<PacketInterceptor> availableInterceptors =
new CopyOnWriteArrayList<PacketInterceptor>();
private CopyOnWriteArrayList<PacketInterceptor> globalInterceptors =
new CopyOnWriteArrayList<PacketInterceptor>();
private ConcurrentHashMap<String, CopyOnWriteArrayList<PacketInterceptor>> localInterceptors =
new ConcurrentHashMap<String, CopyOnWriteArrayList<PacketInterceptor>>();
/**
* Constructs a new InterceptorManager.
*/
protected InterceptorManager() {
loadAvailableInterceptors();
loadGlobalInterceptors();
}
/**
* Returns an unmodifiable list of global packet interceptors. Global
* interceptors are applied to all packets read and sent by the server.
*
* @return an unmodifiable list of the global packet interceptors.
*/
public List<PacketInterceptor> getInterceptors() {
return Collections.unmodifiableList(globalInterceptors);
}
/**
* Returns an unmodifiable list of packet interceptors specific for the specified workgroup.
*
* @param workgroup the bare JID address of the workgroup.
* @return an unmodifiable list of the packet interceptors specific for the specified workgroup.
*/
public List<PacketInterceptor> getInterceptors(String workgroup) {
List<PacketInterceptor> interceptors = getLocalInterceptors(workgroup);
if (interceptors == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(interceptors);
}
/**
* Return the global interceptor at the specified index in the list of currently configured
* interceptors.
*
* @param index the index in the list of interceptors.
* @return the interceptor at the specified index.
*/
public PacketInterceptor getInterceptor(int index) {
if (index < 0 || index > globalInterceptors.size()-1) {
throw new IllegalArgumentException("Index " + index + " is not valid.");
}
return globalInterceptors.get(index);
}
/**
* Return the interceptor at the specified index in the list of currently configured
* interceptors for the specified workgroup.
*
* @param workgroup the bare JID address of the workgroup.
* @param index the index in the list of interceptors.
* @return the interceptor at the specified index.
*/
public PacketInterceptor getInterceptor(String workgroup, int index) {
List<PacketInterceptor> interceptors = getLocalInterceptors(workgroup);
if (interceptors == null) {
return null;
}
if (index < 0 || (index > interceptors.size())) {
throw new IndexOutOfBoundsException("Index " + index + " invalid.");
}
return interceptors.get(index);
}
/**
* Inserts a new interceptor at the specified index in the list of global interceptors.
*
* @param index the index in the list to insert the new global interceptor at.
* @param interceptor the interceptor to add.
*/
public void addInterceptor(int index, PacketInterceptor interceptor) {
if (interceptor == null) {
throw new NullPointerException("Parameter interceptor was null.");
}
if (index < 0 || (index > globalInterceptors.size())) {
throw new IndexOutOfBoundsException("Index " + index + " invalid.");
}
// Remove the interceptor from the list since the position might have changed
if (globalInterceptors.contains(interceptor)) {
int oldIndex = globalInterceptors.indexOf(interceptor);
if (oldIndex < index) {
index -= 1;
}
globalInterceptors.remove(interceptor);
}
globalInterceptors.add(index, interceptor);
saveInterceptors();
}
/**
* Inserts a new interceptor at specified index in the list of currently configured
* interceptors for the specified workgroup.
*
* @param workgroup the bare JID address of the workgroup.
* @param index the index in the list to insert the new interceptor at.
* @param interceptor the interceptor to add.
*/
public void addInterceptor(String workgroup, int index, PacketInterceptor interceptor) {
if (interceptor == null) {
throw new NullPointerException("Parameter interceptor was null.");
}
List<PacketInterceptor> interceptors = getLocalInterceptors(workgroup);
interceptors =
localInterceptors.putIfAbsent(workgroup,
new CopyOnWriteArrayList<PacketInterceptor>());
if (interceptors == null) {
interceptors = localInterceptors.get(workgroup);
}
if (index < 0 || (index > interceptors.size())) {
throw new IndexOutOfBoundsException("Index " + index + " invalid.");
}
// Remove the interceptor from the list since the position might have changed
if (interceptors.contains(interceptor)) {
int oldIndex = interceptors.indexOf(interceptor);
if (oldIndex < index) {
index -= 1;
}
interceptors.remove(interceptor);
}
interceptors.add(index, interceptor);
saveInterceptors();
}
/**
* Removes the global interceptor from the list.
*
* @param interceptor the global interceptor to remove.
* @return true if the item was present in the list
*/
public boolean removeInterceptor(PacketInterceptor interceptor) {
boolean answer = globalInterceptors.remove(interceptor);
saveInterceptors();
return answer;
}
/**
* Removes the interceptor for the specified workgroup from the list.
*
* @param workgroup the bare JID address of the workgroup.
* @param interceptor the interceptor to remove.
* @return true if the item was present in the list
*/
public boolean removeInterceptor(String workgroup, PacketInterceptor interceptor) {
List<PacketInterceptor> interceptors = getLocalInterceptors(workgroup);
if (interceptors == null) {
return false;
}
boolean answer = interceptors.remove(interceptor);
saveInterceptors();
return answer;
}
/**
* Invokes all currently-installed interceptors on the specified packet.
* All global interceptors will be invoked as well as interceptors that
* are related to the specified workgroup.<p>
*
* Interceptors are executed before and after processing an incoming packet
* and sending a packet to a user. This means that interceptors are able to alter or
* reject packets before they are processed further. If possible, interceptors
* should perform their work in a short time so that overall performance is not
* compromised.
*
* @param workgroup the bare JID address of the workgroup.
* @param packet the packet that has been read or is about to be sent.
* @param read true indicates that the packet was read. When false, the packet
* is being sent to a user.
* @param processed true if the packet has already processed (incoming or outgoing).
* If the packet hasn't already been processed, this flag will be false.
* @throws PacketRejectedException if the packet should be prevented from being processed.
*/
public void invokeInterceptors(String workgroup, Packet packet, boolean read,
boolean processed) throws PacketRejectedException
{
// Invoke the global interceptors for this packet
for (PacketInterceptor interceptor : globalInterceptors) {
try {
interceptor.interceptPacket(workgroup, packet, read, processed);
}
catch (PacketRejectedException e) {
if (processed) {
Log.error("Post interceptor cannot reject packet.", e);
}
else {
// Throw this exception since we don't really want to catch it
throw e;
}
}
catch (Exception e) {
Log.error("Error in interceptor", e);
}
}
// Invoke the interceptors that are related to the specified workgroup
List<PacketInterceptor> interceptors = getLocalInterceptors(workgroup);
if (interceptors != null) {
for (PacketInterceptor interceptor : interceptors) {
try {
interceptor.interceptPacket(workgroup, packet, read, processed);
}
catch (PacketRejectedException e) {
if (processed) {
Log.error("Post interceptor cannot reject packet.", e);
}
else {
// Throw this exception since we don't really want to catch it
throw e;
}
}
catch (Exception e) {
Log.error("Error in interceptor", e);
}
}
}
}
public synchronized void saveInterceptors() {
// Delete all global interceptors stored as properties.
JiveGlobals.deleteProperty("interceptor.global." + getPropertySuffix());
// Delete all the workgroup interceptors stored as properties.
for (String jid : localInterceptors.keySet()) {
try {
Workgroup workgroup = WorkgroupManager.getInstance().getWorkgroup(new JID(jid));
Collection<String> propertyNames = workgroup.getProperties().getPropertyNames();
for (String propertyName : propertyNames) {
if (propertyName.startsWith("jive.interceptor." + getPropertySuffix())) {
workgroup.getProperties().deleteProperty(propertyName);
}
}
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
}
// Save the global interceptors as system properties
JiveGlobals.setProperties(getPropertiesMap(globalInterceptors,
"interceptor.global." + getPropertySuffix() + "."));
// Save all the workgroup interceptors as properties of the workgroups.
for (String jid : localInterceptors.keySet()) {
try {
Workgroup workgroup = WorkgroupManager.getInstance().getWorkgroup(new JID(jid));
Map propertyMap = getPropertiesMap(localInterceptors.get(jid),
"jive.interceptor." + getPropertySuffix() + ".");
for (Iterator i=propertyMap.keySet().iterator(); i.hasNext(); ) {
String name = (String)i.next();
String value = (String)propertyMap.get(name);
workgroup.getProperties().setProperty(name, value);
}
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
}
}
private Map<String, String> getPropertiesMap(List<PacketInterceptor> interceptors, String context) {
// Build the properties map that will be saved later
Map<String, String> propertyMap = new HashMap<String, String>();
if (!interceptors.isEmpty()) {
propertyMap.put(context + "interceptorCount", String.valueOf(interceptors.size()));
}
// Now write them out again
for (int i = 0; i < interceptors.size(); i++) {
PacketInterceptor interceptor = interceptors.get(i);
String interceptorContext = context + "interceptor" + i + ".";
// Write out class name
propertyMap.put(interceptorContext + "className", interceptor.getClass().getName());
// Write out all properties
Map<String, String> interceptorProps = BeanUtils.getProperties(interceptor);
for (Map.Entry<String, String> entry : interceptorProps.entrySet()) {
String name = entry.getKey();
String value = entry.getValue();
if (value != null && !"".equals(value)) {
propertyMap.put(interceptorContext + "properties." + name, value);
}
}
}
return propertyMap;
}
/**
* Installs a new class into the list of available interceptors for the system. Exceptions
* are thrown if the class can't be loaded from the classpath, or the class isn't an instance
* of PacketInterceptor.
*
* @param newClass the class to add to the list of available filters in the system.
* @throws IllegalArgumentException if the class is not a filter or could not be instantiated.
*/
public synchronized void addInterceptorClass(Class newClass) throws IllegalArgumentException
{
try {
PacketInterceptor newInterceptor = (PacketInterceptor) newClass.newInstance();
// Make sure the interceptor isn't already in the list.
for (PacketInterceptor interceptor : availableInterceptors) {
if (newInterceptor.getClass().equals(interceptor.getClass())) {
return;
}
}
// Add in the new interceptor
availableInterceptors.add(newInterceptor);
// Write out new class names.
JiveGlobals.deleteProperty("interceptor.interceptorClasses." + getPropertySuffix());
for (int i=0; i<availableInterceptors.size(); i++) {
String cName = availableInterceptors.get(i).getClass().getName();
JiveGlobals.setProperty("interceptor.interceptorClasses."+ getPropertySuffix() + ".interceptor" + i, cName);
}
}
catch (IllegalAccessException e) {
throw new IllegalArgumentException(e.getMessage());
}
catch (InstantiationException e2) {
throw new IllegalArgumentException(e2.getMessage());
}
catch (ClassCastException e5) {
throw new IllegalArgumentException("Class is not a PacketInterceptor");
}
}
/**
* Returns an array of PacketInterceptor objects that are all of the currently available
* incerceptors in the system.
*
* @return an array of all available interceptors in the current context.
*/
public List<PacketInterceptor> getAvailableInterceptors() {
return Collections.unmodifiableList(availableInterceptors);
}
/**
* Returns the suffix to append at the end of global properties to ensure that each subclass
* is not overwritting the properties of another subclass.
*
* @return the suffix to append at the end of global properties to ensure that each subclass
* is not overwritting the properties of another subclass.
*/
protected abstract String getPropertySuffix();
/**
* Returns the collection of built-in packet interceptor classes.
*
* @return the collection of built-in packet interceptor classes.
*/
protected abstract Collection<Class> getBuiltInInterceptorClasses();
private void loadAvailableInterceptors() {
// Load interceptor classes
List<PacketInterceptor> interceptorList = new ArrayList<PacketInterceptor>();
// First, add in built-in list of interceptors.
for (Class newClass : getBuiltInInterceptorClasses()) {
try {
PacketInterceptor interceptor = (PacketInterceptor) newClass.newInstance();
interceptorList.add(interceptor);
}
catch (Exception e) { }
}
// Now get custom interceptors.
List<String> classNames = JiveGlobals.getProperties("interceptor.interceptorClasses." +
getPropertySuffix());
for (int i=0; i<classNames.size(); i++) {
install_interceptor: try {
Class interceptorClass = loadClass(classNames.get(i));
// Make sure that the interceptor isn't already installed.
for (int j=0; j<interceptorList.size(); j++) {
if (interceptorClass.equals(interceptorList.get(j).getClass())) {
break install_interceptor;
}
}
PacketInterceptor interceptor = (PacketInterceptor) interceptorClass.newInstance();
interceptorList.add(interceptor);
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
}
availableInterceptors.addAll(interceptorList);
}
private void loadGlobalInterceptors() {
// See if a record for this context exists yet. If not, create one.
int interceptorCount = JiveGlobals.getIntProperty("interceptor.global." +
getPropertySuffix() +
".interceptorCount", 0);
// Load up all filters and their filter types
List<PacketInterceptor> interceptorList = new ArrayList<PacketInterceptor>(interceptorCount);
for (int i=0; i<interceptorCount; i++) {
try {
String interceptorContext = "interceptor.global." + getPropertySuffix() + ".interceptor" + i + ".";
String className = JiveGlobals.getProperty(interceptorContext + "className");
Class interceptorClass = loadClass(className);
interceptorList.add((PacketInterceptor) interceptorClass.newInstance());
// Load properties.
List<String> props = JiveGlobals.getPropertyNames(interceptorContext + "properties");
Map<String, String> interceptorProps = new HashMap<String, String>();
for (int k = 0; k < props.size(); k++) {
String key = props.get(k);
String value = JiveGlobals.getProperty(props.get(k));
// Get the bean property name, which is everything after the last '.' in the
// xml property name.
interceptorProps.put(key.substring(key.lastIndexOf(".")+1), value);
}
// Set properties on the bean
BeanUtils.setProperties(interceptorList.get(i), interceptorProps);
}
catch (Exception e) {
Log.error("Error loading global interceptor " + i, e);
}
}
globalInterceptors.addAll(interceptorList);
}
private void loadLocalInterceptors(String workgroupJID) throws UserNotFoundException {
Workgroup workgroup = WorkgroupManager.getInstance().getWorkgroup(new JID(workgroupJID));
int interceptorCount = 0;
String iCount = workgroup.getProperties().getProperty("jive.interceptor." +
getPropertySuffix() + ".interceptorCount");
if (iCount != null) {
try {
interceptorCount = Integer.parseInt(iCount);
}
catch (NumberFormatException nfe) { /* ignore */ }
}
// Load up all intercpetors.
List<PacketInterceptor> interceptorList = new ArrayList<PacketInterceptor>(interceptorCount);
for (int i=0; i<interceptorCount; i++) {
try {
String interceptorContext = "jive.interceptor." + getPropertySuffix() + ".interceptor" + i + ".";
String className = workgroup.getProperties().getProperty(interceptorContext +
"className");
Class interceptorClass = loadClass(className);
interceptorList.add((PacketInterceptor) interceptorClass.newInstance());
// Load properties.
Map<String, String> interceptorProps = new HashMap<String, String>();
for (String key : getChildrenPropertyNames(interceptorContext + "properties",
workgroup.getProperties().getPropertyNames()))
{
String value = workgroup.getProperties().getProperty(key);
// Get the bean property name, which is everything after the last '.' in the
// xml property name.
interceptorProps.put(key.substring(key.lastIndexOf(".")+1), value);
}
// Set properties on the bean
BeanUtils.setProperties(interceptorList.get(i), interceptorProps);
}
catch (Exception e) {
Log.error("Error loading local interceptor " + i, e);
}
}
localInterceptors.put(workgroupJID,
new CopyOnWriteArrayList<PacketInterceptor>(interceptorList));
}
/**
* Returns a child property names given a parent and an Iterator of property names.
*
* @param parent parent property name.
* @param properties all property names to search.
* @return an Iterator of child property names.
*/
private static Collection<String> getChildrenPropertyNames(String parent, Collection<String> properties) {
List<String> results = new ArrayList<String>();
for (String name : properties) {
if (name.startsWith(parent) && !name.equals(parent)) {
results.add(name);
}
}
return results;
}
private Class loadClass(String className) throws ClassNotFoundException {
try {
return ClassUtils.forName(className);
}
catch (ClassNotFoundException e) {
return this.getClass().getClassLoader().loadClass(className);
}
}
private List<PacketInterceptor> getLocalInterceptors(String workgroup) {
List<PacketInterceptor> interceptors = localInterceptors.get(workgroup);
if (interceptors == null) {
if (interceptors == null) {
synchronized (workgroup.intern()) {
try {
loadLocalInterceptors(workgroup);
interceptors = localInterceptors.get(workgroup);
}
catch (UserNotFoundException e) {
Log.warn(e.getMessage(), e);
}
}
}
}
return interceptors;
}
}