/* * 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; } }