/* * Tigase Jabber/XMPP Server * Copyright (C) 2004-2012 "Artur Hefczyc" <artur.hefczyc@tigase.org> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License. * * 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. Look for COPYING file in the top folder. * If not, see http://www.gnu.org/licenses/. * * $Rev$ * Last modified by $Author$ * $Date$ */ package tigase.server; //~--- non-JDK imports -------------------------------------------------------- import tigase.annotations.TODO; import tigase.stats.StatisticType; import tigase.stats.StatisticsContainer; import tigase.stats.StatisticsList; import tigase.util.PatternComparator; import tigase.util.PriorityQueueAbstract; import tigase.xml.Element; //~--- JDK imports ------------------------------------------------------------ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; //~--- classes ---------------------------------------------------------------- /** * This is an archetype for all classes processing user-level packets. The * implementation is designed for a heavy packets processing with internal * queues and number of separate threads depending on number of CPUs. Extensions * of the class can process normall user packets and administrator packets via * ad-hoc commands. Good examples of such components are <code>MUC</code>, * <code>PubSub</code>, <code>SessionManager</code>. * <p/> * The class offers scripting API for administrator ad-hoc commands. * <p/> * By default it internally uses priority queues which in some rare cases may * lead to packets reordering. When this happens and it is unacceptable for the * deployment non-priority queues can be used. The queues size is limited and * depends on the available memory size. * <p/> * Packets are processed by <code>processPacket(Packet packet)</code> method * which is concurrently called from multiple threads. * * Created: Tue Nov 22 07:07:11 2005 * * @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a> * @version $Rev$ */ public abstract class AbstractMessageReceiver extends BasicComponent implements StatisticsContainer, MessageReceiver { /** * Configuration property key for setting incoming packets filters on the * component level. */ public static final String INCOMING_FILTERS_PROP_KEY = "incoming-filters"; /** * Configuration property default vakue with a default incoming packet filter * loaded by Tigase server. * <p/> * This is a comma-separated list of classes which should be loaded as packet * filters. The classes must implement <code>PacketFilterIfc</code> interface. */ public static final String INCOMING_FILTERS_PROP_VAL = "tigase.server.filters.PacketCounter"; /** * Configuration property key allowing to overwrite a default (memory size * dependent) size for the component internal queues. By default the queue * size is adjusted to the available memory size to avoid out of memory * errors. */ public static final String MAX_QUEUE_SIZE_PROP_KEY = "max-queue-size"; /** * A default value for max queue size property. The value is calculated at the * server startup time using following formula: <br/> * <code>Runtime.getRuntime().maxMemory() / 400000L</code> You can change the * default queue size by setting a different value for the * <code>MAX_QUEUE_SIZE_PROP_KEY</code> property in the server configuration. */ public static final Integer MAX_QUEUE_SIZE_PROP_VAL = new Long(Runtime.getRuntime() .maxMemory() / 400000L).intValue(); /** * Configuration property key for setting outgoing packets filters on the * component level. This is a comma-separated list of classes which should be * loaded as packet filters. The classes must implement * <code>PacketFilterIfc</code> interface. */ public static final String OUTGOING_FILTERS_PROP_KEY = "outgoing-filters"; /** * Configuration property default vakue with a default outgoing packet filter * loaded by Tigase server. * <p/> * This is a comma-separated list of classes which should be loaded as packet * filters. The classes must implement <code>PacketFilterIfc</code> interface. */ public static final String OUTGOING_FILTERS_PROP_VAL = "tigase.server.filters.PacketCounter"; /** * Constant used in time calculation procedures. Indicates a second that is * 1000 milliseconds. */ protected static final long SECOND = 1000; /** * Constant used in time calculation procedures. Indicates a minute that is 60 * <code>SECOND</code>s. */ protected static final long MINUTE = 60 * SECOND; /** * Constant used in time calculation procedures. Indicates a hour that is 60 * <code>MINUTE</code>s. */ protected static final long HOUR = 60 * MINUTE; // String added intentionally!! // Don't change to AbstractMessageReceiver.class.getName() /** * Variable <code>log</code> is a class logger. */ private static final Logger log = Logger .getLogger("tigase.debug.AbstractMessageReceiver"); // ~--- fields --------------------------------------------------------------- // private static final TigaseTracer tracer = // TigaseTracer.getTracer("abstract"); private int in_queues_size = 1; private long last_hour_packets = 0; private long last_minute_packets = 0; private long last_second_packets = 0; private int out_queues_size = 1; protected int maxInQueueSize = MAX_QUEUE_SIZE_PROP_VAL; protected int maxOutQueueSize = MAX_QUEUE_SIZE_PROP_VAL; private QueueListener out_thread = null; private long packetId = 0; private long packets_per_hour = 0; private long packets_per_minute = 0; private long packets_per_second = 0; private MessageReceiver parent = null; private int pptIdx = 0; // Array cache to speed processing up.... private final Priority[] pr_cache = Priority.values(); private final CopyOnWriteArrayList<PacketFilterIfc> outgoing_filters = new CopyOnWriteArrayList<PacketFilterIfc>(); private final List<PriorityQueueAbstract<Packet>> out_queues = new ArrayList<PriorityQueueAbstract<Packet>>(pr_cache.length); // PriorityQueueAbstract.getPriorityQueue(pr_cache.length, maxQueueSize); private final CopyOnWriteArrayList<PacketFilterIfc> incoming_filters = new CopyOnWriteArrayList<PacketFilterIfc>(); private final List<PriorityQueueAbstract<Packet>> in_queues = new ArrayList<PriorityQueueAbstract<Packet>>(pr_cache.length); private final long[] processPacketTimings = new long[100]; private Timer receiverTasks = null; /** * Variable <code>statAddedMessagesEr</code> keeps counter of unsuccessfuly * added messages due to queue overflow. */ private long statReceivedPacketsEr = 0; /** * Variable <code>statAddedMessagesOk</code> keeps counter of successfuly * added messages to queue. */ private long statReceivedPacketsOk = 0; private long statSentPacketsEr = 0; private long statSentPacketsOk = 0; private ArrayDeque<QueueListener> threadsQueue = null; private final ConcurrentHashMap<String, PacketReceiverTask> waitingTasks = new ConcurrentHashMap<String, PacketReceiverTask>(16, 0.75f, 4); private final Set<Pattern> regexRoutings = new ConcurrentSkipListSet<Pattern>( new PatternComparator()); // ~--- methods -------------------------------------------------------------- /** * This is the main <code>Packet</code> processing method. It is called * concurrently from many threads so implementing it in thread save manner is * essential. The method is called for each packet addressed to the component. * <p/> * Please note, the <code>Packet</code> instance may be processed by different * parts of the server, different components or plugins at the same time. * Therefore this is very important to tread the <code>Packet</code> instance * as unmodifiable object. * <p/> * Processing in this method is asynchronous, therefore there is no result * value. If there are some 'result' packets generated during processing, they * should be passed back using <code>addOutPacket(Packet)</code> method. * * * @param packet * is an instance of the <code>Packet</code> class passed for * processing. */ public abstract void processPacket(Packet packet); /** * By default this method just copies the given packet between queue. This * method operates on packets which have been already processed somehow by the * component so usually the default action is the best one, however some * components in rare cases may choose to process packets differently. In most * cases this method should not be overridden. * * @param packet * is an output packet which normally has to go to other component * for further processing. */ public void processOutPacket(Packet packet) { if (parent != null) { parent.addPacket(packet); } else { // It may happen for MessageRouter and this is intentional addPacketNB(packet); // log.warning("[" + getName() + "] " + "No parent!"); } // end of else } /** * Method adds a <code>Packet</code> object to the internal input queue. * Packets from the input queue are later passed to the * <code>processPacket(Packet)</code> method. This is a blocking method * waiting if necessary for the room if the queue is full. * <p/> * The method returns a <code>boolean</code> value of <code>true</code> if the * packet has been successfully added to the queue and <code>false</code> * otherwise. * <p/> * There can be many queues and many threads processing packets for the * component, however the method makes the best effort to guarantee that * packets are later processed in the correct order. For example that packets * for a single user always end up in the same exact queue. You can tweak the * packets distribution among threads by overwriting * <code>hashCodeForPacket(Packet)</code> method.<br/> * If there is <code>N</code> threads the packets are distributed among thread * using following logic: * * <pre> * int threadNo = Math.abs(hashCodeForPacket(packet) % N); * </pre> * * This is a preferred method to be used by most Tigase components. If the * queues are full the component should stop and wait for more room. The * blocking methods aim to prevent from the system overloading or wasting * resources for generating packets which can't be processed anyway. * * @param packet * is a <code>Packet</code> instance being put to the component * internal input queue. * * @return a <code>boolean</code> value of <code>true</code> if the packet has * been successfully added to the queue and <code>false</code> * otherwise. */ @Override public boolean addPacket(Packet packet) { int queueIdx = Math.abs(hashCodeForPacket(packet) % in_queues_size); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "[{0}] queueIdx={1}, {2}", new Object[] { getName(), queueIdx, packet.toStringSecure() }); } try { in_queues.get(queueIdx).put(packet, packet.getPriority().ordinal()); ++statReceivedPacketsOk; } catch (InterruptedException e) { ++statReceivedPacketsEr; if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Packet dropped for unknown reason: {0}", packet); } return false; } // end of try-catch return true; } /** * This is a variant of <code>addPacket(Packet)</code> method which adds * <code>Packet</code> to in the internal input queue without blocking. * <p/> * The method returns a <code>boolean</code> value of <code>true</code> if the * packet has been successful added to the queue and <code>false</code> * otherwise. * <p/> * Use of the non-blocking methods is not recommended for most of the * components implementations. The only component which is allowed to use them * is the server <code>MessageRouter</code> implementation which can not hang * on any method. This would cause a dead-lock in the application. All other * components must use blocking methods and wait if the system is under so * high load that it's queues are full. * <p/> * See <code>addPacket(Packet)</code> method's documentation for some more * details. * * @param packet * is a <code>Packet</code> instance being put to the component * internal input queue. * * @return a <code>boolean</code> value of <code>true</code> if the packet has * been successfully added to the queue and <code>false</code> * otherwise. * @see AbstractMessageReceiver.addPacket(Packet packet) */ @Override public boolean addPacketNB(Packet packet) { int queueIdx = Math.abs(hashCodeForPacket(packet) % in_queues_size); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "[{0}] queueIdx={1}, {2}", new Object[] { getName(), queueIdx, packet.toStringSecure() }); } boolean result = in_queues.get(queueIdx).offer(packet, packet.getPriority().ordinal()); if (result) { ++statReceivedPacketsOk; } else { // Queue overflow! ++statReceivedPacketsEr; if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Packet dropped due to queue overflow: {0}", packet); } } return result; } /** * This is a convenience method for adding all packets stored in given queue * to the component's internal input queue.<br/> * The method calls <code>addPacket(Packet)</code> in a loop for each packet * in the queue. If the call returns <code>true</code> then the packet is * removed from the given queue, otherwise the methods ends the loop and * returns <code>false</code>. * <p/> * Please note, if the method returns <code>true</code> it means that all the * packets from the queue passed as a parameter have been successfuly run * through the <code>addPacket(Packet)</code> method and the queue passed as a * parameter should be empty. If the method returns false then at least one * packet from the parameter queue wasn't successfully run through the * <code>addPacket(Packet)</code> method. If the method returns * <code>false</code> then the queue passed as a parameter is not empty and it * contains packet which was unseccessfully run through the * <code>addPacket(Packet)</code> method and all the packets which were not * run at all. * * * @param packets * is a <code>Queue</code> of packets for adding to the component * internal input queue. All the packets are later processed by * <code>processPacket(Packet)</code> method in the same exact order * if they are processed by the same thread. See documentation * <code>hashCodeForPacket(Packet)</code> method how to control * assiging packets to particular threads. * * @return a <code>boolean</code> value of <code>true</code> if all packets * has been successfully added to the component's internal input queue * and <code>false</code> otherwise. * @see AbstractMessageReceiver.hashCodeForPacket(Packet packet) */ @Override public boolean addPackets(Queue<Packet> packets) { boolean result = true; Packet p = packets.peek(); while (p != null) { result = addPacket(p); if (result) { packets.poll(); } else { break; } // end of if (result) else p = packets.peek(); } // end of while () return result; } /** * Method adds a new routing address for the component. Routing addresses are * used by the <code>MessageRouter</code> to calculate packet's destination. * If the packet's destination address matches one of the component's routing * addresses the packet is added to the component's internal input queue. * <p/> * By default all components accept packets addressed to the componentId and * to: * * <pre> * component.getName() + '@' + any virtual domain * </pre> * * @TODO: The future implementation most likely accept packets addressed to: * * <pre> * any virtual domain + '/' + component.getName() * </pre> * * instead. * <p/> * The routings are passed as Java regular expression strings are the * extra addresses accepted by the component. In most cases this is * used by the external component protocol implementations which can * dynamically change accepted addresses depending on the connected * external components. * * @param address * is a Java regular expression string for the packet's destination * address accepted by this component. */ public void addRegexRouting(String address) { if (log.isLoggable(Level.FINE)) { log.log(Level.FINE, "{0} - attempt to add regex routing: {1}", new Object[] { getName(), address }); } regexRoutings.add(Pattern.compile(address, Pattern.CASE_INSENSITIVE)); if (log.isLoggable(Level.FINE)) { log.log(Level.FINE, "{0} - success adding regex routing: {1}", new Object[] { getName(), address }); } } /** * Method clears, removes all the component routing addresses. After this * method call the component accepts only packets addressed to default * routings that is component ID or the component name + '@' + virtual domains */ public void clearRegexRoutings() { regexRoutings.clear(); } /** * Utility method executed precisely every hour. A component can overwrite the * method to put own code to be executed at the regular intervals of time. * <p/> * Note, no extensive calculations should happen in this method nor long * lasting operations. It is essential that the method processing does not * exceed 1 hour. The overriding method must call the the super method first * and only then run own code. */ public synchronized void everyHour() { packets_per_hour = statReceivedPacketsOk - last_hour_packets; last_hour_packets = statReceivedPacketsOk; } /** * Utility method executed precisely every minute. A component can overwrite * the method to put own code to be executed at the regular intervals of time. * <p/> * Note, no extensive calculations should happen in this method nor long * lasting operations. It is essential that the method processing does not * exceed 1 minute. The overriding method must call the the super method first * and only then run own code. */ public synchronized void everyMinute() { packets_per_minute = statReceivedPacketsOk - last_minute_packets; last_minute_packets = statReceivedPacketsOk; receiverTasks.purge(); } /** * Utility method executed precisely every second. A component can overwrite * the method to put own code to be executed at the regular intervals of time. * <p/> * Note, no extensive calculations should happen in this method nor long * lasting operations. It is essential that the method processing does not * exceed 1 second. The overriding method must call the the super method first * and only then run own code. */ public synchronized void everySecond() { packets_per_second = statReceivedPacketsOk - last_second_packets; last_second_packets = statReceivedPacketsOk; } // ~--- get methods ---------------------------------------------------------- /** * Returns default configuration settings for the component as a * <code>Map</code> with keys as configuration property IDs and values as the * configuration property values. All the default parameters returned from * this method are later passed to the <code>setProperties(...)</code> method. * Some of them may have changed value if they have been overwritten in the * server configuration. The configuration property value can be of any of the * basic types: <code>int</code>, <code>long</code>, <code>boolean</code>, * <code>String</code>. * * @param params * is a <code>Map</code> with some initial properties set for the * starting up server. These parameters can be used as a hints to * generate component's default configuration. * * @return a <code>Map</code> with the component default configuration. */ @Override public Map<String, Object> getDefaults(Map<String, Object> params) { Map<String, Object> defs = super.getDefaults(params); String queueSize = (String) params.get(GEN_MAX_QUEUE_SIZE); int queueSizeInt = MAX_QUEUE_SIZE_PROP_VAL; if (queueSize != null) { try { queueSizeInt = Integer.parseInt(queueSize); } catch (NumberFormatException e) { queueSizeInt = MAX_QUEUE_SIZE_PROP_VAL; } } defs.put(MAX_QUEUE_SIZE_PROP_KEY, getMaxQueueSize(queueSizeInt)); defs.put(INCOMING_FILTERS_PROP_KEY, INCOMING_FILTERS_PROP_VAL); defs.put(OUTGOING_FILTERS_PROP_KEY, OUTGOING_FILTERS_PROP_VAL); return defs; } /** * Method returns a <code>Set</code> with all component's routings as a * compiled regular expression patterns. The <code>Set</code> can be empty but * it can not be null. * * @return a <code>Set</code> with all component's routings as a compiled * regular expression patterns. */ public Set<Pattern> getRegexRoutings() { return regexRoutings; } /** * Method returns component statistics. Please note, the method can be called * every second by the server monitoring system therefore no extensive or * lengthy calculations are allowed. If there are some statistics requiring * lengthy operations like database access they must have * <code>Level.FINEST</code> assigned and must be put inside the level guard * to prevent generating them by the system monitor. The system monitor does * not collect <code>FINEST</code> statistics. * <p/> * Level guard code looks like the example below: * * <pre> * if (list.checkLevel(Level.FINEST)) { * // Some CPU intensive calculations or lengthy operations * list.add(getName(), "Statistic description", stat_value, Level.FINEST); * } * * <pre> * This way you make sure your extensive operation is not executed every second by the * monitoring system and does not affect the server performance. * * @param list is a <code>StatistcsList</code> * where all statistics are stored. */ @Override public void getStatistics(StatisticsList list) { list.add(getName(), "Last second packets", packets_per_second, Level.FINE); list.add(getName(), "Last minute packets", packets_per_minute, Level.FINE); list.add(getName(), "Last hour packets", packets_per_hour, Level.FINE); list.add(getName(), "Processing threads", processingInThreads(), Level.FINER); list.add(getName(), StatisticType.MSG_RECEIVED_OK.getDescription(), statReceivedPacketsOk, Level.FINE); list.add(getName(), StatisticType.MSG_SENT_OK.getDescription(), statSentPacketsOk, Level.FINE); if (list.checkLevel(Level.FINEST)) { int[] in_priority_sizes = in_queues.get(0).size(); for (int i = 1; i < in_queues.size(); i++) { int[] tmp_pr_sizes = in_queues.get(i).size(); for (int j = 0; j < tmp_pr_sizes.length; j++) { in_priority_sizes[j] += tmp_pr_sizes[j]; } } int[] out_priority_sizes = out_queues.get(0).size(); for (int i = 1; i < out_queues.size(); i++) { int[] tmp_pr_sizes = out_queues.get(i).size(); for (int j = 0; j < tmp_pr_sizes.length; j++) { out_priority_sizes[j] += tmp_pr_sizes[j]; } } for (int i = 0; i < in_priority_sizes.length; i++) { Priority queue = Priority.values()[i]; list.add(getName(), "In queue: " + queue.name(), in_priority_sizes[queue.ordinal()], Level.FINEST); } for (int i = 0; i < out_priority_sizes.length; i++) { Priority queue = Priority.values()[i]; list.add(getName(), "Out queue: " + queue.name(), out_priority_sizes[queue.ordinal()], Level.FINEST); } } int in_queue_size = 0; for (PriorityQueueAbstract<Packet> total_size : in_queues) { in_queue_size += total_size.totalSize(); } int out_queue_size = 0; for (PriorityQueueAbstract<Packet> total_size : out_queues) { out_queue_size += total_size.totalSize(); } list.add(getName(), "Total In queues wait", in_queue_size, Level.INFO); list.add(getName(), "Total Out queues wait", out_queue_size, Level.INFO); list.add(getName(), "Total queues wait", (in_queue_size + out_queue_size), Level.INFO); list.add(getName(), StatisticType.MAX_QUEUE_SIZE.getDescription(), (maxInQueueSize * processingInThreads()), Level.FINEST); list.add(getName(), StatisticType.IN_QUEUE_OVERFLOW.getDescription(), statReceivedPacketsEr, Level.INFO); list.add(getName(), StatisticType.OUT_QUEUE_OVERFLOW.getDescription(), statSentPacketsEr, Level.INFO); list.add(getName(), "Total queues overflow", (statReceivedPacketsEr + statSentPacketsEr), Level.INFO); long res = 0; for (long ppt : processPacketTimings) { res += ppt; } long prcessingTime = res / processPacketTimings.length; list.add(getName(), "Average processing time on last " + processPacketTimings.length + " runs [ms]", prcessingTime, Level.FINE); for (PacketFilterIfc packetFilter : incoming_filters) { packetFilter.getStatistics(list); } for (PacketFilterIfc packetFilter : outgoing_filters) { packetFilter.getStatistics(list); } if (list.checkLevel(Level.FINEST)) { for (QueueListener thread : threadsQueue) { list.add(getName(), "Processed packets " + thread.getName(), thread.packetCounter, Level.FINEST); } } } /** * This method decides how incoming packets are distributed among processing * threads. Different components needs different distribution to efficient use * all threads and avoid packets re-ordering. * <p/> * If there are N processing threads, packets are distributed among threads * using following code: * * <pre> * int threadNo = Math.abs(hashCodeForPacket(packet) % N); * </pre> * * For a PubSub component, for example, a better packets distribution would be * based on the PubSub channel name, for SM a better distribution is based on * the destination address, etc.... * * @param packet * is a <code>Packet</code> which needs to be processed by some * thread. * @return a hash code generated for the input thread. */ public int hashCodeForPacket(Packet packet) { if ((packet.getPacketFrom() != null) && !getComponentId().equals(packet.getPacketFrom())) { // This comes from connection manager so the best way is to get hashcode // by the connectionId, which is in the getFrom() return packet.getPacketFrom().hashCode(); } if (packet.getPacketTo() != null && !getComponentId().equals(packet.getPacketTo())) { return packet.getPacketTo().hashCode(); } // If not, then a better way is to get hashCode from the elemTo address // as this would be by the destination address user name: if (packet.getStanzaTo() != null) { return packet.getStanzaTo().getBareJID().hashCode(); } return 1; } /** * Method description * * * @param address * * @return */ @Override public boolean isInRegexRoutings(String address) { // log.finest(getName() + " looking for regex routings: " + address); for (Pattern pat : regexRoutings) { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "{0} matching: {1} against {2}", new Object[] { getName(), address, pat.toString() }); } if (pat.matcher(address).matches()) { return true; } // log.finest(getName() + " matching failed against pattern: " + // pat.toString()); } return false; } /** * Method description * * * @param prefix * * @return */ public String newPacketId(String prefix) { StringBuilder sb = new StringBuilder(32); if (prefix != null) { sb.append(prefix).append("-"); } sb.append(getName()).append(++packetId); return sb.toString(); } /** * Method description * * * @param packet * @param results */ @Override public final void processPacket(final Packet packet, final Queue<Packet> results) { addPacketNB(packet); } /** * There is now a separate setting for incoming packets and outgoing packets * processing. Some components are uni-directional, hence they don't even use * any threads in one direction. This way you can save resources by reducing * unneeded threads. Use <code>processingOutThreads()</code> and * <code>processingInThreads()</code> instead. * * @return */ @Deprecated public int processingThreads() { return 1; } public int processingOutThreads() { return 1; } public int processingInThreads() { return 1; } /** * Method description * */ @Override public void release() { stop(); } /** * Method description * * * @param address * * @return */ public boolean removeRegexRouting(String address) { return regexRoutings.remove(Pattern.compile(address, Pattern.CASE_INSENSITIVE)); } // ~--- set methods ---------------------------------------------------------- /** * Method description * * * @param maxQueueSize * */ public void setMaxQueueSize(int maxQueueSize) { if ((this.maxInQueueSize != maxQueueSize) || (in_queues.size() == 0)) { // out_queue = PriorityQueueAbstract.getPriorityQueue(pr_cache.length, // maxQueueSize); // Processing threads number is split to incoming and outgoing queues... // So real processing threads number of in_queues is processingThreads()/2 this.maxInQueueSize = (maxQueueSize / processingInThreads()) * 2; this.maxOutQueueSize = (maxQueueSize / processingOutThreads()) * 2; if (in_queues.size() == 0) { for (int i = 0; i < in_queues_size; i++) { PriorityQueueAbstract<Packet> queue = PriorityQueueAbstract.getPriorityQueue(pr_cache.length, maxQueueSize); in_queues.add(queue); } } else { for (int i = 0; i < in_queues.size(); i++) { in_queues.get(i).setMaxSize(maxQueueSize); } } if (out_queues.size() == 0) { for (int i = 0; i < out_queues_size; i++) { PriorityQueueAbstract<Packet> queue = PriorityQueueAbstract.getPriorityQueue(pr_cache.length, maxQueueSize); out_queues.add(queue); } } else { for (int i = 0; i < out_queues.size(); i++) { out_queues.get(i).setMaxSize(maxQueueSize); } } // out_queue.setMaxSize(maxQueueSize); } // end of if (this.maxQueueSize != maxQueueSize) } /** * Method description * * * @param name */ @Override public void setName(String name) { super.setName(name); in_queues_size = processingInThreads(); out_queues_size = processingOutThreads(); setMaxQueueSize(maxInQueueSize); } /** * Method description * * * @param parent */ @Override public void setParent(MessageReceiver parent) { this.parent = parent; } /** * Sets all configuration properties for object. * * @param props */ @Override @TODO(note = "Replace fixed filers loading with configurable options for that") public void setProperties(Map<String, Object> props) { super.setProperties(props); if (props.get(MAX_QUEUE_SIZE_PROP_KEY) != null) { int queueSize = (Integer) props.get(MAX_QUEUE_SIZE_PROP_KEY); setMaxQueueSize(queueSize); } String filters = (String) props.get(INCOMING_FILTERS_PROP_KEY); if ((filters != null) && !filters.trim().isEmpty()) { incoming_filters.clear(); String[] incoming = filters.trim().split(","); for (String inc : incoming) { try { PacketFilterIfc filter = (PacketFilterIfc) Class.forName(inc).newInstance(); filter.init(getName(), QueueType.IN_QUEUE); incoming_filters.add(filter); log.log(Level.CONFIG, "{0} loaded incoming filter: {1}", new Object[] { getName(), inc }); } catch (Exception e) { log.log(Level.WARNING, "Problem loading filter: " + inc + " in component: " + getName(), e); } } } filters = (String) props.get(OUTGOING_FILTERS_PROP_KEY); if ((filters != null) && !filters.trim().isEmpty()) { outgoing_filters.clear(); String[] outgoing = filters.trim().split(","); for (String out : outgoing) { try { PacketFilterIfc filter = (PacketFilterIfc) Class.forName(out).newInstance(); filter.init(getName(), QueueType.OUT_QUEUE); outgoing_filters.add(filter); log.log(Level.CONFIG, "{0} loaded outgoing filter: {1}", new Object[] { getName(), out }); } catch (Exception e) { log.log(Level.WARNING, "Problem loading filter: " + out + " in component: " + getName(), e); } } } } /** * Method description * */ @Override public void start() { if (log.isLoggable(Level.FINER)) { log.log(Level.INFO, "{0}: starting queue management threads ...", getName()); } startThreads(); } /** * Method description * */ public void stop() { if (log.isLoggable(Level.FINER)) { log.log(Level.INFO, "{0}: stopping queue management threads ...", getName()); } stopThreads(); } protected boolean addOutPacket(Packet packet) { int queueIdx = Math.abs(hashCodeForPacket(packet) % out_queues_size); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "[{0}] queueIdx={1}, {2}", new Object[] { getName(), queueIdx, packet.toStringSecure() }); } try { out_queues.get(queueIdx).put(packet, packet.getPriority().ordinal()); ++statSentPacketsOk; } catch (InterruptedException e) { ++statSentPacketsEr; if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Packet dropped for unknown reason: {0}", packet); } return false; } // end of try-catch return true; } /** * Non blocking version of <code>addOutPacket</code>. * * @param packet * a <code>Packet</code> value * @return a <code>boolean</code> value */ protected boolean addOutPacketNB(Packet packet) { int queueIdx = Math.abs(hashCodeForPacket(packet) % out_queues_size); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "[{0}] queueIdx={1}, {2}", new Object[] { getName(), queueIdx, packet.toStringSecure() }); } boolean result = false; result = out_queues.get(queueIdx).offer(packet, packet.getPriority().ordinal()); if (result) { ++statSentPacketsOk; } else { // Queue overflow! ++statSentPacketsEr; if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Packet dropped due to queue overflow: {0}", packet); } } return result; } protected boolean addOutPacketWithTimeout(Packet packet, ReceiverTimeoutHandler handler, long delay, TimeUnit unit) { // It is automatically added to collections and the Timer new PacketReceiverTask(handler, delay, unit, packet); return addOutPacket(packet); } protected boolean addOutPackets(Queue<Packet> packets) { Packet p = null; boolean result = true; while ((p = packets.peek()) != null) { result = addOutPacket(p); if (result) { packets.poll(); } else { return false; } // end of if (result) else } // end of while () return true; } protected void addTimerTask(TimerTask task, long delay, TimeUnit unit) { receiverTasks.schedule(task, unit.toMillis(delay)); } protected void addTimerTask(TimerTask task, long delay) { receiverTasks.schedule(task, delay); } protected Integer getMaxQueueSize(int def) { return def; } private Packet filterPacket(Packet packet, CopyOnWriteArrayList<PacketFilterIfc> filters) { Packet result = packet; for (PacketFilterIfc packetFilterIfc : filters) { result = packetFilterIfc.filter(result); if (result == null) { break; } } return result; } private void startThreads() { if (threadsQueue == null) { threadsQueue = new ArrayDeque<QueueListener>(8); for (int i = 0; i < in_queues_size; i++) { QueueListener in_thread = new QueueListener(in_queues.get(i), QueueType.IN_QUEUE); in_thread.setName("in_" + i + "-" + getName()); in_thread.start(); threadsQueue.add(in_thread); } for (int i = 0; i < out_queues_size; i++) { QueueListener out_thread = new QueueListener(out_queues.get(i), QueueType.OUT_QUEUE); out_thread.setName("out_" + i + "-" + getName()); out_thread.start(); threadsQueue.add(out_thread); } } // end of if (thread == null || ! thread.isAlive()) // if ((out_thread == null) ||!out_thread.isAlive()) { // out_thread = new QueueListener(out_queue, QueueType.OUT_QUEUE); // out_thread.setName("out_" + getName()); // out_thread.start(); // } // end of if (thread == null || ! thread.isAlive()) receiverTasks = new Timer(getName() + " tasks", true); receiverTasks.scheduleAtFixedRate(new TimerTask() { @Override public void run() { everySecond(); } }, SECOND, SECOND); receiverTasks.scheduleAtFixedRate(new TimerTask() { @Override public void run() { everyMinute(); } }, MINUTE, MINUTE); receiverTasks.scheduleAtFixedRate(new TimerTask() { @Override public void run() { everyHour(); } }, HOUR, HOUR); } private void stopThreads() { // stopped = true; try { if (threadsQueue != null) { for (QueueListener in_thread : threadsQueue) { in_thread.threadStopped = true; in_thread.interrupt(); while (in_thread.isAlive()) { Thread.sleep(100); } } } if (out_thread != null) { out_thread.threadStopped = true; out_thread.interrupt(); while (out_thread.isAlive()) { Thread.sleep(100); } } } catch (InterruptedException e) { } threadsQueue = null; out_thread = null; if (receiverTasks != null) { receiverTasks.cancel(); receiverTasks = null; } } private class PacketReceiverTask extends TimerTask { private ReceiverTimeoutHandler handler = null; private String id = null; private Packet packet = null; private PacketReceiverTask(ReceiverTimeoutHandler handler, long delay, TimeUnit unit, Packet packet) { super(); this.handler = handler; this.packet = packet; id = packet.getFrom().toString() + packet.getStanzaId(); waitingTasks.put(id, this); receiverTasks.schedule(this, unit.toMillis(delay)); // log.finest("[" + getName() + "] " + "Added timeout task for: " + id); } /** * Method description * * * @param response */ public void handleResponse(Packet response) { // waitingTasks.remove(packet.getFrom() + packet.getId()); this.cancel(); // log.finest("[" + getName() + "] " + "Response received for id: " + // id); handler.responseReceived(packet, response); } /** * Method description * */ public void handleTimeout() { // log.finest("[" + getName() + "] " + "Fired timeout for id: " + id); waitingTasks.remove(id); handler.timeOutExpired(packet); } /** * Method description * */ @Override public void run() { handleTimeout(); } } private class QueueListener extends Thread { private String compName = null; private long packetCounter = 0; private QueueType type = null; private boolean threadStopped = false; private PriorityQueueAbstract<Packet> queue; // ~--- constructors ------------------------------------------------------- private QueueListener(PriorityQueueAbstract<Packet> q, QueueType type) { this.queue = q; this.type = type; compName = AbstractMessageReceiver.this.getName(); } // ~--- methods ------------------------------------------------------------ /** * Method description * */ @Override public void run() { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "{0} starting queue processing.", getName()); } Packet packet = null; Queue<Packet> results = new ArrayDeque<Packet>(2); while (!threadStopped) { try { // Now process next waiting packet // log.finest("[" + getName() + "] before take... " + type); // packet = queue.take(getName() + ":" + type); packet = queue.take(); ++packetCounter; // if (log.isLoggable(Level.INFO)) { // log.info("[" + getName() + "] packet from " + type + " queue: " + // packet); // } switch (type) { case IN_QUEUE: long startPPT = System.currentTimeMillis(); // tracer.trace(null, packet.getElemTo(), packet.getElemFrom(), // packet.getFrom(), getName(), type.name(), null, packet); PacketReceiverTask task = null; if (packet.getTo() != null) { String id = packet.getTo().toString() + packet.getStanzaId(); task = waitingTasks.remove(id); } if (task != null) { task.handleResponse(packet); } else { // log.finest("[" + getName() + "] " + // "No task found for id: " + id); // Maybe this is a command for local processing... boolean processed = false; if (packet.isCommand() && (packet.getStanzaTo() != null) && compName.equals(packet.getStanzaTo().getLocalpart()) && isLocalDomain(packet.getStanzaTo().getDomain())) { processed = processScriptCommand(packet, results); if (processed) { Packet result = null; while ((result = results.poll()) != null) { addOutPacket(result); } } } if (!processed && ((packet = filterPacket(packet, incoming_filters)) != null)) { processPacket(packet); } // It is all concurrent so we have to use a local index variable int idx = pptIdx; pptIdx = (pptIdx + 1) % processPacketTimings.length; long timing = System.currentTimeMillis() - startPPT; processPacketTimings[idx] = timing; } break; case OUT_QUEUE: // tracer.trace(null, packet.getElemTo(), packet.getElemFrom(), // packet.getTo(), getName(), type.name(), null, packet); if ((packet = filterPacket(packet, outgoing_filters)) != null) { processOutPacket(packet); } break; default: log.log(Level.SEVERE, "Unknown queue element type: {0}", type); break; } // end of switch (qel.type) } catch (InterruptedException e) { // log.log(Level.SEVERE, "Exception during packet processing: ", e); // stopped = true; } catch (Exception e) { log.log(Level.SEVERE, "[" + getName() + "] Exception during packet processing: " + packet, e); } // end of try-catch } // end of while (! threadStopped) } } } // AbstractMessageReceiver // ~ Formatted in Sun Code Convention // ~ Formatted by Jindent --- http://www.jindent.com