diff options
author | Rajith Muditha Attapattu <rajith@apache.org> | 2011-05-27 15:44:23 +0000 |
---|---|---|
committer | Rajith Muditha Attapattu <rajith@apache.org> | 2011-05-27 15:44:23 +0000 |
commit | 66765100f4257159622cefe57bed50125a5ad017 (patch) | |
tree | a88ee23bb194eb91f0ebb2d9b23ff423e3ea8e37 /qpid/java/broker/src/main/java/org/apache | |
parent | 1aeaa7b16e5ce54f10c901d75c4d40f9f88b9db6 (diff) | |
parent | 88b98b2f4152ef59a671fad55a0d08338b6b78ca (diff) | |
download | qpid-python-66765100f4257159622cefe57bed50125a5ad017.tar.gz |
Creating a branch for experimenting with some ideas for JMS client.rajith_jms_client
git-svn-id: https://svn.apache.org/repos/asf/qpid/branches/rajith_jms_client@1128369 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'qpid/java/broker/src/main/java/org/apache')
432 files changed, 68474 insertions, 0 deletions
diff --git a/qpid/java/broker/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java b/qpid/java/broker/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java new file mode 100644 index 0000000000..4426a7aeec --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/log4j/QpidCompositeRollingAppender.java @@ -0,0 +1,1125 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.log4j; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.Writer; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.zip.GZIPOutputStream; + +import org.apache.log4j.helpers.CountingQuietWriter; +import org.apache.log4j.helpers.LogLog; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.spi.LoggingEvent; + +/** + * <p>CompositeRollingAppender combines RollingFileAppender and DailyRollingFileAppender<br> It can function as either + * or do both at the same time (making size based rolling files like RollingFileAppender until a data/time boundary is + * crossed at which time it rolls all of those files as per the DailyRollingFileAppender) based on the setting for + * <code>rollingStyle</code>.<br> <br> To use CompositeRollingAppender to roll log files as they reach a certain size + * (like RollingFileAppender), set rollingStyle=1 (@see config.size)<br> To use CompositeRollingAppender to roll log + * files at certain time intervals (daily for example), set rollingStyle=2 and a datePattern (@see config.time)<br> To + * have CompositeRollingAppender roll log files at a certain size AND rename those according to time intervals, set + * rollingStyle=3 (@see config.composite)<br> + * + * <p>A of few additional optional features have been added:<br> -- Attach date pattern for current log file (@see + * staticLogFileName)<br> -- Backup number increments for newer files (@see countDirection)<br> -- Infinite number of + * backups by file size (@see maxSizeRollBackups)<br> <br> <p>A few notes and warnings: For large or infinite number of + * backups countDirection > 0 is highly recommended, with staticLogFileName = false if time based rolling is also used + * -- this will reduce the number of file renamings to few or none. Changing staticLogFileName or countDirection + * without clearing the directory could have nasty side effects. If Date/Time based rolling is enabled, + * CompositeRollingAppender will attempt to roll existing files in the directory without a date/time tag based on the + * last modified date of the base log files last modification.<br> <br> <p>A maximum number of backups based on + * date/time boundries would be nice but is not yet implemented.<br> + * + * @author Kevin Steppe + * @author Heinz Richter + * @author Eirik Lygre + * @author Ceki Gülcü + * @author Martin Ritchie + */ +public class QpidCompositeRollingAppender extends FileAppender +{ + // The code assumes that the following 'time' constants are in a increasing + // sequence. + static final int TOP_OF_TROUBLE = -1; + static final int TOP_OF_MINUTE = 0; + static final int TOP_OF_HOUR = 1; + static final int HALF_DAY = 2; + static final int TOP_OF_DAY = 3; + static final int TOP_OF_WEEK = 4; + static final int TOP_OF_MONTH = 5; + + /** Style of rolling to use */ + static final int BY_SIZE = 1; + static final int BY_DATE = 2; + static final int BY_COMPOSITE = 3; + + // Not currently used + static final String S_BY_SIZE = "Size"; + static final String S_BY_DATE = "Date"; + static final String S_BY_COMPOSITE = "Composite"; + + /** The date pattern. By default, the pattern is set to "'.'yyyy-MM-dd" meaning daily rollover. */ + private String datePattern = "'.'yyyy-MM-dd"; + + /** + * The actual formatted filename that is currently being written to or will be the file transferred to on roll over + * (based on staticLogFileName). + */ + private String scheduledFilename = null; + + /** The timestamp when we shall next recompute the filename. */ + private long nextCheck = System.currentTimeMillis() - 1; + + /** Holds date of last roll over */ + Date now = new Date(); + + SimpleDateFormat sdf; + + /** Helper class to determine next rollover time */ + RollingCalendar rc = new RollingCalendar(); + + /** The default maximum file size is 10MB. */ + protected long maxFileSize = 10 * 1024 * 1024; + + /** There is zero backup files by default. */ + protected int maxSizeRollBackups = 0; + /** How many sized based backups have been made so far */ + protected int curSizeRollBackups = 0; + + /** not yet implemented */ + protected int maxTimeRollBackups = -1; + protected int curTimeRollBackups = 0; + + /** + * By default newer files have lower numbers. (countDirection < 0) ie. log.1 is most recent, log.5 is the 5th + * backup, etc... countDirection > 0 does the opposite ie. log.1 is the first backup made, log.5 is the 5th backup + * made, etc. For infinite backups use countDirection > 0 to reduce rollOver costs. + */ + protected int countDirection = -1; + + /** Style of rolling to Use. BY_SIZE (1), BY_DATE(2), BY COMPOSITE(3) */ + protected int rollingStyle = BY_COMPOSITE; + protected boolean rollDate = true; + protected boolean rollSize = true; + + /** + * By default file.log is always the current file. Optionally file.log.yyyy-mm-dd for current formated datePattern + * can by the currently logging file (or file.log.curSizeRollBackup or even file.log.yyyy-mm-dd.curSizeRollBackup) + * This will make time based roll overs with a large number of backups much faster -- it won't have to rename all + * the backups! + */ + protected boolean staticLogFileName = true; + + /** FileName provided in configuration. Used for rolling properly */ + protected String baseFileName; + + /** Do we want to .gz our backup files. */ + protected boolean compress = false; + + /** Do we want to use a second thread when compressing our backup files. */ + protected boolean compressAsync = false; + + /** Do we want to start numbering files at zero. */ + protected boolean zeroBased = false; + + /** Path provided in configuration. Used for moving backup files to */ + protected String backupFilesToPath = null; + private final ConcurrentLinkedQueue<CompressJob> _compress = new ConcurrentLinkedQueue<CompressJob>(); + private AtomicBoolean _compressing = new AtomicBoolean(false); + private static final String COMPRESS_EXTENSION = ".gz"; + + /** The default constructor does nothing. */ + public QpidCompositeRollingAppender() + { } + + /** + * Instantiate a <code>CompositeRollingAppender</code> and open the file designated by <code>filename</code>. The + * opened filename will become the ouput destination for this appender. + */ + public QpidCompositeRollingAppender(Layout layout, String filename, String datePattern) throws IOException + { + this(layout, filename, datePattern, true); + } + + /** + * Instantiate a CompositeRollingAppender and open the file designated by <code>filename</code>. The opened filename + * will become the ouput destination for this appender. + * + * <p>If the <code>append</code> parameter is true, the file will be appended to. Otherwise, the file desginated by + * <code>filename</code> will be truncated before being opened. + */ + public QpidCompositeRollingAppender(Layout layout, String filename, boolean append) throws IOException + { + super(layout, filename, append); + } + + /** + * Instantiate a CompositeRollingAppender and open the file designated by <code>filename</code>. The opened filename + * will become the ouput destination for this appender. + */ + public QpidCompositeRollingAppender(Layout layout, String filename, String datePattern, boolean append) + throws IOException + { + super(layout, filename, append); + this.datePattern = datePattern; + activateOptions(); + } + + /** + * Instantiate a CompositeRollingAppender and open the file designated by <code>filename</code>. The opened filename + * will become the output destination for this appender. + * + * <p>The file will be appended to. DatePattern is default. + */ + public QpidCompositeRollingAppender(Layout layout, String filename) throws IOException + { + super(layout, filename); + } + + /** + * The <b>DatePattern</b> takes a string in the same format as expected by {@link java.text.SimpleDateFormat}. This + * options determines the rollover schedule. + */ + public void setDatePattern(String pattern) + { + datePattern = pattern; + } + + /** Returns the value of the <b>DatePattern</b> option. */ + public String getDatePattern() + { + return datePattern; + } + + /** Returns the value of the <b>maxSizeRollBackups</b> option. */ + public int getMaxSizeRollBackups() + { + return maxSizeRollBackups; + } + + /** + * Get the maximum size that the output file is allowed to reach before being rolled over to backup files. + * + * @since 1.1 + */ + public long getMaximumFileSize() + { + return maxFileSize; + } + + /** + * <p>Set the maximum number of backup files to keep around based on file size. + * + * <p>The <b>MaxSizeRollBackups</b> option determines how many backup files are kept before the oldest is erased. + * This option takes an integer value. If set to zero, then there will be no backup files and the log file will be + * truncated when it reaches <code>MaxFileSize</code>. If a negative number is supplied then no deletions will be + * made. Note that this could result in very slow performance as a large number of files are rolled over unless + * {@link #setCountDirection} up is used. + * + * <p>The maximum applys to -each- time based group of files and -not- the total. Using a daily roll the maximum + * total files would be (#days run) * (maxSizeRollBackups) + */ + public void setMaxSizeRollBackups(int maxBackups) + { + maxSizeRollBackups = maxBackups; + } + + /** + * Set the maximum size that the output file is allowed to reach before being rolled over to backup files. + * + * <p>This method is equivalent to {@link #setMaxFileSize} except that it is required for differentiating the setter + * taking a <code>long</code> argument from the setter taking a <code>String</code> argument by the JavaBeans {@link + * java.beans.Introspector Introspector}. + * + * @see #setMaxFileSize(String) + */ + public void setMaxFileSize(long maxFileSize) + { + this.maxFileSize = maxFileSize; + } + + /** + * Set the maximum size that the output file is allowed to reach before being rolled over to backup files. + * + * <p>This method is equivalent to {@link #setMaxFileSize} except that it is required for differentiating the setter + * taking a <code>long</code> argument from the setter taking a <code>String</code> argument by the JavaBeans {@link + * java.beans.Introspector Introspector}. + * + * @see #setMaxFileSize(String) + */ + public void setMaximumFileSize(long maxFileSize) + { + this.maxFileSize = maxFileSize; + } + + /** + * Set the maximum size that the output file is allowed to reach before being rolled over to backup files. + * + * <p>In configuration files, the <b>MaxFileSize</b> option takes an long integer in the range 0 - 2^63. You can + * specify the value with the suffixes "KB", "MB" or "GB" so that the integer is interpreted being expressed + * respectively in kilobytes, megabytes or gigabytes. For example, the value "10KB" will be interpreted as 10240. + */ + public void setMaxFileSize(String value) + { + maxFileSize = OptionConverter.toFileSize(value, maxFileSize + 1); + } + + protected void setQWForFiles(Writer writer) + { + qw = new CountingQuietWriter(writer, errorHandler); + } + + // Taken verbatum from DailyRollingFileAppender + int computeCheckPeriod() + { + RollingCalendar c = new RollingCalendar(); + // set sate to 1970-01-01 00:00:00 GMT + Date epoch = new Date(0); + if (datePattern != null) + { + for (int i = TOP_OF_MINUTE; i <= TOP_OF_MONTH; i++) + { + String r0 = sdf.format(epoch); + c.setType(i); + Date next = new Date(c.getNextCheckMillis(epoch)); + String r1 = sdf.format(next); + // LogLog.debug("Type = "+i+", r0 = "+r0+", r1 = "+r1); + if ((r0 != null) && (r1 != null) && !r0.equals(r1)) + { + return i; + } + } + } + + return TOP_OF_TROUBLE; // Deliberately head for trouble... + } + + // Now for the new stuff + /** + * Handles append time behavior for CompositeRollingAppender. This checks if a roll over either by date (checked + * first) or time (checked second) is need and then appends to the file last. + */ + protected void subAppend(LoggingEvent event) + { + + if (rollDate) + { + long n = System.currentTimeMillis(); + if (n >= nextCheck) + { + now.setTime(n); + nextCheck = rc.getNextCheckMillis(now); + + rollOverTime(); + } + } + + if (rollSize) + { + if ((fileName != null) && (((CountingQuietWriter) qw).getCount() >= maxFileSize)) + { + rollOverSize(); + } + } + + super.subAppend(event); + } + + public void setFile(String file) + { + baseFileName = file.trim(); + fileName = file.trim(); + } + + /** + * Creates and opens the file for logging. If <code>staticLogFileName</code> is false then the fully qualified name + * is determined and used. + */ + public synchronized void setFile(String fileName, boolean append) throws IOException + { + if (!staticLogFileName) + { + scheduledFilename = fileName = fileName.trim() + sdf.format(now); + } + + super.setFile(fileName, append, bufferedIO, bufferSize); + + if (append) + { + File f = new File(fileName); + ((CountingQuietWriter) qw).setCount(f.length()); + } + } + + public int getCountDirection() + { + return countDirection; + } + + public void setCountDirection(int direction) + { + countDirection = direction; + } + + public int getRollingStyle() + { + return rollingStyle; + } + + public void setRollingStyle(int style) + { + rollingStyle = style; + switch (rollingStyle) + { + + case BY_SIZE: + rollDate = false; + rollSize = true; + break; + + case BY_DATE: + rollDate = true; + rollSize = false; + break; + + case BY_COMPOSITE: + rollDate = true; + rollSize = true; + break; + + default: + errorHandler.error("Invalid rolling Style, use 1 (by size only), 2 (by date only) or 3 (both)"); + } + } + + /* + public void setRollingStyle(String style) { + if (style == S_BY_SIZE) { + rollingStyle = BY_SIZE; + } + else if (style == S_BY_DATE) { + rollingStyle = BY_DATE; + } + else if (style == S_BY_COMPOSITE) { + rollingStyle = BY_COMPOSITE; + } + } + */ + public boolean getStaticLogFileName() + { + return staticLogFileName; + } + + public void setStaticLogFileName(boolean s) + { + staticLogFileName = s; + } + + public void setStaticLogFileName(String value) + { + setStaticLogFileName(OptionConverter.toBoolean(value, true)); + } + + public boolean getCompressBackupFiles() + { + return compress; + } + + public void setCompressBackupFiles(boolean c) + { + compress = c; + } + + public boolean getCompressAsync() + { + return compressAsync; + } + + public void setCompressAsync(boolean c) + { + compressAsync = c; + if (compressAsync) + { + executor = Executors.newFixedThreadPool(1); + + compressor = new Compressor(); + } + } + + public boolean getZeroBased() + { + return zeroBased; + } + + public void setZeroBased(boolean z) + { + zeroBased = z; + } + + public String getBackupFilesToPath() + { + return backupFilesToPath; + } + + public void setbackupFilesToPath(String path) + { + File td = new File(path); + if (!td.exists()) + { + td.mkdirs(); + } + + backupFilesToPath = path; + } + + /** + * Initializes based on exisiting conditions at time of <code> activateOptions</code>. The following is done:<br> + * <br> A) determine curSizeRollBackups<br> B) determine curTimeRollBackups (not implemented)<br> C) initiates a + * roll over if needed for crossing a date boundary since the last run. + */ + protected void existingInit() + { + curTimeRollBackups = 0; + + // part A starts here + // This is now down at first log when curSizeRollBackup==0 see rollFile + // part A ends here + + // part B not yet implemented + + // part C + if (staticLogFileName && rollDate) + { + File old = new File(baseFileName); + if (old.exists()) + { + Date last = new Date(old.lastModified()); + if (!(sdf.format(last).equals(sdf.format(now)))) + { + scheduledFilename = baseFileName + sdf.format(last); + LogLog.debug("Initial roll over to: " + scheduledFilename); + rollOverTime(); + } + } + } + + LogLog.debug("curSizeRollBackups after rollOver at: " + curSizeRollBackups); + // part C ends here + + } + + /** + * Sets initial conditions including date/time roll over information, first check, scheduledFilename, and calls + * <code>existingInit</code> to initialize the current # of backups. + */ + public void activateOptions() + { + + // REMOVE removed rollDate from boolean to enable Alex's change + if (datePattern != null) + { + now.setTime(System.currentTimeMillis()); + sdf = new SimpleDateFormat(datePattern); + int type = computeCheckPeriod(); + // printPeriodicity(type); + rc.setType(type); + // next line added as this removes the name check in rollOver + nextCheck = rc.getNextCheckMillis(now); + } + else + { + if (rollDate) + { + LogLog.error("Either DatePattern or rollingStyle options are not set for [" + name + "]."); + } + } + + existingInit(); + + if (rollDate && (fileName != null) && (scheduledFilename == null)) + { + scheduledFilename = fileName + sdf.format(now); + } + + try + { + this.setFile(fileName, true); + } + catch (IOException e) + { + errorHandler.error("Cannot set file name:" + fileName); + } + + super.activateOptions(); + } + + /** + * Rollover the file(s) to date/time tagged file(s). Opens the new file (through setFile) and resets + * curSizeRollBackups. + */ + protected void rollOverTime() + { + + curTimeRollBackups++; + + this.closeFile(); // keep windows happy. + + + rollFile(); + + try + { + curSizeRollBackups = 0; // We're cleared out the old date and are ready for the new + + // new scheduled name + scheduledFilename = fileName + sdf.format(now); + this.setFile(baseFileName, false); + } + catch (IOException e) + { + errorHandler.error("setFile(" + fileName + ", false) call failed."); + } + + } + + /** + * Renames file <code>from</code> to file <code>to</code>. It also checks for existence of target file and deletes + * if it does. + */ + protected void rollFile(String from, String to, boolean compress) + { + if (from.equals(to)) + { + if (compress) + { + LogLog.error("Attempting to compress file with same output name."); + } + + return; + } + + if (backupFilesToPath != null) + { + to = backupFilesToPath + System.getProperty("file.separator") + new File(to).getName(); + } + + File target = new File(to); + + File file = new File(from); + // Perform Roll by renaming + if (!file.getPath().equals(target.getPath())) + { + file.renameTo(target); + } + + // Compress file after it has been moved out the way... this is safe + // as it will gain a .gz ending and we can then safely delete this file + // as it will not be the statically named value. + if (compress) + { + compress(target); + } + + LogLog.debug(from + " -> " + to); + } + + private void compress(File target) + { + if (compressAsync) + { + synchronized (_compress) + { + _compress.offer(new CompressJob(target, target)); + } + + startCompression(); + } + else + { + doCompress(target, target); + } + } + + private void startCompression() + { + if (_compressing.compareAndSet(false, true)) + { + executor.execute(compressor); + } + } + + /** + * Delete the given file that is prepended with the relative path to the log + * directory. + * + * Compress is enabled check for file with COMPRESS_EXTENSION(.gz) + * + * if backupFilesToPath is set then check in this directory not the + * main log directory. + */ + protected void deleteFile(String relativeFileName) + { + String fileName=""; + // If we have configured a backup location then we should look in there + // for the file we are trying to delete + if (backupFilesToPath != null) + { + File file = new File(relativeFileName); + + fileName = backupFilesToPath + System.getProperty("file.separator") + file.getName(); + } + + // If we are compressing the at the extension + if (compress) + { + fileName += COMPRESS_EXTENSION; + } + + + File file = new File(fileName); + + if (file.exists()) + { + file.delete(); + } + } + + /** + * Implements roll overs base on file size. + * + * <p>If the maximum number of size based backups is reached (<code>curSizeRollBackups == maxSizeRollBackups</code) + * then the oldest file is deleted -- it's index determined by the sign of countDirection.<br> If + * <code>countDirection</code> < 0, then files {<code>File.1</code>, ..., <code>File.curSizeRollBackups -1</code>} + * are renamed to {<code>File.2</code>, ..., <code>File.curSizeRollBackups</code>}. Moreover, <code>File</code> is + * renamed <code>File.1</code> and closed.<br> + * + * A new file is created to receive further log output. + * + * <p>If <code>maxSizeRollBackups</code> is equal to zero, then the <code>File</code> is truncated with no backup + * files created. + * + * <p>If <code>maxSizeRollBackups</code> < 0, then <code>File</code> is renamed if needed and no files are deleted. + */ + + // synchronization not necessary since doAppend is alreasy synched + protected void rollOverSize() + { + File file; + + this.closeFile(); // keep windows happy. + + LogLog.debug("rolling over count=" + ((CountingQuietWriter) qw).getCount()); + LogLog.debug("maxSizeRollBackups = " + maxSizeRollBackups); + LogLog.debug("curSizeRollBackups = " + curSizeRollBackups); + LogLog.debug("countDirection = " + countDirection); + + // If maxBackups <= 0, then there is no file renaming to be done. + if (maxSizeRollBackups != 0) + { + rollFile(); + } + + try + { + // This will also close the file. This is OK since multiple + // close operations are safe. + this.setFile(baseFileName, false); + } + catch (IOException e) + { + LogLog.error("setFile(" + fileName + ", false) call failed.", e); + } + } + + /** + * Perform file Rollover ensuring the countDirection is applied along with + * the other options + */ + private void rollFile() + { + LogLog.debug("CD="+countDirection+",start"); + if (countDirection < 0) + { + // If we haven't rolled yet then validate we have the right value + // for curSizeRollBackups + if (curSizeRollBackups == 0) + { + //Validate curSizeRollBackups + curSizeRollBackups = countFileIndex(fileName); + // decrement to offset the later increment + curSizeRollBackups--; + } + + // If we are not keeping an infinite set of backups the delete oldest + if (maxSizeRollBackups > 0) + { + LogLog.debug("CD=-1,curSizeRollBackups:"+curSizeRollBackups); + LogLog.debug("CD=-1,maxSizeRollBackups:"+maxSizeRollBackups); + + // Delete the oldest file. + // curSizeRollBackups is never -1 so infinite backups are ok here + if ((curSizeRollBackups - maxSizeRollBackups) >= 0) + { + //The oldest file is the one with the largest number + // as the 0 is always fileName + // which moves to fileName.1 etc. + LogLog.debug("CD=-1,deleteFile:"+curSizeRollBackups); + deleteFile(fileName + '.' + curSizeRollBackups); + // decrement to offset the later increment + curSizeRollBackups--; + } + } + // Map {(maxBackupIndex - 1), ..., 2, 1} to {maxBackupIndex, ..., 3, 2} + for (int i = curSizeRollBackups; i >= 1; i--) + { + String oldName = (fileName + "." + i); + String newName = (fileName + '.' + (i + 1)); + + // Ensure that when compressing we rename the compressed archives + if (compress) + { + rollFile(oldName + COMPRESS_EXTENSION, newName + COMPRESS_EXTENSION, false); + } + else + { + rollFile(oldName, newName, false); + } + } + + curSizeRollBackups++; + // Rename fileName to fileName.1 + rollFile(fileName, fileName + ".1", compress); + + } // REMOVE This code branching for Alexander Cerna's request + else if (countDirection == 0) + { + // rollFile based on date pattern + now.setTime(System.currentTimeMillis()); + String newFile = fileName + sdf.format(now); + + // If we haven't rolled yet then validate we have the right value + // for curSizeRollBackups + if (curSizeRollBackups == 0) + { + //Validate curSizeRollBackups + curSizeRollBackups = countFileIndex(newFile); + // to balance the increment just coming up. as the count returns + // the next free number not the last used. + curSizeRollBackups--; + } + + // If we are not keeping an infinite set of backups the delete oldest + if (maxSizeRollBackups > 0) + { + // Don't prune older files if they exist just go for the last + // one based on our maxSizeRollBackups. This means we may have + // more files left on disk that maxSizeRollBackups if this value + // is adjusted between runs but that is an acceptable state. + // Otherwise we would have to check on startup that we didn't + // have more than maxSizeRollBackups and prune then. + + if (((curSizeRollBackups - maxSizeRollBackups) >= 0)) + { + LogLog.debug("CD=0,curSizeRollBackups:"+curSizeRollBackups); + LogLog.debug("CD=0,maxSizeRollBackups:"+maxSizeRollBackups); + + // delete the first and keep counting up. + int oldestFileIndex = curSizeRollBackups - maxSizeRollBackups + 1; + LogLog.debug("CD=0,deleteFile:"+oldestFileIndex); + deleteFile(newFile + '.' + oldestFileIndex); + } + } + + + String finalName = newFile; + + curSizeRollBackups++; + + // Add rollSize if it is > 0 + if (curSizeRollBackups > 0 ) + { + finalName = newFile + '.' + curSizeRollBackups; + + } + + rollFile(fileName, finalName, compress); + } + else + { // countDirection > 0 + // If we haven't rolled yet then validate we have the right value + // for curSizeRollBackups + if (curSizeRollBackups == 0) + { + //Validate curSizeRollBackups + curSizeRollBackups = countFileIndex(fileName); + // to balance the increment just coming up. as the count returns + // the next free number not the last used. + curSizeRollBackups--; + } + + // If we are not keeping an infinite set of backups the delete oldest + if (maxSizeRollBackups > 0) + { + LogLog.debug("CD=1,curSizeRollBackups:"+curSizeRollBackups); + LogLog.debug("CD=1,maxSizeRollBackups:"+maxSizeRollBackups); + + // Don't prune older files if they exist just go for the last + // one based on our maxSizeRollBackups. This means we may have + // more files left on disk that maxSizeRollBackups if this value + // is adjusted between runs but that is an acceptable state. + // Otherwise we would have to check on startup that we didn't + // have more than maxSizeRollBackups and prune then. + + if (((curSizeRollBackups - maxSizeRollBackups) >= 0)) + { + // delete the first and keep counting up. + int oldestFileIndex = curSizeRollBackups - maxSizeRollBackups + 1; + LogLog.debug("CD=1,deleteFile:"+oldestFileIndex); + deleteFile(fileName + '.' + oldestFileIndex); + } + } + + + curSizeRollBackups++; + + rollFile(fileName, fileName + '.' + curSizeRollBackups, compress); + + } + LogLog.debug("CD="+countDirection+",done"); + } + + + private int countFileIndex(String fileName) + { + return countFileIndex(fileName, true); + } + /** + * Use filename as a base name and find what count number we are up to by + * looking at the files in this format: + * + * <filename>.<count>[COMPRESS_EXTENSION] + * + * If a count value of 1 cannot be found then a directory listing is + * performed to try and identify if there is a valid value for <count>. + * + * + * @param fileName the basefilename to use + * @param checkBackupLocation should backupFilesToPath location be checked for existing backups + * @return int the next free index + */ + private int countFileIndex(String fileName, boolean checkBackupLocation) + { + String testFileName; + + // It is possible for index 1..n to be missing leaving n+1..n+1+m logs + // in this scenario we should still return n+1+m+1 + int index=1; + + testFileName = fileName + "." + index; + + // Bail out early if there is a problem with the file + if (new File(testFileName) == null + || new File(testFileName + COMPRESS_EXTENSION) == null) + + { + return index; + } + + // Check that we do not have the 1..n missing scenario + if (!(new File(testFileName).exists() + || new File(testFileName + COMPRESS_EXTENSION).exists())) + + { + int max=0; + String prunedFileName = new File(fileName).getName(); + + // Look through all files to find next index + if (new File(fileName).getParentFile() != null) + { + for (File file : new File(fileName).getParentFile().listFiles()) + { + String name = file.getName(); + + if (name.startsWith(prunedFileName) && !name.equals(prunedFileName)) + { + String parsedCount = name.substring(prunedFileName.length() + 1); + + if (parsedCount.endsWith(COMPRESS_EXTENSION)) + { + parsedCount = parsedCount.substring(0, parsedCount.indexOf(COMPRESS_EXTENSION)); + } + + try + { + max = Integer.parseInt(parsedCount); + + // if we got a good value then update our index value. + if (max > index) + { + // +1 as we want to return the next free value. + index = max + 1; + } + } + catch (NumberFormatException nfe) + { + //ignore it assume file doesn't exist. + } + } + } + } + + // Update testFileName + testFileName = fileName + "." + index; + } + + + while (new File(testFileName).exists() + || new File(testFileName + COMPRESS_EXTENSION).exists()) + { + index++; + testFileName = fileName + "." + index; + } + + if (checkBackupLocation && index == 1 && backupFilesToPath != null) + { + LogLog.debug("Trying backup location:"+backupFilesToPath + System.getProperty("file.separator") + fileName); + return countFileIndex(backupFilesToPath + System.getProperty("file.separator") + new File(fileName).getName(), false); + } + + return index; + } + + protected synchronized void doCompress(File from, File to) + { + String toFile; + + toFile = to.getPath() + COMPRESS_EXTENSION; + + File target = new File(toFile); + if (target.exists()) + { + LogLog.debug("deleting existing target file: " + target); + target.delete(); + } + + try + { + // Create the GZIP output stream + GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(target)); + try + { + // Open the input file + FileInputStream in = new FileInputStream(from); + try + { + // Transfer bytes from the input file to the GZIP output stream + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) + { + out.write(buf, 0, len); + } + } + finally + { + in.close(); + } + + // Complete the GZIP file + out.finish(); + } + finally + { + out.close(); + } + // Remove old file. + from.delete(); + } + catch (IOException e) + { + if (target.exists()) + { + target.delete(); + } + + rollFile(from.getPath(), to.getPath(), false); + } + } + + private static class CompressJob + { + File _from, _to; + + CompressJob(File from, File to) + { + _from = from; + _to = to; + } + + File getFrom() + { + return _from; + } + + File getTo() + { + return _to; + } + } + + Compressor compressor = null; + + Executor executor; + + private class Compressor implements Runnable + { + public void run() + { + boolean running = true; + while (running) + { + CompressJob job = _compress.poll(); + + doCompress(job.getFrom(), job.getTo()); + + synchronized (_compress) + { + if (_compress.isEmpty()) + { + running = false; + _compressing.set(false); + } + } + } + + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/log4j/xml/QpidLog4JConfigurator.java b/qpid/java/broker/src/main/java/org/apache/log4j/xml/QpidLog4JConfigurator.java new file mode 100644 index 0000000000..1200ba6e0b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/log4j/xml/QpidLog4JConfigurator.java @@ -0,0 +1,320 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.log4j.xml; + +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.qpid.server.logging.management.LoggingManagementMBean; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +/** + * Substitute for the Log4J XMLWatchdog (as used by DOMConfigurator.configureAndWatch) + * + * Extends the default behaviour with a strict parser check on the XML file before allowing the reconfiguration to proceed, + * ensuring that any parser error or warning prevents initiation of a configuration update by Log4J, which aborts mid-update + * upon fatal errors from the parser and proceeds in the event of 'regular' parser errors and warnings, in all cases allowing + * startup to proceed with whatever half-baked configuration then exists. + */ +public class QpidLog4JConfigurator +{ + //lock to protect access to the configuration file + //shared with LoggingManagementMBean + public static final ReentrantLock LOCK = new ReentrantLock(); + private static Logger _logger; + private static DOMConfigurator domConfig = new DOMConfigurator(); + + private QpidLog4JConfigurator() + { + //no instances + } + + public static void configure(String filename) throws IOException, ParserConfigurationException, + SAXException, IllegalLoggerLevelException + { + try + { + LOCK.lock(); + + parseXMLConfigFile(filename); + checkLoggerLevels(filename); + + DOMConfigurator.configure(filename); + + if(_logger == null) + { + _logger = Logger.getLogger(QpidLog4JConfigurator.class); + } + } + finally + { + LOCK.unlock(); + } + } + + public static void configureAndWatch(String filename, long delay) throws IOException, ParserConfigurationException, + SAXException, IllegalLoggerLevelException + { + parseXMLConfigFile(filename); + checkLoggerLevels(filename); + + QpidLog4JXMLWatchdog watchdog = new QpidLog4JXMLWatchdog(filename); + watchdog.setDelay(delay); + watchdog.start(); + } + + private static void parseXMLConfigFile(String fileName) throws IOException, SAXException, + ParserConfigurationException + { + try + { + LOCK.lock(); + + //check file was specified, exists, and is readable + if(fileName == null) + { + throw new IOException("Provided log4j XML configuration filename was null"); + } + + File configFile = new File(fileName); + + if (!configFile.exists()) + { + throw new IOException("The log4j XML configuration file does not exist: " + fileName); + } + else if (!configFile.canRead()) + { + throw new IOException("The log4j XML configuration file is not readable: " + fileName); + } + + //parse it + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder; + + ErrorHandler errHandler = new QpidLog4JSaxErrorHandler(); + + docFactory.setValidating(true); + docBuilder = docFactory.newDocumentBuilder(); + docBuilder.setErrorHandler(errHandler); + docBuilder.setEntityResolver(new Log4jEntityResolver()); + docBuilder.parse(fileName); + } + finally + { + LOCK.unlock(); + } + } + + public static class QpidLog4JSaxErrorHandler implements ErrorHandler + { + public void error(SAXParseException e) throws SAXException + { + if(_logger != null) + { + _logger.warn(constructMessage("Error parsing XML file", e)); + } + else + { + System.err.println(constructMessage("Error parsing XML file", e)); + } + } + + public void fatalError(SAXParseException e) throws SAXException + { + throw new SAXException(constructMessage("Fatal error parsing XML file", e)); + } + + public void warning(SAXParseException e) throws SAXException + { + if(_logger != null) + { + _logger.warn(constructMessage("Warning parsing XML file", e)); + } + else + { + System.err.println(constructMessage("Warning parsing XML file", e)); + } + } + + private static String constructMessage(final String msg, final SAXParseException ex) + { + return msg + ": Line " + ex.getLineNumber()+" column " +ex.getColumnNumber() + ": " + ex.getMessage(); + } + } + + private static class QpidLog4JXMLWatchdog extends XMLWatchdog + { + public QpidLog4JXMLWatchdog(String filename) + { + super(filename); + } + + public void doOnChange() + { + try + { + LOCK.lock(); + + try + { + parseXMLConfigFile(filename); + } + catch (Exception e) + { + //logger will be instantiated following first configuration success, which has been pre-validated + //and so the null check should never actually be required. + if(_logger != null) + { + _logger.warn("Parsing the log4j XML configuration file generated errors/warnings. " + + "The new configuration was not applied. Correct the issues to prompt " + + "another update attempt: " + e.getMessage()); + } + return; + } + + try + { + checkLoggerLevels(filename); + } + catch (Exception e) + { + //logger will be instantiated following first configuration success, which has been pre-validated + //and so the null check should never actually be required. + if(_logger != null) + { + _logger.warn("Errors were found when validating the logger level values in the " + + "log4j XML configuration file. The new configuration was not applied. " + + "Correct the issues to prompt another update attempt: " + e.getMessage()); + } + return; + } + + //everything checked was ok, let the normal update process proceed + super.doOnChange(); + + //a configuration has now been applied, enable logging for future attempts + if(_logger == null) + { + _logger = Logger.getLogger(QpidLog4JConfigurator.class); + } + + _logger.info("Applied log4j configuration from: " + filename); + } + finally + { + LOCK.unlock(); + } + + } + } + + protected static void checkLoggerLevels(String filename) throws IllegalLoggerLevelException, IOException + { + //check that the logger levels specified in the XML are actually valid + + try + { + LOCK.lock(); + + //get the Logger levels to check + Map<String, String> loggersLevels; + loggersLevels = LoggingManagementMBean.retrieveConfigFileLoggersLevels(filename); + //add the RootLogger to the list too + String rootLoggerlevelString = LoggingManagementMBean.retrieveConfigFileRootLoggerLevel(filename); + loggersLevels.put("Root", rootLoggerlevelString); + + + for (Map.Entry<String, String> entry : loggersLevels.entrySet()) + { + String loggerName = entry.getKey(); + String levelString = entry.getValue(); + + //let log4j replace any properties in the string + String log4jConfiguredString = domConfig.subst(levelString); + + if(log4jConfiguredString.equals("") && ! log4jConfiguredString.equals(levelString)) + { + //log4j has returned an empty string but this isnt what we gave it. + //There may have been an undefined property. Unlike an incorrect + //literal value, we will allow this case to proceed, but warn users. + + if(_logger != null) + { + _logger.warn("Unable to detect Level value from '" + levelString + +"' for logger '" + loggerName + "', Log4J will default this to DEBUG"); + } + else + { + System.err.println("Unable to detect Level value from '" + levelString + +"' for logger " + loggerName + ", Log4J will default this to DEBUG"); + } + + continue; + } + + checkLevel(loggerName,log4jConfiguredString); + } + } + finally + { + LOCK.unlock(); + } + } + + private static void checkLevel(String loggerName, String levelString) throws IllegalLoggerLevelException + { + if("null".equalsIgnoreCase(levelString) || "inherited".equalsIgnoreCase(levelString)) + { + //the string "null" signals to inherit from a parent logger + return; + } + + Level level = Level.toLevel(levelString); + + //above Level.toLevel call returns a DEBUG Level if the request fails. Check the result. + if (level.equals(Level.DEBUG) && !(levelString.equalsIgnoreCase("debug"))) + { + //received DEBUG but we did not ask for it, the Level request failed. + throw new IllegalLoggerLevelException("Level '" + levelString + "' specified for Logger '" + loggerName + "' is invalid"); + } + } + + public static class IllegalLoggerLevelException extends Exception + { + private static final long serialVersionUID = 1L; + + public IllegalLoggerLevelException(String msg) + { + super(msg); + } + } +} + diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/configuration/Configuration.java b/qpid/java/broker/src/main/java/org/apache/qpid/configuration/Configuration.java new file mode 100644 index 0000000000..0b63c68854 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/configuration/Configuration.java @@ -0,0 +1,188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.configuration; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.cli.PosixParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; + +public class Configuration +{ + public static final String QPID_HOME = "QPID_HOME"; + + final String QPIDHOME = System.getProperty(QPID_HOME); + + private static Logger _devlog = LoggerFactory.getLogger(Configuration.class); + + public static final String DEFAULT_LOG_CONFIG_FILENAME = "log4j.xml"; + public static final String DEFAULT_CONFIG_FILE = "etc/config.xml"; + + protected final Options _options = new Options(); + protected CommandLine _commandLine; + protected File _configFile; + + + public Configuration() + { + + } + + public void processCommandline(String[] args) throws InitException + { + try + { + _commandLine = new PosixParser().parse(_options, args); + } + catch (ParseException e) + { + throw new InitException("Unable to parse commmandline", e); + } + + final File defaultConfigFile = new File(QPIDHOME, DEFAULT_CONFIG_FILE); + setConfig(new File(_commandLine.getOptionValue("c", defaultConfigFile.getPath()))); + } + + public void setConfig(File file) + { + _configFile = file; + } + + /** + * @param option The option to set. + */ + public void setOption(Option option) + { + _options.addOption(option); + } + + /** + * getOptionValue from the configuration + * @param option variable argument, first string is option to get, second if present is the default value. + * @return the String for the given option or null if not present (if default value not specified) + */ + public String getOptionValue(String... option) + { + if (option.length == 1) + { + return _commandLine.getOptionValue(option[0]); + } + else if (option.length == 2) + { + return _commandLine.getOptionValue(option[0], option[1]); + } + return null; + } + + public void loadConfig(File file) throws InitException + { + setConfig(file); + loadConfig(); + } + + private void loadConfig() throws InitException + { + if (!_configFile.exists()) + { + String error = "File " + _configFile + " could not be found. Check the file exists and is readable."; + + if (QPIDHOME == null) + { + error = error + "\nNote: " + QPID_HOME + " is not set."; + } + + throw new InitException(error, null); + } + else + { + _devlog.debug("Using configuration file " + _configFile.getAbsolutePath()); + } + +// String logConfig = _commandLine.getOptionValue("l"); +// String logWatchConfig = _commandLine.getOptionValue("w", "0"); +// if (logConfig != null) +// { +// File logConfigFile = new File(logConfig); +// configureLogging(logConfigFile, logWatchConfig); +// } +// else +// { +// File configFileDirectory = _configFile.getParentFile(); +// File logConfigFile = new File(configFileDirectory, DEFAULT_LOG_CONFIG_FILENAME); +// configureLogging(logConfigFile, logWatchConfig); +// } + } + + +// private void configureLogging(File logConfigFile, String logWatchConfig) +// { +// int logWatchTime = 0; +// try +// { +// logWatchTime = Integer.parseInt(logWatchConfig); +// } +// catch (NumberFormatException e) +// { +// _devlog.error("Log watch configuration value of " + logWatchConfig + " is invalid. Must be " +// + "a non-negative integer. Using default of zero (no watching configured"); +// } +// +// if (logConfigFile.exists() && logConfigFile.canRead()) +// { +// _devlog.info("Configuring logger using configuration file " + logConfigFile.getAbsolutePath()); +// if (logWatchTime > 0) +// { +// _devlog.info("log file " + logConfigFile.getAbsolutePath() + " will be checked for changes every " +// + logWatchTime + " seconds"); +// // log4j expects the watch interval in milliseconds +// DOMConfigurator.configureAndWatch(logConfigFile.getAbsolutePath(), logWatchTime * 1000); +// } +// else +// { +// DOMConfigurator.configure(logConfigFile.getAbsolutePath()); +// } +// } +// else +// { +// System.err.println("Logging configuration error: unable to read file " + logConfigFile.getAbsolutePath()); +// System.err.println("Using basic log4j configuration"); +// BasicConfigurator.configure(); +// } +// } + + public File getConfigFile() + { + return _configFile; + } + + + public static class InitException extends Exception + { + InitException(String msg, Throwable cause) + { + super(msg, cause); + } + } +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/CompletionCode.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/CompletionCode.java new file mode 100644 index 0000000000..706ab3974a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/CompletionCode.java @@ -0,0 +1,36 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +public enum CompletionCode +{ + OK, + UNKNOWN_OBJECT, + UNKNOWN_METHOD, + NOT_IMPLEMENTED, + INVALID_PARAMETER, + FEATURE_NOT_IMPLEMENTED, + FORBIDDEN, + EXCEPTION, + UNKNOWN_PACKAGE, + UNKNOWN_CLASS; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/ManagementExchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/ManagementExchange.java new file mode 100644 index 0000000000..593c1616fb --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/ManagementExchange.java @@ -0,0 +1,557 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.configuration.ConfigStore; +import org.apache.qpid.server.configuration.ConfiguredObject; +import org.apache.qpid.server.configuration.ExchangeConfigType; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeReferrer; +import org.apache.qpid.server.exchange.ExchangeType; +import org.apache.qpid.server.exchange.topic.TopicExchangeResult; +import org.apache.qpid.server.exchange.topic.TopicMatcherResult; +import org.apache.qpid.server.exchange.topic.TopicNormalizer; +import org.apache.qpid.server.exchange.topic.TopicParser; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.virtualhost.HouseKeepingTask; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.TimerTask; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.atomic.AtomicLong; + +public class ManagementExchange implements Exchange, QMFService.Listener +{ + private static final AMQShortString QPID_MANAGEMENT = new AMQShortString("qpid.management"); + private static final AMQShortString QPID_MANAGEMENT_TYPE = new AMQShortString("management"); + + private VirtualHost _virtualHost; + + private final TopicParser _parser = new TopicParser(); + + private final Map<AMQShortString, TopicExchangeResult> _topicExchangeResults = + new ConcurrentHashMap<AMQShortString, TopicExchangeResult>(); + + private final Set<Binding> _bindingSet = new CopyOnWriteArraySet<Binding>(); + private UUID _id; + private static final String AGENT_BANK = "0"; + + private int _bindingCountHigh; + private final AtomicLong _msgReceived = new AtomicLong(); + private final AtomicLong _bytesReceived = new AtomicLong(); + + private final CopyOnWriteArrayList<BindingListener> _listeners = new CopyOnWriteArrayList<Exchange.BindingListener>(); + + //TODO : persist creation time + private long _createTime = System.currentTimeMillis(); + + + private class ManagementQueue implements BaseQueue + { + private final String NAME_AS_STRING = "##__mgmt_pseudo_queue__##" + UUID.randomUUID().toString(); + private final AMQShortString NAME_AS_SHORT_STRING = new AMQShortString(NAME_AS_STRING); + + public void enqueue(ServerMessage message) throws AMQException + { + long size = message.getSize(); + + ByteBuffer buf = ByteBuffer.allocate((int) size); + + int offset = 0; + + while(offset < size) + { + offset += message.getContent(buf,offset); + } + + buf.flip(); + QMFCommandDecoder commandDecoder = new QMFCommandDecoder(getQMFService(),buf); + QMFCommand cmd; + while((cmd = commandDecoder.decode()) != null) + { + cmd.process(_virtualHost, message); + } + + } + + public void enqueue(ServerMessage message, PostEnqueueAction action) throws AMQException + { + enqueue(message); + } + + public boolean isDurable() + { + return false; + } + + public AMQShortString getNameShortString() + { + return NAME_AS_SHORT_STRING; + } + + public String getResourceName() + { + return NAME_AS_STRING; + } + } + + + private final ManagementQueue _mgmtQueue = new ManagementQueue(); + + public ManagementExchange() + { + } + + public static final ExchangeType<ManagementExchange> TYPE = new ExchangeType<ManagementExchange>() + { + + public AMQShortString getName() + { + return QPID_MANAGEMENT_TYPE; + } + + public Class<ManagementExchange> getExchangeClass() + { + return ManagementExchange.class; + } + + public ManagementExchange newInstance(VirtualHost host, + AMQShortString name, + boolean durable, + int ticket, + boolean autoDelete) throws AMQException + { + ManagementExchange exch = new ManagementExchange(); + exch.initialise(host, name, durable, ticket, autoDelete); + return exch; + } + + public AMQShortString getDefaultExchangeName() + { + return QPID_MANAGEMENT; + } + }; + + + public AMQShortString getNameShortString() + { + return QPID_MANAGEMENT; + } + + public AMQShortString getTypeShortString() + { + return QPID_MANAGEMENT_TYPE; + } + + public void initialise(VirtualHost host, AMQShortString name, boolean durable, int ticket, boolean autoDelete) + throws AMQException + { + if(!QPID_MANAGEMENT.equals(name)) + { + throw new AMQException("Can't create more than one Management exchange"); + } + _virtualHost = host; + _id = host.getConfigStore().createId(); + _virtualHost.scheduleHouseKeepingTask(_virtualHost.getBroker().getManagementPublishInterval(), new UpdateTask(_virtualHost)); + getConfigStore().addConfiguredObject(this); + getQMFService().addListener(this); + } + + public UUID getId() + { + return _id; + } + + public ExchangeConfigType getConfigType() + { + return ExchangeConfigType.getInstance(); + } + + public ConfiguredObject getParent() + { + return _virtualHost; + } + + public boolean isDurable() + { + return true; + } + + public VirtualHost getVirtualHost() + { + return _virtualHost; + } + + public String getName() + { + return QPID_MANAGEMENT.toString(); + } + + public ExchangeType getType() + { + return TYPE; + } + + public boolean isAutoDelete() + { + return false; + } + + public int getTicket() + { + return 0; + } + + public void close() throws AMQException + { + getConfigStore().removeConfiguredObject(this); + } + + public ConfigStore getConfigStore() + { + return getVirtualHost().getConfigStore(); + } + + public synchronized void addBinding(final Binding b) + { + + if(_bindingSet.add(b)) + { + AMQShortString routingKey = TopicNormalizer.normalize(new AMQShortString(b.getBindingKey())); + + TopicExchangeResult result = _topicExchangeResults.get(routingKey); + if(result == null) + { + result = new TopicExchangeResult(); + result.addUnfilteredQueue(b.getQueue()); + _parser.addBinding(routingKey, result); + _topicExchangeResults.put(routingKey,result); + } + else + { + result.addUnfilteredQueue(b.getQueue()); + } + + result.addBinding(b); + } + + for(BindingListener listener : _listeners) + { + listener.bindingAdded(this, b); + } + + if(_bindingSet.size() > _bindingCountHigh) + { + _bindingCountHigh = _bindingSet.size(); + } + + String bindingKey = b.getBindingKey(); + + if(bindingKey.startsWith("schema.") || bindingKey.startsWith("*.") || bindingKey.startsWith("#.")) + { + publishAllSchema(); + } + if(bindingKey.startsWith("console.") || bindingKey.startsWith("*.") || bindingKey.startsWith("#.")) + { + publishAllConsole(); + } + + } + + void publishAllConsole() + { + QMFService qmfService = getQMFService(); + + long sampleTime = System.currentTimeMillis(); + + for(QMFPackage pkg : qmfService.getSupportedSchemas()) + { + for(QMFClass qmfClass : pkg.getClasses()) + { + Collection<QMFObject> qmfObjects = qmfService.getObjects(qmfClass); + + publishObjectsToConsole(sampleTime, qmfObjects); + } + + } + + } + + private QMFService getQMFService() + { + return _virtualHost.getApplicationRegistry().getQMFService(); + } + + void publishObjectsToConsole(final long sampleTime, + final Collection<QMFObject> qmfObjects) + { + if(!qmfObjects.isEmpty() && hasBindings()) + { + QMFClass qmfClass = qmfObjects.iterator().next().getQMFClass(); + ArrayList<QMFCommand> commands = new ArrayList<QMFCommand>(); + + + for(QMFObject obj : qmfObjects) + { + commands.add(obj.asConfigInfoCmd(sampleTime)); + commands.add(obj.asInstrumentInfoCmd(sampleTime)); + } + + publishToConsole(qmfClass, commands); + } + } + + private void publishToConsole(final QMFClass qmfClass, final ArrayList<QMFCommand> commands) + { + if(!commands.isEmpty() && hasBindings()) + { + String routingKey = "console.obj.1." + AGENT_BANK + "." + qmfClass.getPackage().getName() + "." + qmfClass.getName(); + QMFMessage message = new QMFMessage(routingKey,commands.toArray(new QMFCommand[commands.size()])); + + Collection<TopicMatcherResult> results = _parser.parse(new AMQShortString(routingKey)); + HashSet<AMQQueue> queues = new HashSet<AMQQueue>(); + for(TopicMatcherResult result : results) + { + TopicExchangeResult res = (TopicExchangeResult)result; + + for(Binding b : res.getBindings()) + { + b.incrementMatches(); + } + + queues.addAll(((TopicExchangeResult)result).getUnfilteredQueues()); + } + for(AMQQueue queue : queues) + { + try + { + queue.enqueue(message); + } + catch (AMQException e) + { + throw new RuntimeException(e); + } + } + } + } + + void publishAllSchema() + { + + } + + public synchronized void removeBinding(final Binding binding) + { + if(_bindingSet.remove(binding)) + { + AMQShortString bindingKey = TopicNormalizer.normalize(new AMQShortString(binding.getBindingKey())); + TopicExchangeResult result = _topicExchangeResults.get(bindingKey); + result.removeBinding(binding); + result.removeUnfilteredQueue(binding.getQueue()); + } + + for(BindingListener listener : _listeners) + { + listener.bindingRemoved(this, binding); + } + } + + public synchronized Collection<Binding> getBindings() + { + return new ArrayList<Binding>(_bindingSet); + } + + public ArrayList<BaseQueue> route(InboundMessage message) + { + ArrayList<BaseQueue> queues = new ArrayList<BaseQueue>(1); + _msgReceived.incrementAndGet(); + _bytesReceived.addAndGet(message.getSize()); + queues.add(_mgmtQueue); + return queues; + } + + public boolean isBound(AMQShortString routingKey, FieldTable arguments, AMQQueue queue) + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isBound(AMQShortString routingKey, AMQQueue queue) + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isBound(AMQShortString routingKey) + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isBound(AMQQueue queue) + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean hasBindings() + { + return !_bindingSet.isEmpty(); + } + + public boolean isBound(String bindingKey, AMQQueue queue) + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public boolean isBound(String bindingKey) + { + return false; //To change body of implemented methods use File | Settings | File Templates. + } + + public void addCloseTask(final Task task) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void removeCloseTask(final Task task) + { + //To change body of implemented methods use File | Settings | File Templates. + } + + + + public Exchange getAlternateExchange() + { + return null; + } + + public Map<String, Object> getArguments() + { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void setAlternateExchange(Exchange exchange) + { + + } + + public void removeReference(ExchangeReferrer exchange) + { + } + + public void addReference(ExchangeReferrer exchange) + { + } + + public boolean hasReferrers() + { + return true; + } + + + + private class UpdateTask extends HouseKeepingTask + { + public UpdateTask(VirtualHost vhost) + { + super(vhost); + } + + public void execute() + { + publishAllConsole(); + publishAllSchema(); + } + + } + + public void objectCreated(final QMFObject obj) + { + publishObjectsToConsole(System.currentTimeMillis(), Collections.singleton(obj)); + } + + public void objectDeleted(final QMFObject obj) + { + publishObjectsToConsole(System.currentTimeMillis(), Collections.singleton(obj)); + } + + public long getBindingCount() + { + return getBindings().size(); + } + + public long getBindingCountHigh() + { + return _bindingCountHigh; + } + + public long getMsgReceives() + { + return _msgReceived.get(); + } + + public long getMsgRoutes() + { + return getMsgReceives(); + } + + public long getByteReceives() + { + return _bytesReceived.get(); + } + + public long getByteRoutes() + { + return getByteReceives(); + } + + public long getCreateTime() + { + return _createTime; + } + + public void addBindingListener(final BindingListener listener) + { + _listeners.add(listener); + } + + public void removeBindingListener(final BindingListener listener) + { + _listeners.remove(listener); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFBrokerRequestCommand.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFBrokerRequestCommand.java new file mode 100644 index 0000000000..b98daf7cb1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFBrokerRequestCommand.java @@ -0,0 +1,78 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.transport.codec.BBDecoder; +import org.apache.qpid.transport.codec.BBEncoder; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.message.MessageTransferMessage; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.AMQException; +import org.apache.qpid.management.common.mbeans.ManagedConnection; + +import java.util.ArrayList; + +public class QMFBrokerRequestCommand extends QMFCommand +{ + + public QMFBrokerRequestCommand(QMFCommandHeader header, BBDecoder buf) + { + super(header); + } + + public void process(VirtualHost virtualHost, ServerMessage message) + { + String exchangeName = message.getMessageHeader().getReplyToExchange(); + String queueName = message.getMessageHeader().getReplyToRoutingKey(); + + QMFCommand[] commands = new QMFCommand[2]; + commands[0] = new QMFBrokerResponseCommand(this, virtualHost); + commands[1] = new QMFCommandCompletionCommand(this); + + Exchange exchange = virtualHost.getExchangeRegistry().getExchange(exchangeName); + + for(QMFCommand cmd : commands) + { + QMFMessage responseMessage = new QMFMessage(queueName, cmd); + + + ArrayList<? extends BaseQueue> queues = exchange.route(responseMessage); + + + for(BaseQueue q : queues) + { + try + { + q.enqueue(responseMessage); + } + catch (AMQException e) + { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + } + } + } + + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFBrokerResponseCommand.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFBrokerResponseCommand.java new file mode 100644 index 0000000000..ac01c47fe8 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFBrokerResponseCommand.java @@ -0,0 +1,46 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.transport.codec.BBEncoder; + +public class QMFBrokerResponseCommand extends QMFCommand +{ + private QMFCommandHeader _header; + private VirtualHost _virtualHost; + + public QMFBrokerResponseCommand(QMFBrokerRequestCommand qmfBrokerRequestCommand, VirtualHost virtualHost) + { + super( new QMFCommandHeader(qmfBrokerRequestCommand.getHeader().getVersion(), + qmfBrokerRequestCommand.getHeader().getSeq(), + QMFOperation.BROKER_RESPONSE)); + _virtualHost = virtualHost; + } + + public void encode(BBEncoder encoder) + { + super.encode(encoder); + encoder.writeUuid(_virtualHost.getBrokerId()); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFClass.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFClass.java new file mode 100644 index 0000000000..3408ff09f4 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFClass.java @@ -0,0 +1,158 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.server.configuration.ConfiguredObject; + +import java.util.Collection; +import java.util.Map; +import java.util.List; +import java.util.LinkedHashMap; + +abstract public class QMFClass +{ + + + public enum Type + { + OBJECT((byte)1), + EVENT((byte)2); + + private final byte _value; + + Type(byte value) + { + _value = value; + } + + public byte getValue() + { + return _value; + } + } + + private final Type _type; + private QMFPackage _package; + private final String _name; + private byte[] _schemaHash; + + private Map<String, QMFProperty> _properties = new LinkedHashMap<String, QMFProperty>(); + private Map<String, QMFStatistic> _statistics = new LinkedHashMap<String, QMFStatistic>(); + private Map<String, QMFMethod> _methods = new LinkedHashMap<String, QMFMethod>(); + + + + public QMFClass(Type type, String name, byte[] schemaHash, List<QMFProperty> properties, + List<QMFStatistic> statistics, List<QMFMethod> methods) + { + this(type, name, schemaHash); + setProperties(properties); + setStatistics(statistics); + setMethods(methods); + } + + + public QMFClass(Type type, String name, byte[] schemaHash) + + { + _type = type; + _name = name; + _schemaHash = schemaHash; + + } + + protected void setProperties(List<QMFProperty> properties) + { + for(QMFProperty prop : properties) + { + _properties.put(prop.getName(), prop); + } + } + + protected void setStatistics(List<QMFStatistic> statistics) + { + for(QMFStatistic stat : statistics) + { + _statistics.put(stat.getName(), stat); + } + } + + + protected void setMethods(List<QMFMethod> methods) + { + for(QMFMethod method : methods) + { + _methods.put(method.getName(), method); + } + } + + public void setPackage(QMFPackage aPackage) + { + _package = aPackage; + for(QMFProperty prop : _properties.values()) + { + prop.setQMFClass(this); + } + // TODO Statisics, Methods + } + + public Type getType() + { + return _type; + } + + public QMFPackage getPackage() + { + return _package; + } + + public String getName() + { + return _name; + } + + public byte[] getSchemaHash() + { + return _schemaHash; + } + + public Collection<QMFProperty> getProperties() + { + return _properties.values(); + } + + public Collection<QMFStatistic> getStatistics() + { + return _statistics.values(); + } + + public Collection<QMFMethod> getMethods() + { + return _methods.values(); + } + + public QMFMethod getMethod(String methodName) + { + return _methods.get(methodName); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFClassIndicationCommand.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFClassIndicationCommand.java new file mode 100644 index 0000000000..a956a9bd70 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFClassIndicationCommand.java @@ -0,0 +1,50 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.transport.codec.BBEncoder; + +public class QMFClassIndicationCommand extends QMFCommand +{ + private QMFClass _qmfClass; + + public QMFClassIndicationCommand(QMFClassQueryCommand qmfClassQueryCommand, QMFClass qmfClass) + { + super(new QMFCommandHeader(qmfClassQueryCommand.getHeader().getVersion(), + qmfClassQueryCommand.getHeader().getSeq(), + QMFOperation.CLASS_INDICATION)); + _qmfClass = qmfClass; + } + + + @Override + public void encode(BBEncoder encoder) + { + super.encode(encoder); + encoder.writeUint8(_qmfClass.getType().getValue()); + encoder.writeStr8(_qmfClass.getPackage().getName()); + encoder.writeStr8(_qmfClass.getName()); + encoder.writeBin128(_qmfClass.getSchemaHash()); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFClassQueryCommand.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFClassQueryCommand.java new file mode 100644 index 0000000000..26a27cfa19 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFClassQueryCommand.java @@ -0,0 +1,90 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.transport.codec.BBDecoder; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.AMQException; + +import java.util.ArrayList; +import java.util.Collection; + +public class QMFClassQueryCommand extends QMFCommand +{ + private final String _package; + + public QMFClassQueryCommand(QMFCommandHeader header, BBDecoder decoder) + { + super(header); + _package = decoder.readStr8(); + } + + public void process(VirtualHost virtualHost, ServerMessage message) + { + String exchangeName = message.getMessageHeader().getReplyToExchange(); + String routingKey = message.getMessageHeader().getReplyToRoutingKey(); + + IApplicationRegistry appRegistry = virtualHost.getApplicationRegistry(); + QMFService service = appRegistry.getQMFService(); + + QMFPackage qmfPackage = service.getPackage(_package); + Collection<QMFClass> qmfClasses = qmfPackage.getClasses(); + + QMFCommand[] commands = new QMFCommand[ qmfClasses.size() + 1 ]; + + int i = 0; + for(QMFClass qmfClass : qmfClasses) + { + commands[i++] = new QMFClassIndicationCommand(this, qmfClass); + } + commands[ commands.length - 1 ] = new QMFCommandCompletionCommand(this); + + + for(QMFCommand cmd : commands) + { + + + QMFMessage responseMessage = new QMFMessage(routingKey, cmd); + + Exchange exchange = virtualHost.getExchangeRegistry().getExchange(exchangeName); + + ArrayList<? extends BaseQueue> queues = exchange.route(responseMessage); + + for(BaseQueue q : queues) + { + try + { + q.enqueue(responseMessage); + } + catch (AMQException e) + { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + } + } + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFCommand.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFCommand.java new file mode 100644 index 0000000000..4f143701af --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFCommand.java @@ -0,0 +1,54 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.transport.codec.BBEncoder; + +public abstract class QMFCommand +{ + + private final QMFCommandHeader _header; + + protected QMFCommand(QMFCommandHeader header) + { + _header = header; + } + + + public void process(final VirtualHost virtualHost, final ServerMessage message) + { + throw new UnsupportedOperationException(); + } + + public void encode(BBEncoder encoder) + { + _header.encode(encoder); + + } + + public QMFCommandHeader getHeader() + { + return _header; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFCommandCompletionCommand.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFCommandCompletionCommand.java new file mode 100644 index 0000000000..f163e434d1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFCommandCompletionCommand.java @@ -0,0 +1,56 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.transport.codec.BBEncoder; + +public class QMFCommandCompletionCommand extends QMFCommand +{ + + private final CompletionCode _status; + private final String _text; + + public QMFCommandCompletionCommand(QMFCommand command) + { + this(command, CompletionCode.OK, ""); + } + public QMFCommandCompletionCommand(QMFCommand command, CompletionCode status, String text) + { + super( new QMFCommandHeader(command.getHeader().getVersion(), + command.getHeader().getSeq(), + QMFOperation.COMMAND_COMPLETION)); + + _status = status; + _text = text; + } + + + @Override + public void encode(BBEncoder encoder) + { + super.encode(encoder); + encoder.writeInt32(_status.ordinal()); + encoder.writeStr8(_text); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFCommandDecoder.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFCommandDecoder.java new file mode 100644 index 0000000000..ac036dfa19 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFCommandDecoder.java @@ -0,0 +1,98 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.transport.codec.BBDecoder; + +import java.nio.ByteBuffer; + +public class QMFCommandDecoder +{ + private BBDecoder _decoder; + + + private static final QMFOperation[] OP_CODES = new QMFOperation[256]; + private final QMFService _qmfService; + + static + { + for(QMFOperation op : QMFOperation.values()) + { + OP_CODES[op.getOpcode()] = op; + } + } + + public QMFCommandDecoder(final QMFService qmfService, ByteBuffer buf) + { + _qmfService = qmfService; + _decoder = new BBDecoder(); + _decoder.init(buf); + } + + public QMFCommand decode() + { + if(_decoder.hasRemaining()) + { + QMFCommandHeader header = readQMFHeader(); + + switch(header.getOperation()) + { + case BROKER_REQUEST: + return new QMFBrokerRequestCommand(header, _decoder); + case PACKAGE_QUERY: + return new QMFPackageQueryCommand(header, _decoder); + case CLASS_QUERY: + return new QMFClassQueryCommand(header, _decoder); + case SCHEMA_REQUEST: + return new QMFSchemaRequestCommand(header, _decoder); + case METHOD_REQUEST: + return new QMFMethodRequestCommand(header, _decoder, _qmfService); + case GET_QUERY: + return new QMFGetQueryCommand(header, _decoder); + default: + System.out.println("Unknown command"); + + } + + return null; + } + else + { + return null; + } + } + + private QMFCommandHeader readQMFHeader() + { + if(_decoder.readInt8() == (byte) 'A' + && _decoder.readInt8() == (byte) 'M') + { + byte version = _decoder.readInt8(); + short opCode = _decoder.readUint8(); + int seq = _decoder.readInt32(); + + return new QMFCommandHeader(version, seq, OP_CODES[opCode]); + + } + return null; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFCommandHeader.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFCommandHeader.java new file mode 100644 index 0000000000..c4d771317f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFCommandHeader.java @@ -0,0 +1,63 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.transport.codec.BBEncoder; + +public class QMFCommandHeader +{ + private final byte _version; + private final int _seq; + + private final QMFOperation _operation; + + public QMFCommandHeader(byte version, int seq, QMFOperation operation) + { + _version = version; + _seq = seq; + _operation = operation; + } + + public byte getVersion() + { + return _version; + } + + public int getSeq() + { + return _seq; + } + + public QMFOperation getOperation() + { + return _operation; + } + + public void encode(BBEncoder encoder) + { + encoder.writeUint8((short)'A'); + encoder.writeUint8((short)'M'); + encoder.writeInt8(_version); + encoder.writeUint8((short)_operation.getOpcode()); + encoder.writeInt32(_seq); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFEventClass.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFEventClass.java new file mode 100644 index 0000000000..ec471f18e8 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFEventClass.java @@ -0,0 +1,42 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import java.util.List; + +public abstract class QMFEventClass extends QMFClass +{ + public QMFEventClass(String name, + byte[] schemaHash, + List<QMFProperty> properties, + List<QMFStatistic> statistics, List<QMFMethod> methods) + { + super(Type.EVENT, name, schemaHash, properties, statistics, methods); + } + + public QMFEventClass(String name, byte[] schemaHash) + { + super(Type.EVENT, name, schemaHash); + } + + abstract public QMFEventSeverity getSeverity(); + +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFEventCommand.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFEventCommand.java new file mode 100644 index 0000000000..d70c12db19 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFEventCommand.java @@ -0,0 +1,49 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.transport.codec.BBEncoder; + +public abstract class QMFEventCommand<T extends QMFEventClass> extends QMFCommand +{ + private final long _timestamp; + + protected QMFEventCommand() + { + super(new QMFCommandHeader((byte)'2',0, QMFOperation.EVENT)); + _timestamp = System.currentTimeMillis(); + } + + abstract public T getEventClass(); + + @Override + public void encode(final BBEncoder encoder) + { + super.encode(encoder); + encoder.writeStr8(getEventClass().getPackage().getName()); + encoder.writeStr8(getEventClass().getName()); + encoder.writeBin128(new byte[16]); + encoder.writeUint64(_timestamp * 1000000L); + encoder.writeUint8((short) getEventClass().getSeverity().ordinal()); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFEventSeverity.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFEventSeverity.java new file mode 100644 index 0000000000..9f9c832732 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFEventSeverity.java @@ -0,0 +1,33 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +public enum QMFEventSeverity +{ + EMERGENCY, + ALERT, + CRITICAL, + ERROR, + WARN, + NOTICE, + INFORM, + DEBUG +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFGetQueryCommand.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFGetQueryCommand.java new file mode 100644 index 0000000000..8e8cb55a0d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFGetQueryCommand.java @@ -0,0 +1,182 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.transport.codec.BBDecoder; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.AMQException; + +import java.util.*; + +public class QMFGetQueryCommand extends QMFCommand +{ + private Map<String, Object> _map; + + + public QMFGetQueryCommand(QMFCommandHeader header, BBDecoder decoder) + { + super(header); + + _map = decoder.readMap(); + } + + public void process(VirtualHost virtualHost, ServerMessage message) + { + String exchangeName = message.getMessageHeader().getReplyToExchange(); + String routingKey = message.getMessageHeader().getReplyToRoutingKey(); + + IApplicationRegistry appRegistry = virtualHost.getApplicationRegistry(); + QMFService service = appRegistry.getQMFService(); + + String className = (String) _map.get("_class"); + String packageName = (String) _map.get("_package"); + byte[] objectIdBytes = (byte[]) _map.get("_objectId"); + UUID objectId; + if(objectIdBytes != null) + { + long msb = 0; + long lsb = 0; + + for (int i = 0; i != 8; i++) + { + msb = (msb << 8) | (objectIdBytes[i] & 0xff); + } + for (int i = 8; i != 16; i++) + { + lsb = (lsb << 8) | (objectIdBytes[i] & 0xff); + } + objectId = new UUID(msb, lsb); + } + else + { + objectId = null; + } + + List<QMFCommand> commands = new ArrayList<QMFCommand>(); + final long sampleTime = System.currentTimeMillis() * 1000000l; + + Collection<QMFPackage> packages; + + if(packageName != null && packageName.length() != 0) + { + QMFPackage qmfPackage = service.getPackage(packageName); + if(qmfPackage == null) + { + packages = Collections.EMPTY_LIST; + } + else + { + packages = Collections.singleton(qmfPackage); + } + } + else + { + packages = service.getSupportedSchemas(); + } + + for(QMFPackage qmfPackage : packages) + { + + Collection<QMFClass> qmfClasses; + + if(className != null && className.length() != 0) + { + QMFClass qmfClass = qmfPackage.getQMFClass(className); + if(qmfClass == null) + { + qmfClasses = Collections.EMPTY_LIST; + } + else + { + qmfClasses = Collections.singleton(qmfClass); + } + } + else + { + qmfClasses = qmfPackage.getClasses(); + } + + + for(QMFClass qmfClass : qmfClasses) + { + Collection<QMFObject> objects; + + if(objectId != null) + { + QMFObject obj = service.getObjectById(qmfClass, objectId); + if(obj == null) + { + objects = Collections.EMPTY_LIST; + } + else + { + objects = Collections.singleton(obj); + } + } + else + { + objects = service.getObjects(qmfClass); + } + + for(QMFObject object : objects) + { + + commands.add(object.asGetQueryResponseCmd(this, sampleTime)); + } + } + + + } + + + commands.add( new QMFCommandCompletionCommand(this)); + + + for(QMFCommand cmd : commands) + { + + + QMFMessage responseMessage = new QMFMessage(routingKey, cmd); + + Exchange exchange = virtualHost.getExchangeRegistry().getExchange(exchangeName); + + ArrayList<? extends BaseQueue> queues = exchange.route(responseMessage); + + for(BaseQueue q : queues) + { + try + { + q.enqueue(responseMessage); + } + catch (AMQException e) + { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + } + } + } + +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFMessage.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFMessage.java new file mode 100644 index 0000000000..895ff643a2 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFMessage.java @@ -0,0 +1,211 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.server.configuration.SessionConfig; +import org.apache.qpid.server.message.*; +import org.apache.qpid.transport.codec.BBEncoder; + +import java.nio.ByteBuffer; +import java.util.Set; + +public class QMFMessage implements ServerMessage, InboundMessage, AMQMessageHeader +{ + + private ByteBuffer _content; + private String _routingKey; + + public QMFMessage(String routingKey, QMFCommand command) + { + this(routingKey, new QMFCommand[] { command }); + } + + + public QMFMessage(String routingKey, QMFCommand[] commands) + { + _routingKey = routingKey; + BBEncoder encoder = new BBEncoder(256); + + for(QMFCommand cmd : commands) + { + cmd.encode(encoder); + } + + + _content = encoder.buffer(); + } + + public String getRoutingKey() + { + return _routingKey; + } + + public AMQMessageHeader getMessageHeader() + { + return this; + } + + public boolean isPersistent() + { + return false; + } + + public boolean isRedelivered() + { + return false; + } + + public long getSize() + { + return _content.limit(); + } + + public boolean isImmediate() + { + return false; + } + + public String getCorrelationId() + { + return null; + } + + public long getExpiration() + { + return 0; + } + + public String getMessageId() + { + return null; + } + + public String getMimeType() + { + return null; + } + + public String getEncoding() + { + return null; + } + + public byte getPriority() + { + return 4; + } + + public long getTimestamp() + { + return 0; + } + + public String getType() + { + return null; + } + + public String getReplyTo() + { + return null; + } + + public String getReplyToExchange() + { + return null; + } + + public String getReplyToRoutingKey() + { + return null; + } + + public Object getHeader(String name) + { + return null; + } + + public boolean containsHeaders(Set<String> names) + { + return false; + } + + public boolean containsHeader(String name) + { + return false; + } + + public MessageReference newReference() + { + return new QMFMessageReference(this); + } + + public Long getMessageNumber() + { + return null; + } + + public long getArrivalTime() + { + return 0; + } + + public int getContent(ByteBuffer buf, int offset) + { + ByteBuffer src = _content.duplicate(); + _content.position(offset); + _content = _content.slice(); + int len = _content.remaining(); + if(len > buf.remaining()) + { + len = buf.remaining(); + } + + buf.put(src); + + return len; + } + + private static class QMFMessageReference extends MessageReference<QMFMessage> + { + public QMFMessageReference(QMFMessage message) + { + super(message); + } + + protected void onReference(QMFMessage message) + { + + } + + protected void onRelease(QMFMessage message) + { + + } + } + + public SessionConfig getSessionConfig() + { + return null; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFMethod.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFMethod.java new file mode 100644 index 0000000000..63e8fa6a1e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFMethod.java @@ -0,0 +1,157 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.transport.codec.Encoder; +import org.apache.qpid.transport.codec.BBDecoder; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.ArrayList; + +public abstract class QMFMethod<T extends QMFObject> +{ + private final LinkedHashMap<String,Object> _map = new LinkedHashMap<String,Object>(); + private final List<Argument> _arguments = new ArrayList<Argument>(); + + private static final String NAME = "name"; + private static final String TYPE = "type"; + private static final String REF_PACKAGE = "refPackage"; + private static final String REF_CLASS = "refClass"; + private static final String UNIT = "unit"; + private static final String MIN = "min"; + private static final String MAX = "max"; + private static final String MAX_LENGTH = "maxlen"; + private static final String DESCRIPTION = "desc"; + private static final String DEFAULT = "default"; + private static final String DIRECTION = "dir"; + private static final String ARG_COUNT = "argCount"; + + + + public enum Direction + { + I, + O, + IO; + } + + public class Argument + { + private final LinkedHashMap<String,Object> _map = new LinkedHashMap<String,Object>(); + + public Argument(String name, QMFType type) + { + _map.put(NAME, name); + _map.put(TYPE, type.codeValue()); + } + + public void setRefPackage(String refPackage) + { + _map.put(REF_PACKAGE, refPackage); + } + + public void setRefClass(String refClass) + { + _map.put(REF_CLASS, refClass); + } + + public void setUnit(String unit) + { + _map.put(UNIT, unit); + } + + public void setMax(Number max) + { + _map.put(MAX, max); + } + + public void setMin(Number min) + { + _map.put(MIN, min); + } + + public void setMaxLength(int len) + { + _map.put(MAX_LENGTH, len); + } + + public void setDefault(Object dflt) + { + _map.put(DEFAULT, dflt); + } + + public void setDescription(String desc) + { + _map.put(DESCRIPTION, desc); + } + + public void setDirection(Direction direction) + { + _map.put(DIRECTION, direction.toString()); + } + + public void encode(Encoder encoder) + { + encoder.writeMap(_map); + } + + public String getName() + { + return (String) _map.get(NAME); + } + } + + public QMFMethod(String name, String description) + { + _map.put(NAME, name); + _map.put(ARG_COUNT, 0); + if(description != null) + { + _map.put(DESCRIPTION, description); + } + + } + + abstract public QMFMethodInvocation<T> parse(final BBDecoder decoder); + + protected void addArgument(Argument arg) + { + _arguments.add(arg); + _map.put(ARG_COUNT, _arguments.size()); + } + + + public void encode(Encoder encoder) + { + encoder.writeMap(_map); + for(Argument arg : _arguments) + { + arg.encode(encoder); + } + } + + public String getName() + { + return (String) _map.get(NAME); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFMethodInvocation.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFMethodInvocation.java new file mode 100644 index 0000000000..5348c2783f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFMethodInvocation.java @@ -0,0 +1,26 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +public interface QMFMethodInvocation<T extends QMFObject> +{ + QMFMethodResponseCommand execute(T obj, QMFMethodRequestCommand cmd); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFMethodRequestCommand.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFMethodRequestCommand.java new file mode 100644 index 0000000000..cf27e4b970 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFMethodRequestCommand.java @@ -0,0 +1,88 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.transport.codec.BBDecoder; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.AMQException; + +import java.util.UUID; +import java.util.ArrayList; + +public class QMFMethodRequestCommand extends QMFCommand +{ + private QMFMethodInvocation _methodInstance; + private QMFObject _object; + + public QMFMethodRequestCommand(final QMFCommandHeader header, final BBDecoder decoder, final QMFService qmfService) + { + super(header); + UUID objectId = decoder.readUuid(); + String packageName = decoder.readStr8(); + String className = decoder.readStr8(); + byte[] hash = decoder.readBin128(); + String methodName = decoder.readStr8(); + + QMFPackage qmfPackage = qmfService.getPackage(packageName); + QMFClass qmfClass = qmfPackage.getQMFClass(className); + _object = qmfService.getObjectById(qmfClass, objectId); + QMFMethod method = qmfClass.getMethod(methodName); + _methodInstance = method.parse(decoder); + + } + + public void process(final VirtualHost virtualHost, final ServerMessage message) + { + String exchangeName = message.getMessageHeader().getReplyToExchange(); + String queueName = message.getMessageHeader().getReplyToRoutingKey(); + + QMFCommand[] commands = new QMFCommand[2]; + commands[0] = _methodInstance.execute(_object, this); + commands[1] = new QMFCommandCompletionCommand(this); + + Exchange exchange = virtualHost.getExchangeRegistry().getExchange(exchangeName); + + for(QMFCommand cmd : commands) + { + QMFMessage responseMessage = new QMFMessage(queueName, cmd); + + + ArrayList<? extends BaseQueue> queues = exchange.route(responseMessage); + + + for(BaseQueue q : queues) + { + try + { + q.enqueue(responseMessage); + } + catch (AMQException e) + { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + } + } + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFMethodResponseCommand.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFMethodResponseCommand.java new file mode 100644 index 0000000000..5fea014ad8 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFMethodResponseCommand.java @@ -0,0 +1,76 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.transport.codec.BBEncoder; + +public class QMFMethodResponseCommand extends QMFCommand +{ + private CompletionCode _status = null; + private String _msg = null; + + public QMFMethodResponseCommand(final QMFMethodRequestCommand cmd, + CompletionCode status, + String msg) + { + super( new QMFCommandHeader(cmd.getHeader().getVersion(), + cmd.getHeader().getSeq(), + QMFOperation.METHOD_RESPONSE)); + + if(status == null) + { + _status = CompletionCode.OK; + } + else + { + _status = status; + } + + _msg = msg; + } + + public CompletionCode getStatus() + { + return _status; + } + + public String getStatusText() + { + return _msg; + } + + @Override + public void encode(final BBEncoder encoder) + { + super.encode(encoder); + + encoder.writeUint32(_status.ordinal()); + + if(_msg == null) + { + encoder.writeStr16(_status.toString()); + } + else + { + encoder.writeStr16(_msg); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFObject.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFObject.java new file mode 100644 index 0000000000..d126717fc8 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFObject.java @@ -0,0 +1,76 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import java.util.UUID; + +public abstract class QMFObject<C extends QMFClass, D extends QMFObject.Delegate> +{ + private long _deleteTime; + + public interface Delegate + { + UUID getId(); + long getCreateTime(); + } + + + private D _delegate; + + protected QMFObject(D delegate) + { + _delegate = delegate; + } + + public D getDelegate() + { + return _delegate; + } + + abstract public C getQMFClass(); + + public final UUID getId() + { + return _delegate.getId(); + } + + public final long getCreateTime() + { + return _delegate.getCreateTime(); + } + + public final void setDeleteTime() + { + _deleteTime = System.currentTimeMillis(); + } + + public final long getDeleteTime() + { + return _deleteTime; + } + + + + abstract public QMFCommand asConfigInfoCmd(long sampleTime); + abstract public QMFCommand asInstrumentInfoCmd(long sampleTime); + abstract public QMFCommand asGetQueryResponseCmd(final QMFGetQueryCommand queryCommand, long sampleTime); + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFObjectClass.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFObjectClass.java new file mode 100644 index 0000000000..fefdecb8d7 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFObjectClass.java @@ -0,0 +1,44 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import java.util.List; + +public abstract class QMFObjectClass<T extends QMFObject, S extends QMFObject.Delegate> extends QMFClass +{ + public QMFObjectClass(String name, + byte[] schemaHash, + List<QMFProperty> properties, + List<QMFStatistic> statistics, List<QMFMethod> methods) + { + super(QMFClass.Type.OBJECT, name, schemaHash, properties, statistics, methods); + } + + public QMFObjectClass(String name, byte[] schemaHash) + { + super(QMFClass.Type.OBJECT, name, schemaHash); + } + + + public abstract T newInstance(S delegate); + + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFOperation.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFOperation.java new file mode 100644 index 0000000000..6736b5d460 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFOperation.java @@ -0,0 +1,67 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +public enum QMFOperation +{ + + + BROKER_REQUEST('B'), + BROKER_RESPONSE('b'), // This message contains a broker response, sent from the broker in response to a broker request message. + COMMAND_COMPLETION('z'), // This message is sent to indicate the completion of a request. + CLASS_QUERY('Q'), // Class query messages are used by a management console to request a list of schema classes that are known by the management broker. + CLASS_INDICATION('q'), // Sent by the management broker, a class indication notifies the peer of the existence of a schema class. + SCHEMA_REQUEST('S'), // Schema request messages are used to request the full schema details for a class. + SCHEMA_RESPONSE('s'), // Schema response message contain a full description of the schema for a class. + HEARTBEAT_INDEICATION('h'), // This message is published once per publish-interval. It can be used by a client to positively determine which objects did not change during the interval (since updates are not published for objects with no changes). + CONFIG_INDICATION('c'), + INSTRUMENTATION_INDICATION('i'), + GET_QUERY_RESPONSE('g'), // This message contains a content record. Content records contain the values of all properties or statistics in an object. Such records are broadcast on a periodic interval if 1) a change has been made in the value of one of the elements, or 2) if a new management client has bound a queue to the management exchange. + GET_QUERY('G'), // Sent by a management console, a get query requests that the management broker provide content indications for all objects that match the query criteria. + METHOD_REQUEST('M'), // This message contains a method request. + METHOD_RESPONSE('m'), // This message contains a method result. + PACKAGE_QUERY('P'), // This message contains a schema package query request, requesting that the broker dump the list of known packages + PACKAGE_INDICATION('p'), // This message contains a schema package indication, identifying a package known by the broker + AGENT_ATTACH_REUQEST('A'), // This message is sent by a remote agent when it wishes to attach to a management broker + AGENT_ATTACH_RESPONSE('a'), // The management broker sends this response if an attaching remote agent is permitted to join + CONSOLE_ADDED_INDICATION('x'), // This message is sent to all remote agents by the management broker when a new console binds to the management exchange + EVENT('e') + ; + + + private final char _opcode; + + private static final QMFOperation[] OP_CODES = new QMFOperation[256]; + + + QMFOperation(char opcode) + { + _opcode = opcode; + } + + + public char getOpcode() + { + return _opcode; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFPackage.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFPackage.java new file mode 100644 index 0000000000..681e64b799 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFPackage.java @@ -0,0 +1,67 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import java.util.Collection; +import java.util.Map; +import java.util.HashMap; + +public class QMFPackage +{ + private final String _name; + private final Map<String, QMFClass> _classes = new HashMap<String, QMFClass>(); + + public QMFPackage(String name) + { + _name = name; + } + + public QMFPackage(String name, Collection<QMFClass> classes) + { + this(name); + setClasses(classes); + } + + protected void setClasses(Collection<QMFClass> classes) + { + for(QMFClass qmfClass : classes) + { + qmfClass.setPackage(this); + _classes.put(qmfClass.getName(), qmfClass); + } + } + + public String getName() + { + return _name; + } + + public Collection<QMFClass> getClasses() + { + return _classes.values(); + } + + public QMFClass getQMFClass(String className) + { + return _classes.get(className); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFPackageIndicationCommand.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFPackageIndicationCommand.java new file mode 100644 index 0000000000..7053b80655 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFPackageIndicationCommand.java @@ -0,0 +1,46 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.transport.codec.BBEncoder; + +public class QMFPackageIndicationCommand extends QMFCommand +{ + private String _supportedSchema; + + public QMFPackageIndicationCommand(QMFPackageQueryCommand qmfPackageQueryCommand, String supportedSchema) + { + super( new QMFCommandHeader(qmfPackageQueryCommand.getHeader().getVersion(), + qmfPackageQueryCommand.getHeader().getSeq(), + QMFOperation.PACKAGE_INDICATION)); + _supportedSchema = supportedSchema; + + } + + public void encode(BBEncoder encoder) + { + super.encode(encoder); + encoder.writeStr8(_supportedSchema); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFPackageQueryCommand.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFPackageQueryCommand.java new file mode 100644 index 0000000000..6defd088de --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFPackageQueryCommand.java @@ -0,0 +1,86 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.transport.codec.BBDecoder; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.AMQException; + +import java.util.ArrayList; +import java.util.Collection; + +public class QMFPackageQueryCommand extends QMFCommand +{ + public QMFPackageQueryCommand(QMFCommandHeader header, BBDecoder decoder) + { + super(header); + } + + public void process(VirtualHost virtualHost, ServerMessage message) + { + String exchangeName = message.getMessageHeader().getReplyToExchange(); + String routingKey = message.getMessageHeader().getReplyToRoutingKey(); + + + IApplicationRegistry appRegistry = virtualHost.getApplicationRegistry(); + QMFService service = appRegistry.getQMFService(); + + Collection<QMFPackage> supportedSchemas = service.getSupportedSchemas(); + + QMFCommand[] commands = new QMFCommand[ supportedSchemas.size() + 1 ]; + + int i = 0; + for(QMFPackage p : supportedSchemas) + { + commands[i++] = new QMFPackageIndicationCommand(this, p.getName()); + } + commands[ commands.length - 1 ] = new QMFCommandCompletionCommand(this); + + + for(QMFCommand cmd : commands) + { + + QMFMessage responseMessage = new QMFMessage(routingKey, cmd); + + Exchange exchange = virtualHost.getExchangeRegistry().getExchange(exchangeName); + + ArrayList<? extends BaseQueue> queues = exchange.route(responseMessage); + + + for(BaseQueue q : queues) + { + try + { + q.enqueue(responseMessage); + } + catch (AMQException e) + { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + } + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFProperty.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFProperty.java new file mode 100644 index 0000000000..5748722afe --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFProperty.java @@ -0,0 +1,123 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.transport.codec.Encoder; + +import java.util.Map; +import java.util.HashMap; +import java.util.LinkedHashMap; + +public class QMFProperty +{ + private final Map<String, Object> _map = new LinkedHashMap<String, Object>(); + private static final String NAME = "name"; + private static final String TYPE = "type"; + private static final String ACCESS = "access"; + private static final String INDEX = "index"; + private static final String OPTIONAL = "optional"; + private static final String REF_PACKAGE = "refPackage"; + private static final String REF_CLASS = "refClass"; + private static final String UNIT = "unit"; + private static final String MIN = "min"; + private static final String MAX = "max"; + private static final String MAX_LENGTH = "maxlen"; + private static final String DESCRIPTION = "desc"; + + + public static enum AccessCode + { + RC, + RW, + RO; + + public int codeValue() + { + return ordinal()+1; + } + } + + public QMFProperty(String name, QMFType type, AccessCode accessCode, boolean index, boolean optional) + { + _map.put(NAME, name); + _map.put(TYPE, type.codeValue()); + _map.put(ACCESS, accessCode.codeValue()); + _map.put(INDEX, index ? 1 : 0); + _map.put(OPTIONAL, optional ? 1 :0); + } + + + public void setQMFClass(QMFClass qmfClass) + { + /* _map.put(REF_CLASS, qmfClass.getName()); + _map.put(REF_PACKAGE, qmfClass.getPackage().getName());*/ + } + + public void setReferencedClass(String refClass) + { + _map.put(REF_CLASS, refClass); + } + + public void setReferencedPackage(String refPackage) + { + _map.put(REF_CLASS, refPackage); + } + + + public String getName() + { + return (String) _map.get(NAME); + } + + + public void setUnit(String unit) + { + _map.put(UNIT, unit); + } + + public void setMin(Number min) + { + _map.put(MIN, min); + } + + public void setMax(Number max) + { + _map.put(MAX, max); + } + + public void setMaxLength(int maxlen) + { + _map.put(MAX_LENGTH, maxlen); + } + + + public void setDescription(String description) + { + _map.put(DESCRIPTION, description); + } + + public void encode(Encoder encoder) + { + encoder.writeMap(_map); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFSchemaRequestCommand.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFSchemaRequestCommand.java new file mode 100644 index 0000000000..3141676f10 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFSchemaRequestCommand.java @@ -0,0 +1,88 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.transport.codec.BBDecoder; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.AMQException; + +import java.util.Collection; +import java.util.ArrayList; + +public class QMFSchemaRequestCommand extends QMFCommand +{ + private final String _packageName; + private final String _className; + private final byte[] _hash; + + public QMFSchemaRequestCommand(QMFCommandHeader header, BBDecoder decoder) + { + super(header); + _packageName = decoder.readStr8(); + _className = decoder.readStr8(); + _hash = decoder.readBin128(); + } + + public void process(VirtualHost virtualHost, ServerMessage message) + { + String exchangeName = message.getMessageHeader().getReplyToExchange(); + String routingKey = message.getMessageHeader().getReplyToRoutingKey(); + + IApplicationRegistry appRegistry = virtualHost.getApplicationRegistry(); + QMFService service = appRegistry.getQMFService(); + + QMFPackage qmfPackage = service.getPackage(_packageName); + QMFClass qmfClass = qmfPackage.getQMFClass( _className ); + + QMFCommand[] commands = new QMFCommand[2]; + commands[0] = new QMFSchemaResponseCommand(this, qmfClass); + commands[ 1 ] = new QMFCommandCompletionCommand(this); + + + + Exchange exchange = virtualHost.getExchangeRegistry().getExchange(exchangeName); + + for(QMFCommand cmd : commands) + { + QMFMessage responseMessage = new QMFMessage(routingKey, cmd); + + + ArrayList<? extends BaseQueue> queues = exchange.route(responseMessage); + + for(BaseQueue q : queues) + { + try + { + q.enqueue(responseMessage); + } + catch (AMQException e) + { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + } + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFSchemaResponseCommand.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFSchemaResponseCommand.java new file mode 100644 index 0000000000..fea2430130 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFSchemaResponseCommand.java @@ -0,0 +1,83 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.transport.codec.BBEncoder; + +import java.util.Collection; + +public class QMFSchemaResponseCommand extends QMFCommand +{ + private final QMFClass _qmfClass; + + + public QMFSchemaResponseCommand(QMFSchemaRequestCommand qmfSchemaRequestCommand, QMFClass qmfClass) + { + super(new QMFCommandHeader(qmfSchemaRequestCommand.getHeader().getVersion(), + qmfSchemaRequestCommand.getHeader().getSeq(), + QMFOperation.SCHEMA_RESPONSE)); + _qmfClass = qmfClass; + } + + @Override + public void encode(BBEncoder encoder) + { + super.encode(encoder); + encoder.writeUint8(_qmfClass.getType().getValue()); + encoder.writeStr8(_qmfClass.getPackage().getName()); + encoder.writeStr8(_qmfClass.getName()); + encoder.writeBin128(_qmfClass.getSchemaHash()); + + Collection<QMFProperty> props = _qmfClass.getProperties(); + Collection<QMFStatistic> stats = _qmfClass.getStatistics(); + Collection<QMFMethod> methods = _qmfClass.getMethods(); + + encoder.writeUint16(props.size()); + if(_qmfClass.getType() == QMFClass.Type.OBJECT) + { + encoder.writeUint16(stats.size()); + encoder.writeUint16(methods.size()); + } + + for(QMFProperty prop : props) + { + prop.encode(encoder); + } + + if(_qmfClass.getType() == QMFClass.Type.OBJECT) + { + + for(QMFStatistic stat : stats) + { + stat.encode(encoder); + } + + for(QMFMethod method : methods) + { + method.encode(encoder); + } + } + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFService.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFService.java new file mode 100644 index 0000000000..5192d5be6f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFService.java @@ -0,0 +1,1670 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.AMQException; +import org.apache.qpid.common.Closeable; +import org.apache.qpid.qmf.schema.BrokerSchema; +import org.apache.qpid.server.configuration.*; +import org.apache.qpid.server.registry.IApplicationRegistry; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +public class QMFService implements ConfigStore.ConfigEventListener, Closeable +{ + + + private IApplicationRegistry _applicationRegistry; + private ConfigStore _configStore; + + + private final Map<String, QMFPackage> _supportedSchemas = new HashMap<String, QMFPackage>(); + private static final Map<String, ConfigObjectType> _qmfClassMapping = new HashMap<String, ConfigObjectType>(); + + static + { + _qmfClassMapping.put("system", SystemConfigType.getInstance()); + _qmfClassMapping.put("broker", BrokerConfigType.getInstance()); + _qmfClassMapping.put("vhost", VirtualHostConfigType.getInstance()); + _qmfClassMapping.put("exchange", ExchangeConfigType.getInstance()); + _qmfClassMapping.put("queue", QueueConfigType.getInstance()); + _qmfClassMapping.put("binding", BindingConfigType.getInstance()); + _qmfClassMapping.put("connection", ConnectionConfigType.getInstance()); + _qmfClassMapping.put("session", SessionConfigType.getInstance()); + _qmfClassMapping.put("subscription", SubscriptionConfigType.getInstance()); + _qmfClassMapping.put("link", LinkConfigType.getInstance()); + _qmfClassMapping.put("bridge", BridgeConfigType.getInstance()); + } + + private final Map<ConfigObjectType, ConfigObjectAdapter> _adapterMap = + new HashMap<ConfigObjectType, ConfigObjectAdapter>(); + private final Map<ConfigObjectType,QMFClass> _classMap = + new HashMap<ConfigObjectType,QMFClass>(); + + + private final ConcurrentHashMap<QMFClass,ConcurrentHashMap<ConfiguredObject, QMFObject>> _managedObjects = + new ConcurrentHashMap<QMFClass,ConcurrentHashMap<ConfiguredObject, QMFObject>>(); + + private final ConcurrentHashMap<QMFClass,ConcurrentHashMap<UUID, QMFObject>> _managedObjectsById = + new ConcurrentHashMap<QMFClass,ConcurrentHashMap<UUID, QMFObject>>(); + + private static final BrokerSchema PACKAGE = BrokerSchema.getPackage(); + + public static interface Listener + { + public void objectCreated(QMFObject obj); + public void objectDeleted(QMFObject obj); + } + + private final CopyOnWriteArrayList<Listener> _listeners = new CopyOnWriteArrayList<Listener>(); + + abstract class ConfigObjectAdapter<Q extends QMFObject<S,D>, S extends QMFObjectClass<Q,D>, D extends QMFObject.Delegate, T extends ConfigObjectType<T,C>, C extends ConfiguredObject<T,C>> + { + private final T _type; + private final S _qmfClass; + + + protected ConfigObjectAdapter(final T type, final S qmfClass) + { + _type = type; + _qmfClass = qmfClass; + _adapterMap.put(type,this); + _classMap.put(type,qmfClass); + } + + public T getType() + { + return _type; + } + + public S getQMFClass() + { + return _qmfClass; + } + + protected final Q newInstance(D delegate) + { + return _qmfClass.newInstance(delegate); + } + + public abstract Q createQMFObject(C configObject); + } + + private ConfigObjectAdapter<BrokerSchema.SystemObject, + BrokerSchema.SystemClass, + BrokerSchema.SystemDelegate, + SystemConfigType, + SystemConfig> _systemObjectAdapter = + new ConfigObjectAdapter<BrokerSchema.SystemObject, + BrokerSchema.SystemClass, + BrokerSchema.SystemDelegate, + SystemConfigType, + SystemConfig>(SystemConfigType.getInstance(), + PACKAGE.getQMFClassInstance(BrokerSchema.SystemClass.class)) + { + + + public BrokerSchema.SystemObject createQMFObject( + final SystemConfig configObject) + { + return newInstance(new SystemDelegate(configObject)); + } + }; + + private ConfigObjectAdapter<BrokerSchema.BrokerObject, + BrokerSchema.BrokerClass, + BrokerSchema.BrokerDelegate, + BrokerConfigType, + BrokerConfig> _brokerObjectAdapter = + new ConfigObjectAdapter<BrokerSchema.BrokerObject, + BrokerSchema.BrokerClass, + BrokerSchema.BrokerDelegate, + BrokerConfigType, + BrokerConfig>(BrokerConfigType.getInstance(), + PACKAGE.getQMFClassInstance(BrokerSchema.BrokerClass.class)) + { + + public BrokerSchema.BrokerObject createQMFObject( + final BrokerConfig configObject) + { + return newInstance(new BrokerDelegate(configObject)); + } + }; + + private ConfigObjectAdapter<BrokerSchema.VhostObject, + BrokerSchema.VhostClass, + BrokerSchema.VhostDelegate, + VirtualHostConfigType, + VirtualHostConfig> _vhostObjectAdapter = + new ConfigObjectAdapter<BrokerSchema.VhostObject, + BrokerSchema.VhostClass, + BrokerSchema.VhostDelegate, + VirtualHostConfigType, + VirtualHostConfig>(VirtualHostConfigType.getInstance(), + PACKAGE.getQMFClassInstance(BrokerSchema.VhostClass.class)) + { + + public BrokerSchema.VhostObject createQMFObject( + final VirtualHostConfig configObject) + { + return newInstance(new VhostDelegate(configObject)); + } + }; + + + private ConfigObjectAdapter<BrokerSchema.ExchangeObject, + BrokerSchema.ExchangeClass, + BrokerSchema.ExchangeDelegate, + ExchangeConfigType, + ExchangeConfig> _exchangeObjectAdapter = + new ConfigObjectAdapter<BrokerSchema.ExchangeObject, + BrokerSchema.ExchangeClass, + BrokerSchema.ExchangeDelegate, + ExchangeConfigType, + ExchangeConfig>(ExchangeConfigType.getInstance(), + PACKAGE.getQMFClassInstance(BrokerSchema.ExchangeClass.class)) + { + + public BrokerSchema.ExchangeObject createQMFObject( + final ExchangeConfig configObject) + { + return newInstance(new ExchangeDelegate(configObject)); + } + }; + + + private ConfigObjectAdapter<BrokerSchema.QueueObject, + BrokerSchema.QueueClass, + BrokerSchema.QueueDelegate, + QueueConfigType, + QueueConfig> _queueObjectAdapter = + new ConfigObjectAdapter<BrokerSchema.QueueObject, + BrokerSchema.QueueClass, + BrokerSchema.QueueDelegate, + QueueConfigType, + QueueConfig>(QueueConfigType.getInstance(), + PACKAGE.getQMFClassInstance(BrokerSchema.QueueClass.class)) + { + + public BrokerSchema.QueueObject createQMFObject( + final QueueConfig configObject) + { + return newInstance(new QueueDelegate(configObject)); + } + }; + + + private ConfigObjectAdapter<BrokerSchema.BindingObject, + BrokerSchema.BindingClass, + BrokerSchema.BindingDelegate, + BindingConfigType, + BindingConfig> _bindingObjectAdapter = + new ConfigObjectAdapter<BrokerSchema.BindingObject, + BrokerSchema.BindingClass, + BrokerSchema.BindingDelegate, + BindingConfigType, + BindingConfig>(BindingConfigType.getInstance(), + PACKAGE.getQMFClassInstance(BrokerSchema.BindingClass.class)) + { + + public BrokerSchema.BindingObject createQMFObject( + final BindingConfig configObject) + { + return newInstance(new BindingDelegate(configObject)); + } + }; + + + private ConfigObjectAdapter<BrokerSchema.ConnectionObject, + BrokerSchema.ConnectionClass, + BrokerSchema.ConnectionDelegate, + ConnectionConfigType, + ConnectionConfig> _connectionObjectAdapter = + new ConfigObjectAdapter<BrokerSchema.ConnectionObject, + BrokerSchema.ConnectionClass, + BrokerSchema.ConnectionDelegate, + ConnectionConfigType, + ConnectionConfig>(ConnectionConfigType.getInstance(), + PACKAGE.getQMFClassInstance(BrokerSchema.ConnectionClass.class)) + { + + public BrokerSchema.ConnectionObject createQMFObject( + final ConnectionConfig configObject) + { + return newInstance(new ConnectionDelegate(configObject)); + } + }; + + + private ConfigObjectAdapter<BrokerSchema.SessionObject, + BrokerSchema.SessionClass, + BrokerSchema.SessionDelegate, + SessionConfigType, + SessionConfig> _sessionObjectAdapter = + new ConfigObjectAdapter<BrokerSchema.SessionObject, + BrokerSchema.SessionClass, + BrokerSchema.SessionDelegate, + SessionConfigType, + SessionConfig>(SessionConfigType.getInstance(), + PACKAGE.getQMFClassInstance(BrokerSchema.SessionClass.class)) + { + + public BrokerSchema.SessionObject createQMFObject( + final SessionConfig configObject) + { + return newInstance(new SessionDelegate(configObject)); + } + }; + + private ConfigObjectAdapter<BrokerSchema.SubscriptionObject, + BrokerSchema.SubscriptionClass, + BrokerSchema.SubscriptionDelegate, + SubscriptionConfigType, + SubscriptionConfig> _subscriptionObjectAdapter = + new ConfigObjectAdapter<BrokerSchema.SubscriptionObject, + BrokerSchema.SubscriptionClass, + BrokerSchema.SubscriptionDelegate, + SubscriptionConfigType, + SubscriptionConfig>(SubscriptionConfigType.getInstance(), + PACKAGE.getQMFClassInstance(BrokerSchema.SubscriptionClass.class)) + { + + public BrokerSchema.SubscriptionObject createQMFObject( + final SubscriptionConfig configObject) + { + return newInstance(new SubscriptionDelegate(configObject)); + } + }; + + + private ConfigObjectAdapter<BrokerSchema.LinkObject, + BrokerSchema.LinkClass, + BrokerSchema.LinkDelegate, + LinkConfigType, + LinkConfig> _linkObjectAdapter = + new ConfigObjectAdapter<BrokerSchema.LinkObject, + BrokerSchema.LinkClass, + BrokerSchema.LinkDelegate, + LinkConfigType, + LinkConfig>(LinkConfigType.getInstance(), + PACKAGE.getQMFClassInstance(BrokerSchema.LinkClass.class)) + { + + public BrokerSchema.LinkObject createQMFObject( + final LinkConfig configObject) + { + return newInstance(new LinkDelegate(configObject)); + } + }; + + + private ConfigObjectAdapter<BrokerSchema.BridgeObject, + BrokerSchema.BridgeClass, + BrokerSchema.BridgeDelegate, + BridgeConfigType, + BridgeConfig> _bridgeObjectAdapter = + new ConfigObjectAdapter<BrokerSchema.BridgeObject, + BrokerSchema.BridgeClass, + BrokerSchema.BridgeDelegate, + BridgeConfigType, + BridgeConfig>(BridgeConfigType.getInstance(), + PACKAGE.getQMFClassInstance(BrokerSchema.BridgeClass.class)) + { + + public BrokerSchema.BridgeObject createQMFObject( + final BridgeConfig configObject) + { + return newInstance(new BridgeDelegate(configObject)); + } + }; + + + + public QMFService(ConfigStore configStore, IApplicationRegistry applicationRegistry) + { + _configStore = configStore; + _applicationRegistry = applicationRegistry; + registerSchema(PACKAGE); + + for(ConfigObjectType v : _qmfClassMapping.values()) + { + configStore.addConfigEventListener(v, this); + } + init(); + } + + public void close() + { + for(ConfigObjectType v : _qmfClassMapping.values()) + { + _configStore.removeConfigEventListener(v, this); + } + _listeners.clear(); + + _managedObjects.clear(); + _managedObjectsById.clear(); + _classMap.clear(); + _adapterMap.clear(); + _supportedSchemas.clear(); + } + + + public void registerSchema(QMFPackage qmfPackage) + { + _supportedSchemas.put(qmfPackage.getName(), qmfPackage); + } + + + public Collection<QMFPackage> getSupportedSchemas() + { + return _supportedSchemas.values(); + } + + public QMFPackage getPackage(String aPackage) + { + return _supportedSchemas.get(aPackage); + } + + public void onEvent(final ConfiguredObject object, final ConfigStore.Event evt) + { + + switch (evt) + { + case CREATED: + manageObject(object); + break; + + case DELETED: + unmanageObject(object); + break; + } + } + + public QMFObject getObjectById(QMFClass qmfclass, UUID id) + { + ConcurrentHashMap<UUID, QMFObject> map = _managedObjectsById.get(qmfclass); + if(map != null) + { + return map.get(id); + } + else + { + return null; + } + } + + private void unmanageObject(final ConfiguredObject object) + { + final QMFClass qmfClass = _classMap.get(object.getConfigType()); + + if(qmfClass == null) + { + return; + } + + ConcurrentHashMap<ConfiguredObject, QMFObject> classObjects = _managedObjects.get(qmfClass); + if(classObjects != null) + { + QMFObject qmfObject = classObjects.remove(object); + if(qmfObject != null) + { + _managedObjectsById.get(qmfClass).remove(object.getId()); + objectRemoved(qmfObject); + } + } + } + + private void manageObject(final ConfiguredObject object) + { + ConfigObjectAdapter adapter = _adapterMap.get(object.getConfigType()); + QMFObject qmfObject = adapter.createQMFObject(object); + final QMFClass qmfClass = qmfObject.getQMFClass(); + ConcurrentHashMap<ConfiguredObject, QMFObject> classObjects = _managedObjects.get(qmfClass); + + if(classObjects == null) + { + classObjects = new ConcurrentHashMap<ConfiguredObject, QMFObject>(); + if(_managedObjects.putIfAbsent(qmfClass, classObjects) != null) + { + classObjects = _managedObjects.get(qmfClass); + } + } + + ConcurrentHashMap<UUID, QMFObject> classObjectsById = _managedObjectsById.get(qmfClass); + if(classObjectsById == null) + { + classObjectsById = new ConcurrentHashMap<UUID, QMFObject>(); + if(_managedObjectsById.putIfAbsent(qmfClass, classObjectsById) != null) + { + classObjectsById = _managedObjectsById.get(qmfClass); + } + } + + classObjectsById.put(object.getId(),qmfObject); + + if(classObjects.putIfAbsent(object, qmfObject) == null) + { + objectAdded(qmfObject); + } + } + + private void objectRemoved(final QMFObject qmfObject) + { + qmfObject.setDeleteTime(); + for(Listener l : _listeners) + { + l.objectDeleted(qmfObject); + } + } + + private void objectAdded(final QMFObject qmfObject) + { + for(Listener l : _listeners) + { + l.objectCreated(qmfObject); + } + } + + public void addListener(Listener l) + { + _listeners.add(l); + } + + public void removeListener(Listener l) + { + _listeners.remove(l); + } + + + private void init() + { + for(QMFClass qmfClass : PACKAGE.getClasses()) + { + ConfigObjectType configType = getConfigType(qmfClass); + if(configType != null) + { + Collection<ConfiguredObject> objects = _configStore.getConfiguredObjects(configType); + for(ConfiguredObject object : objects) + { + manageObject(object); + } + } + } + } + + public Collection<QMFObject> getObjects(QMFClass qmfClass) + { + ConcurrentHashMap<ConfiguredObject, QMFObject> classObjects = _managedObjects.get(qmfClass); + if(classObjects != null) + { + return classObjects.values(); + } + else + { + return Collections.EMPTY_SET; + } + } + + private QMFObject adapt(final ConfiguredObject object) + { + if(object == null) + { + return null; + } + + QMFClass qmfClass = _classMap.get(object.getConfigType()); + ConcurrentHashMap<ConfiguredObject, QMFObject> classObjects = _managedObjects.get(qmfClass); + if(classObjects != null) + { + QMFObject qmfObject = classObjects.get(object); + if(qmfObject != null) + { + return qmfObject; + } + } + + return _adapterMap.get(object.getConfigType()).createQMFObject(object); + } + + private ConfigObjectType getConfigType(final QMFClass qmfClass) + { + return _qmfClassMapping.get(qmfClass.getName()); + } + + private static class SystemDelegate implements BrokerSchema.SystemDelegate + { + private final SystemConfig _obj; + + public SystemDelegate(final SystemConfig obj) + { + _obj = obj; + } + + public UUID getSystemId() + { + return _obj.getId(); + } + + public String getOsName() + { + return _obj.getOperatingSystemName(); + } + + public String getNodeName() + { + return _obj.getNodeName(); + } + + public String getRelease() + { + return _obj.getOSRelease(); + } + + public String getVersion() + { + return _obj.getOSVersion(); + } + + public String getMachine() + { + return _obj.getOSArchitecture(); + } + + public UUID getId() + { + return _obj.getId(); + } + + public long getCreateTime() + { + return _obj.getCreateTime(); + } + } + + private class BrokerDelegate implements BrokerSchema.BrokerDelegate + { + private final BrokerConfig _obj; + + public BrokerDelegate(final BrokerConfig obj) + { + _obj = obj; + } + + public BrokerSchema.SystemObject getSystemRef() + { + return (BrokerSchema.SystemObject) adapt(_obj.getSystem()); + } + + public String getName() + { + return "amqp-broker"; + } + + public Integer getPort() + { + return _obj.getPort(); + } + + public Integer getWorkerThreads() + { + return _obj.getWorkerThreads(); + } + + public Integer getMaxConns() + { + return _obj.getMaxConnections(); + } + + public Integer getConnBacklog() + { + return _obj.getConnectionBacklogLimit(); + } + + public Long getStagingThreshold() + { + return _obj.getStagingThreshold(); + } + + public Integer getMgmtPubInterval() + { + return _obj.getManagementPublishInterval(); + } + + public String getVersion() + { + return _obj.getVersion(); + } + + public String getDataDir() + { + return _obj.getDataDirectory(); + } + + public Long getUptime() + { + return (System.currentTimeMillis() - _obj.getCreateTime()) * 1000000L; + } + + public BrokerSchema.BrokerClass.EchoMethodResponseCommand echo(final BrokerSchema.BrokerClass.EchoMethodResponseCommandFactory factory, + final Long sequence, + final String body) + { + return factory.createResponseCommand(sequence, body); + } + + public BrokerSchema.BrokerClass.ConnectMethodResponseCommand connect(final BrokerSchema.BrokerClass.ConnectMethodResponseCommandFactory factory, + final String host, + final Long port, + final Boolean durable, + final String authMechanism, + final String username, + final String password, + final String transport) + { + _obj.createBrokerConnection(transport, host, port.intValue(), durable, authMechanism, username, password); + + return factory.createResponseCommand(); + } + + public BrokerSchema.BrokerClass.QueueMoveMessagesMethodResponseCommand queueMoveMessages(final BrokerSchema.BrokerClass.QueueMoveMessagesMethodResponseCommandFactory factory, + final String srcQueue, + final String destQueue, + final Long qty) + { + // TODO + return factory.createResponseCommand(CompletionCode.NOT_IMPLEMENTED); + } + + public BrokerSchema.BrokerClass.GetLogLevelMethodResponseCommand getLogLevel(final BrokerSchema.BrokerClass.GetLogLevelMethodResponseCommandFactory factory) + { + // TODO: The Java broker has numerous loggers, so we can't really implement this method properly. + return factory.createResponseCommand(CompletionCode.NOT_IMPLEMENTED); + } + + public BrokerSchema.BrokerClass.SetLogLevelMethodResponseCommand setLogLevel(final BrokerSchema.BrokerClass.SetLogLevelMethodResponseCommandFactory factory, String level) + { + // TODO: The Java broker has numerous loggers, so we can't really implement this method properly. + return factory.createResponseCommand(CompletionCode.NOT_IMPLEMENTED); + } + + public BrokerSchema.BrokerClass.CreateMethodResponseCommand create(final BrokerSchema.BrokerClass.CreateMethodResponseCommandFactory factory, + final String type, + final String name, + final Map properties, + final java.lang.Boolean lenient) + { + //TODO: + return factory.createResponseCommand(CompletionCode.NOT_IMPLEMENTED); + } + + public BrokerSchema.BrokerClass.DeleteMethodResponseCommand delete(final BrokerSchema.BrokerClass.DeleteMethodResponseCommandFactory factory, + final String type, + final String name, + final Map options) + { + //TODO: + return factory.createResponseCommand(CompletionCode.NOT_IMPLEMENTED); + } + + public UUID getId() + { + return _obj.getId(); + } + + public long getCreateTime() + { + return _obj.getCreateTime(); + } + } + + private class VhostDelegate implements BrokerSchema.VhostDelegate + { + private final VirtualHostConfig _obj; + + public VhostDelegate(final VirtualHostConfig obj) + { + _obj = obj; + } + + public BrokerSchema.BrokerObject getBrokerRef() + { + return (BrokerSchema.BrokerObject) adapt(_obj.getBroker()); + } + + public String getName() + { + return _obj.getName(); + } + + public String getFederationTag() + { + return _obj.getFederationTag(); + } + + public UUID getId() + { + return _obj.getId(); + } + + public long getCreateTime() + { + return _obj.getCreateTime(); + } + } + + private class ExchangeDelegate implements BrokerSchema.ExchangeDelegate + { + private final ExchangeConfig _obj; + + public ExchangeDelegate(final ExchangeConfig obj) + { + _obj = obj; + } + + public BrokerSchema.VhostObject getVhostRef() + { + return (BrokerSchema.VhostObject) adapt(_obj.getVirtualHost()); + } + + public String getName() + { + return _obj.getName(); + } + + public String getType() + { + return _obj.getType().getName().toString(); + } + + public Boolean getDurable() + { + return _obj.isDurable(); + } + + public Boolean getAutoDelete() + { + return _obj.isAutoDelete(); + } + + public BrokerSchema.ExchangeObject getAltExchange() + { + if(_obj.getAlternateExchange() != null) + { + return (BrokerSchema.ExchangeObject) adapt(_obj.getAlternateExchange()); + } + else + { + return null; + } + } + + public Map getArguments() + { + return _obj.getArguments(); + } + + public Long getProducerCount() + { + // TODO + return 0l; + } + + public Long getProducerCountHigh() + { + // TODO + return 0l; + } + + public Long getProducerCountLow() + { + // TODO + return 0l; + } + + public Long getBindingCount() + { + return _obj.getBindingCount(); + } + + public Long getBindingCountHigh() + { + return _obj.getBindingCountHigh(); + } + + public Long getBindingCountLow() + { + // TODO + return 0l; + } + + public Long getMsgReceives() + { + return _obj.getMsgReceives(); + } + + public Long getMsgDrops() + { + return getMsgReceives() - getMsgRoutes(); + } + + public Long getMsgRoutes() + { + return _obj.getMsgRoutes(); + } + + public Long getByteReceives() + { + return _obj.getByteReceives(); + } + + public Long getByteDrops() + { + return getByteReceives() - getByteRoutes(); + } + + public Long getByteRoutes() + { + return _obj.getByteRoutes(); + } + + public UUID getId() + { + return _obj.getId(); + } + + public long getCreateTime() + { + return _obj.getCreateTime(); + } + } + + private class QueueDelegate implements BrokerSchema.QueueDelegate + { + private final QueueConfig _obj; + + public QueueDelegate(final QueueConfig obj) + { + _obj = obj; + } + + public BrokerSchema.VhostObject getVhostRef() + { + return (BrokerSchema.VhostObject) adapt(_obj.getVirtualHost()); + } + + public String getName() + { + return _obj.getName(); + } + + public Boolean getDurable() + { + return _obj.isDurable(); + } + + public Boolean getAutoDelete() + { + return _obj.isAutoDelete(); + } + + public Boolean getExclusive() + { + return _obj.isExclusive(); + } + + public BrokerSchema.ExchangeObject getAltExchange() + { + if(_obj.getAlternateExchange() != null) + { + return (BrokerSchema.ExchangeObject) adapt(_obj.getAlternateExchange()); + } + else + { + return null; + } + } + + public Long getMsgTotalEnqueues() + { + return _obj.getReceivedMessageCount(); + } + + public Long getMsgTotalDequeues() + { + return _obj.getMessageDequeueCount(); + } + + public Long getMsgTxnEnqueues() + { + return _obj.getMsgTxnEnqueues(); + } + + public Long getMsgTxnDequeues() + { + return _obj.getMsgTxnDequeues(); + } + + public Long getMsgPersistEnqueues() + { + return _obj.getPersistentMsgEnqueues(); + } + + public Long getMsgPersistDequeues() + { + return _obj.getPersistentMsgDequeues(); + } + + public Long getMsgDepth() + { + return (long) _obj.getMessageCount(); + } + + public Long getByteDepth() + { + return _obj.getQueueDepth(); + } + + public Long getByteTotalEnqueues() + { + return _obj.getTotalEnqueueSize(); + } + + public Long getByteTotalDequeues() + { + return _obj.getTotalDequeueSize(); + } + + public Long getByteTxnEnqueues() + { + return _obj.getByteTxnEnqueues(); + } + + public Long getByteTxnDequeues() + { + return _obj.getByteTxnDequeues(); + } + + public Long getBytePersistEnqueues() + { + return _obj.getPersistentByteEnqueues(); + } + + public Long getBytePersistDequeues() + { + return _obj.getPersistentByteDequeues(); + } + + public Long getConsumerCount() + { + return (long) _obj.getConsumerCount(); + } + + public Long getConsumerCountHigh() + { + return (long) _obj.getConsumerCountHigh(); + } + + public Long getConsumerCountLow() + { + // TODO + return 0l; + } + + public Long getBindingCount() + { + return (long) _obj.getBindingCount(); + } + + public Long getBindingCountHigh() + { + return (long) _obj.getBindingCountHigh(); + } + + public Long getBindingCountLow() + { + // TODO + return 0l; + } + + public Long getUnackedMessages() + { + return _obj.getUnackedMessageCount(); + } + + public Long getUnackedMessagesHigh() + { + return _obj.getUnackedMessageCountHigh(); + } + + public Long getUnackedMessagesLow() + { + // TODO + return 0l; + } + + public Long getMessageLatencySamples() + { + // TODO + return 0l; + } + + public Long getMessageLatencyMin() + { + // TODO + return 0l; + } + + public Long getMessageLatencyMax() + { + // TODO + return 0l; + } + + public Long getMessageLatencyAverage() + { + // TODO + return 0l; + } + + public Boolean getFlowStopped() + { + return Boolean.FALSE; + } + + public Long getFlowStoppedCount() + { + return 0L; + } + + public BrokerSchema.QueueClass.PurgeMethodResponseCommand purge(final BrokerSchema.QueueClass.PurgeMethodResponseCommandFactory factory, + final Long request) + { + try + { + _obj.purge(request); + } catch (AMQException e) + { + // TODO + throw new RuntimeException(); + } + return factory.createResponseCommand(); + } + + public BrokerSchema.QueueClass.RerouteMethodResponseCommand reroute(final BrokerSchema.QueueClass.RerouteMethodResponseCommandFactory factory, + final Long request, + final Boolean useAltExchange, + final String exchange) + { + //TODO + return factory.createResponseCommand(CompletionCode.NOT_IMPLEMENTED); + } + + + public Map getArguments() + { + return _obj.getArguments(); + } + + public UUID getId() + { + return _obj.getId(); + } + + public long getCreateTime() + { + return _obj.getCreateTime(); + } + } + + private class BindingDelegate implements BrokerSchema.BindingDelegate + { + private final BindingConfig _obj; + + public BindingDelegate(final BindingConfig obj) + { + _obj = obj; + } + + public BrokerSchema.ExchangeObject getExchangeRef() + { + return (BrokerSchema.ExchangeObject) adapt(_obj.getExchange()); + } + + public BrokerSchema.QueueObject getQueueRef() + { + return (BrokerSchema.QueueObject) adapt(_obj.getQueue()); + } + + public String getBindingKey() + { + return _obj.getBindingKey(); + } + + public Map getArguments() + { + return _obj.getArguments(); + } + + public String getOrigin() + { + return _obj.getOrigin(); + } + + public Long getMsgMatched() + { + // TODO + return _obj.getMatches(); + } + + public UUID getId() + { + return _obj.getId(); + } + + public long getCreateTime() + { + return _obj.getCreateTime(); + } + } + + private class ConnectionDelegate implements BrokerSchema.ConnectionDelegate + { + private final ConnectionConfig _obj; + + public ConnectionDelegate(final ConnectionConfig obj) + { + _obj = obj; + } + + public BrokerSchema.VhostObject getVhostRef() + { + return (BrokerSchema.VhostObject) adapt(_obj.getVirtualHost()); + } + + public String getAddress() + { + return _obj.getAddress(); + } + + public Boolean getIncoming() + { + return _obj.isIncoming(); + } + + public Boolean getSystemConnection() + { + return _obj.isSystemConnection(); + } + + public Boolean getFederationLink() + { + return _obj.isFederationLink(); + } + + public String getAuthIdentity() + { + return _obj.getAuthId(); + } + + public String getRemoteProcessName() + { + return _obj.getRemoteProcessName(); + } + + public Long getRemotePid() + { + Integer remotePID = _obj.getRemotePID(); + return remotePID == null ? null : (long) remotePID; + } + + public Long getRemoteParentPid() + { + Integer remotePPID = _obj.getRemoteParentPID(); + return remotePPID == null ? null : (long) remotePPID; + + } + + public Boolean getClosing() + { + return false; + } + + public Long getFramesFromClient() + { + // TODO + return 0l; + } + + public Long getFramesToClient() + { + // TODO + return 0l; + } + + public Long getBytesFromClient() + { + // TODO + return 0l; + } + + public Long getBytesToClient() + { + // TODO + return 0l; + } + + public Long getMsgsFromClient() + { + // TODO + return 0l; + } + + public Long getMsgsToClient() + { + // TODO + return 0l; + } + + public BrokerSchema.ConnectionClass.CloseMethodResponseCommand close(final BrokerSchema.ConnectionClass.CloseMethodResponseCommandFactory factory) + { + _obj.mgmtClose(); + + return factory.createResponseCommand(); + } + + public UUID getId() + { + return _obj.getId(); + } + + public long getCreateTime() + { + return _obj.getCreateTime(); + } + + public Boolean getShadow() + { + return _obj.isShadow(); + } + } + + private class SessionDelegate implements BrokerSchema.SessionDelegate + { + private final SessionConfig _obj; + + public SessionDelegate(final SessionConfig obj) + { + _obj = obj; + } + + public BrokerSchema.VhostObject getVhostRef() + { + return (BrokerSchema.VhostObject) adapt(_obj.getVirtualHost()); + } + + public String getName() + { + return _obj.getSessionName(); + } + + public Integer getChannelId() + { + return _obj.getChannel(); + } + + public BrokerSchema.ConnectionObject getConnectionRef() + { + return (BrokerSchema.ConnectionObject) adapt(_obj.getConnectionConfig()); + } + + public Long getDetachedLifespan() + { + return _obj.getDetachedLifespan(); + } + + public Boolean getAttached() + { + return _obj.isAttached(); + } + + public Long getExpireTime() + { + return _obj.getExpiryTime(); + } + + public Long getMaxClientRate() + { + return _obj.getMaxClientRate(); + } + + public Long getFramesOutstanding() + { + // TODO + return 0l; + } + + public Long getTxnStarts() + { + return _obj.getTxnStarts(); + } + + public Long getTxnCommits() + { + return _obj.getTxnCommits(); + } + + public Long getTxnRejects() + { + return _obj.getTxnRejects(); + } + + public Long getTxnCount() + { + return _obj.getTxnCount(); + } + + public Long getClientCredit() + { + // TODO + return 0l; + } + + public BrokerSchema.SessionClass.SolicitAckMethodResponseCommand solicitAck(final BrokerSchema.SessionClass.SolicitAckMethodResponseCommandFactory factory) + { + //TODO + return factory.createResponseCommand(CompletionCode.NOT_IMPLEMENTED); + } + + public BrokerSchema.SessionClass.DetachMethodResponseCommand detach(final BrokerSchema.SessionClass.DetachMethodResponseCommandFactory factory) + { + //TODO + return factory.createResponseCommand(CompletionCode.NOT_IMPLEMENTED); + } + + public BrokerSchema.SessionClass.ResetLifespanMethodResponseCommand resetLifespan(final BrokerSchema.SessionClass.ResetLifespanMethodResponseCommandFactory factory) + { + //TODO + return factory.createResponseCommand(CompletionCode.NOT_IMPLEMENTED); + } + + public BrokerSchema.SessionClass.CloseMethodResponseCommand close(final BrokerSchema.SessionClass.CloseMethodResponseCommandFactory factory) + { + try + { + _obj.mgmtClose(); + } + catch (AMQException e) + { + return factory.createResponseCommand(CompletionCode.EXCEPTION, e.getMessage()); + } + + return factory.createResponseCommand(); + } + + public UUID getId() + { + return _obj.getId(); + } + + public long getCreateTime() + { + return _obj.getCreateTime(); + } + } + + private class SubscriptionDelegate implements BrokerSchema.SubscriptionDelegate + { + private final SubscriptionConfig _obj; + + private SubscriptionDelegate(final SubscriptionConfig obj) + { + _obj = obj; + } + + + public BrokerSchema.SessionObject getSessionRef() + { + return (BrokerSchema.SessionObject) adapt(_obj.getSessionConfig()); + } + + public BrokerSchema.QueueObject getQueueRef() + { + return (BrokerSchema.QueueObject) adapt(_obj.getQueue()); + } + + public String getName() + { + return _obj.getName(); + } + + public Boolean getBrowsing() + { + return _obj.isBrowsing(); + } + + public Boolean getAcknowledged() + { + return _obj.isExplicitAcknowledge(); + } + + public Boolean getExclusive() + { + return _obj.isExclusive(); + } + + public String getCreditMode() + { + return _obj.getCreditMode(); + } + + public Map getArguments() + { + return _obj.getArguments(); + } + + public Long getDelivered() + { + return _obj.getDelivered(); + } + + public UUID getId() + { + return _obj.getId(); + } + + public long getCreateTime() + { + return _obj.getCreateTime(); + } + } + + private class BridgeDelegate implements BrokerSchema.BridgeDelegate + { + private final BridgeConfig _obj; + + private BridgeDelegate(final BridgeConfig obj) + { + _obj = obj; + } + + public BrokerSchema.LinkObject getLinkRef() + { + return (BrokerSchema.LinkObject) adapt(_obj.getLink()); + } + + public Integer getChannelId() + { + return _obj.getChannelId(); + } + + public Boolean getDurable() + { + return _obj.isDurable(); + } + + public String getSrc() + { + return _obj.getSource(); + } + + public String getDest() + { + return _obj.getDestination(); + } + + public String getKey() + { + return _obj.getKey(); + } + + public Boolean getSrcIsQueue() + { + return _obj.isQueueBridge(); + } + + public Boolean getSrcIsLocal() + { + return _obj.isLocalSource(); + } + + public String getTag() + { + return _obj.getTag(); + } + + public String getExcludes() + { + return _obj.getExcludes(); + } + + public Boolean getDynamic() + { + return _obj.isDynamic(); + } + + public Integer getSync() + { + return _obj.getAckBatching(); + } + + public BrokerSchema.BridgeClass.CloseMethodResponseCommand close(final BrokerSchema.BridgeClass.CloseMethodResponseCommandFactory factory) + { + return null; + } + + public UUID getId() + { + return _obj.getId(); + } + + public long getCreateTime() + { + return _obj.getCreateTime(); + } + } + + private class LinkDelegate implements BrokerSchema.LinkDelegate + { + private final LinkConfig _obj; + + private LinkDelegate(final LinkConfig obj) + { + _obj = obj; + } + + public BrokerSchema.VhostObject getVhostRef() + { + return (BrokerSchema.VhostObject) adapt(_obj.getVirtualHost()); + } + + public String getHost() + { + return _obj.getHost(); + } + + public Integer getPort() + { + return _obj.getPort(); + } + + public String getTransport() + { + return _obj.getTransport(); + } + + public Boolean getDurable() + { + return _obj.isDurable(); + } + + public String getState() + { + // TODO + return ""; + } + + public String getLastError() + { + // TODO + return ""; + } + + public BrokerSchema.LinkClass.CloseMethodResponseCommand close(final BrokerSchema.LinkClass.CloseMethodResponseCommandFactory factory) + { + _obj.close(); + return factory.createResponseCommand(); + } + + public BrokerSchema.LinkClass.BridgeMethodResponseCommand bridge(final BrokerSchema.LinkClass.BridgeMethodResponseCommandFactory factory, + final Boolean durable, + final String src, + final String dest, + final String key, + final String tag, + final String excludes, + final Boolean srcIsQueue, + final Boolean srcIsLocal, + final Boolean dynamic, + final Integer sync) + { + _obj.createBridge(durable, dynamic, srcIsQueue, srcIsLocal, src, dest, key, tag, excludes); + return factory.createResponseCommand(); + } + + public UUID getId() + { + return _obj.getId(); + } + + public long getCreateTime() + { + return _obj.getCreateTime(); + } + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFStatistic.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFStatistic.java new file mode 100644 index 0000000000..89d650e03b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFStatistic.java @@ -0,0 +1,61 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +import org.apache.qpid.transport.codec.Encoder; + +import java.util.LinkedHashMap; + +public class QMFStatistic +{ + private final LinkedHashMap<String,Object> _map = new LinkedHashMap<String,Object>(); + private static final String NAME = "name"; + private static final String TYPE = "type"; + private static final String UNIT = "unit"; + private static final String DESCRIPTION = "desc"; + + + public QMFStatistic(String name, QMFType type, String unit, String description) + { + _map.put(NAME, name); + _map.put(TYPE, type.codeValue()); + if(unit != null) + { + _map.put(UNIT, unit); + } + if(description != null) + { + _map.put(DESCRIPTION, description); + } + + } + + public void encode(Encoder encoder) + { + encoder.writeMap(_map); + } + + public String getName() + { + return (String) _map.get(NAME); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFType.java b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFType.java new file mode 100644 index 0000000000..0e01c27db5 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/qmf/QMFType.java @@ -0,0 +1,53 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.qmf; + +public enum QMFType +{ + + UINT8, + UINT16, + UINT32, + UINT64, + UNKNOWN, + STR8, + STR16, + ABSTIME, + DELTATIME, + OBJECTREFERENCE, + BOOLEAN, + FLOAT, + DOUBLE, + UUID, + MAP, + INT8, + INT16, + INT32, + INT64, + OBJECT, + LIST, + ARRAY; + + public int codeValue() + { + return ordinal()+1; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/AMQBrokerManagerMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/AMQBrokerManagerMBean.java new file mode 100644 index 0000000000..d1ea5dba69 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/AMQBrokerManagerMBean.java @@ -0,0 +1,400 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.management.JMException; +import javax.management.MBeanException; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.management.common.mbeans.ManagedBroker; +import org.apache.qpid.management.common.mbeans.ManagedQueue; +import org.apache.qpid.management.common.mbeans.annotations.MBeanConstructor; +import org.apache.qpid.management.common.mbeans.annotations.MBeanDescription; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.exchange.ExchangeType; +import org.apache.qpid.server.management.AMQManagedObject; +import org.apache.qpid.server.management.ManagedObject; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.AMQQueueMBean; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.store.DurableConfigurationStore; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.virtualhost.VirtualHostImpl; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.ManagementActor; + +/** + * This MBean implements the broker management interface and exposes the + * Broker level management features like creating and deleting exchanges and queue. + */ +@MBeanDescription("This MBean exposes the broker level management features") +public class AMQBrokerManagerMBean extends AMQManagedObject implements ManagedBroker +{ + private final QueueRegistry _queueRegistry; + private final ExchangeRegistry _exchangeRegistry; + private final ExchangeFactory _exchangeFactory; + private final DurableConfigurationStore _durableConfig; + + private final VirtualHostImpl.VirtualHostMBean _virtualHostMBean; + + @MBeanConstructor("Creates the Broker Manager MBean") + public AMQBrokerManagerMBean(VirtualHostImpl.VirtualHostMBean virtualHostMBean) throws JMException + { + super(ManagedBroker.class, ManagedBroker.TYPE); + + _virtualHostMBean = virtualHostMBean; + VirtualHost virtualHost = virtualHostMBean.getVirtualHost(); + + _queueRegistry = virtualHost.getQueueRegistry(); + _exchangeRegistry = virtualHost.getExchangeRegistry(); + _durableConfig = virtualHost.getDurableConfigurationStore(); + _exchangeFactory = virtualHost.getExchangeFactory(); + } + + public String getObjectInstanceName() + { + return _virtualHostMBean.getVirtualHost().getName(); + } + + /** + * Returns an array of the exchange types available for creation. + * @since Qpid JMX API 1.3 + * @throws IOException + */ + public String[] getExchangeTypes() throws IOException + { + ArrayList<String> exchangeTypes = new ArrayList<String>(); + for(ExchangeType<? extends Exchange> ex : _exchangeFactory.getPublicCreatableTypes()) + { + exchangeTypes.add(ex.getName().toString()); + } + + return exchangeTypes.toArray(new String[0]); + } + + /** + * Returns a list containing the names of the attributes available for the Queue mbeans. + * @since Qpid JMX API 1.3 + * @throws IOException + */ + public List<String> retrieveQueueAttributeNames() throws IOException + { + return ManagedQueue.QUEUE_ATTRIBUTES; + } + + /** + * Returns a List of Object Lists containing the requested attribute values (in the same sequence requested) for each queue in the virtualhost. + * If a particular attribute cant be found or raises an mbean/reflection exception whilst being gathered its value is substituted with the String "-". + * @since Qpid JMX API 1.3 + * @throws IOException + */ + public List<List<Object>> retrieveQueueAttributeValues(String[] attributes) throws IOException + { + if(_queueRegistry.getQueues().size() == 0) + { + return new ArrayList<List<Object>>(); + } + + List<List<Object>> queueAttributesList = new ArrayList<List<Object>>(_queueRegistry.getQueues().size()); + + int attributesLength = attributes.length; + + for(AMQQueue queue : _queueRegistry.getQueues()) + { + AMQQueueMBean mbean = (AMQQueueMBean) queue.getManagedObject(); + + if(mbean == null) + { + continue; + } + + List<Object> attributeValues = new ArrayList<Object>(attributesLength); + + for(int i=0; i < attributesLength; i++) + { + try + { + attributeValues.add(mbean.getAttribute(attributes[i])); + } + catch (Exception e) + { + attributeValues.add("-"); + } + } + + queueAttributesList.add(attributeValues); + } + + return queueAttributesList; + } + + /** + * Creates new exchange and registers it with the registry. + * + * @param exchangeName + * @param type + * @param durable + * @throws JMException + * @throws MBeanException + */ + public void createNewExchange(String exchangeName, String type, boolean durable) throws JMException, MBeanException + { + CurrentActor.set(new ManagementActor(_logActor.getRootMessageLogger())); + try + { + synchronized (_exchangeRegistry) + { + Exchange exchange = _exchangeRegistry.getExchange(new AMQShortString(exchangeName)); + if (exchange == null) + { + exchange = _exchangeFactory.createExchange(new AMQShortString(exchangeName), new AMQShortString(type), + durable, false, 0); + _exchangeRegistry.registerExchange(exchange); + if (durable) + { + _durableConfig.createExchange(exchange); + } + } + else + { + throw new JMException("The exchange \"" + exchangeName + "\" already exists."); + } + } + } + catch (AMQException ex) + { + JMException jme = new JMException(ex.toString()); + throw new MBeanException(jme, "Error in creating exchange " + exchangeName); + } + finally + { + CurrentActor.remove(); + } + } + + /** + * Unregisters the exchange from registry. + * + * @param exchangeName + * @throws JMException + * @throws MBeanException + */ + public void unregisterExchange(String exchangeName) throws JMException, MBeanException + { + // TODO + // Check if the exchange is in use. + // boolean inUse = false; + // Check if there are queue-bindings with the exchange and unregister + // when there are no bindings. + CurrentActor.set(new ManagementActor(_logActor.getRootMessageLogger())); + try + { + _exchangeRegistry.unregisterExchange(new AMQShortString(exchangeName), false); + } + catch (AMQException ex) + { + JMException jme = new JMException(ex.toString()); + throw new MBeanException(jme, "Error in unregistering exchange " + exchangeName); + } + finally + { + CurrentActor.remove(); + } + } + + /** + * Creates a new queue and registers it with the registry and puts it + * in persistance storage if durable queue. + * + * @param queueName + * @param durable + * @param owner + * @throws JMException + * @throws MBeanException + */ + public void createNewQueue(String queueName, String owner, boolean durable) throws JMException, MBeanException + { + AMQQueue queue = _queueRegistry.getQueue(new AMQShortString(queueName)); + if (queue != null) + { + throw new JMException("The queue \"" + queueName + "\" already exists."); + } + + CurrentActor.set(new ManagementActor(_logActor.getRootMessageLogger())); + try + { + AMQShortString ownerShortString = null; + if (owner != null) + { + ownerShortString = new AMQShortString(owner); + } + + queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString(queueName), durable, ownerShortString, false, false, getVirtualHost(), null); + if (queue.isDurable() && !queue.isAutoDelete()) + { + _durableConfig.createQueue(queue); + } + + _queueRegistry.registerQueue(queue); + } + catch (AMQException ex) + { + JMException jme = new JMException(ex.toString()); + throw new MBeanException(jme, "Error in creating queue " + queueName); + } + finally + { + CurrentActor.remove(); + } + } + + private VirtualHost getVirtualHost() + { + return _virtualHostMBean.getVirtualHost(); + } + + /** + * Deletes the queue from queue registry and persistant storage. + * + * @param queueName + * @throws JMException + * @throws MBeanException + */ + public void deleteQueue(String queueName) throws JMException, MBeanException + { + AMQQueue queue = _queueRegistry.getQueue(new AMQShortString(queueName)); + if (queue == null) + { + throw new JMException("The Queue " + queueName + " is not a registered queue."); + } + + CurrentActor.set(new ManagementActor(_logActor.getRootMessageLogger())); + try + { + queue.delete(); + if (queue.isDurable()) + { + _durableConfig.removeQueue(queue); + } + } + catch (AMQException ex) + { + JMException jme = new JMException(ex.toString()); + throw new MBeanException(jme, "Error in deleting queue " + queueName); + } + finally + { + CurrentActor.remove(); + } + } + + @Override + public ManagedObject getParentObject() + { + return _virtualHostMBean; + } + + // This will have a single instance for a virtual host, so not having the name property in the ObjectName + @Override + public ObjectName getObjectName() throws MalformedObjectNameException + { + return getObjectNameForSingleInstanceMBean(); + } + + public void resetStatistics() throws Exception + { + getVirtualHost().resetStatistics(); + } + + public double getPeakMessageDeliveryRate() + { + return getVirtualHost().getMessageDeliveryStatistics().getPeak(); + } + + public double getPeakDataDeliveryRate() + { + return getVirtualHost().getDataDeliveryStatistics().getPeak(); + } + + public double getMessageDeliveryRate() + { + return getVirtualHost().getMessageDeliveryStatistics().getRate(); + } + + public double getDataDeliveryRate() + { + return getVirtualHost().getDataDeliveryStatistics().getRate(); + } + + public long getTotalMessagesDelivered() + { + return getVirtualHost().getMessageDeliveryStatistics().getTotal(); + } + + public long getTotalDataDelivered() + { + return getVirtualHost().getDataDeliveryStatistics().getTotal(); + } + + public double getPeakMessageReceiptRate() + { + return getVirtualHost().getMessageReceiptStatistics().getPeak(); + } + + public double getPeakDataReceiptRate() + { + return getVirtualHost().getDataReceiptStatistics().getPeak(); + } + + public double getMessageReceiptRate() + { + return getVirtualHost().getMessageReceiptStatistics().getRate(); + } + + public double getDataReceiptRate() + { + return getVirtualHost().getDataReceiptStatistics().getRate(); + } + + public long getTotalMessagesReceived() + { + return getVirtualHost().getMessageReceiptStatistics().getTotal(); + } + + public long getTotalDataReceived() + { + return getVirtualHost().getDataReceiptStatistics().getTotal(); + } + + public boolean isStatisticsEnabled() + { + return getVirtualHost().isStatisticsEnabled(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java new file mode 100644 index 0000000000..08eb05680c --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/AMQChannel.java @@ -0,0 +1,1465 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.ack.UnacknowledgedMessageMap; +import org.apache.qpid.server.ack.UnacknowledgedMessageMapImpl; +import org.apache.qpid.server.configuration.ConfigStore; +import org.apache.qpid.server.configuration.ConfiguredObject; +import org.apache.qpid.server.configuration.ConnectionConfig; +import org.apache.qpid.server.configuration.SessionConfig; +import org.apache.qpid.server.configuration.SessionConfigType; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.flow.FlowCreditManager; +import org.apache.qpid.server.flow.Pre0_10CreditManager; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.AMQPChannelActor; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.ChannelMessages; +import org.apache.qpid.server.logging.subjects.ChannelLogSubject; +import org.apache.qpid.server.message.AMQMessage; +import org.apache.qpid.server.message.MessageMetaData; +import org.apache.qpid.server.message.MessageReference; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.output.ProtocolOutputConverter; +import org.apache.qpid.server.protocol.AMQProtocolEngine; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.protocol.AMQConnectionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.queue.IncomingMessage; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.subscription.ClientDeliveryMethod; +import org.apache.qpid.server.subscription.RecordDeliveryMethod; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.subscription.SubscriptionFactoryImpl; +import org.apache.qpid.server.txn.AutoCommitTransaction; +import org.apache.qpid.server.txn.LocalTransaction; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +public class AMQChannel implements SessionConfig, AMQSessionModel +{ + public static final int DEFAULT_PREFETCH = 5000; + + private static final Logger _logger = Logger.getLogger(AMQChannel.class); + + private static final boolean MSG_AUTH = + ApplicationRegistry.getInstance().getConfiguration().getMsgAuth(); + + + private final int _channelId; + + + private final Pre0_10CreditManager _creditManager = new Pre0_10CreditManager(0l,0l); + + /** + * The delivery tag is unique per channel. This is pre-incremented before putting into the deliver frame so that + * value of this represents the <b>last</b> tag sent out + */ + private long _deliveryTag = 0; + + /** A channel has a default queue (the last declared) that is used when no queue name is explictily set */ + private AMQQueue _defaultQueue; + + /** This tag is unique per subscription to a queue. The server returns this in response to a basic.consume request. */ + private int _consumerTag; + + /** + * The current message - which may be partial in the sense that not all frames have been received yet - which has + * been received by this channel. As the frames are received the message gets updated and once all frames have been + * received the message can then be routed. + */ + private IncomingMessage _currentMessage; + + /** Maps from consumer tag to subscription instance. Allows us to unsubscribe from a queue. */ + protected final Map<AMQShortString, Subscription> _tag2SubscriptionMap = new HashMap<AMQShortString, Subscription>(); + + private final MessageStore _messageStore; + + private UnacknowledgedMessageMap _unacknowledgedMessageMap = new UnacknowledgedMessageMapImpl(DEFAULT_PREFETCH); + + // Set of messages being acknoweledged in the current transaction + private SortedSet<QueueEntry> _acknowledgedMessages = new TreeSet<QueueEntry>(); + + private final AtomicBoolean _suspended = new AtomicBoolean(false); + + private ServerTransaction _transaction; + + private final AtomicLong _txnStarts = new AtomicLong(0); + private final AtomicLong _txnCommits = new AtomicLong(0); + private final AtomicLong _txnRejects = new AtomicLong(0); + private final AtomicLong _txnCount = new AtomicLong(0); + private final AtomicLong _txnUpdateTime = new AtomicLong(0); + + private final AMQProtocolSession _session; + private AtomicBoolean _closing = new AtomicBoolean(false); + + private final ConcurrentMap<AMQQueue, Boolean> _blockingQueues = new ConcurrentHashMap<AMQQueue, Boolean>(); + + private final AtomicBoolean _blocking = new AtomicBoolean(false); + + + private LogActor _actor; + private LogSubject _logSubject; + private volatile boolean _rollingBack; + + private static final Runnable NULL_TASK = new Runnable() { public void run() {} }; + private List<QueueEntry> _resendList = new ArrayList<QueueEntry>(); + private static final + AMQShortString IMMEDIATE_DELIVERY_REPLY_TEXT = new AMQShortString("Immediate delivery is not possible."); + private final UUID _id; + private long _createTime = System.currentTimeMillis(); + + public AMQChannel(AMQProtocolSession session, int channelId, MessageStore messageStore) + throws AMQException + { + _session = session; + _channelId = channelId; + + _actor = new AMQPChannelActor(this, session.getLogActor().getRootMessageLogger()); + _logSubject = new ChannelLogSubject(this); + _id = getConfigStore().createId(); + _actor.message(ChannelMessages.CREATE()); + + getConfigStore().addConfiguredObject(this); + + _messageStore = messageStore; + + // by default the session is non-transactional + _transaction = new AutoCommitTransaction(_messageStore); + } + + public ConfigStore getConfigStore() + { + return getVirtualHost().getConfigStore(); + } + + /** Sets this channel to be part of a local transaction */ + public void setLocalTransactional() + { + _transaction = new LocalTransaction(_messageStore); + _txnStarts.incrementAndGet(); + } + + public boolean isTransactional() + { + // this does not look great but there should only be one "non-transactional" + // transactional context, while there could be several transactional ones in + // theory + return !(_transaction instanceof AutoCommitTransaction); + } + + public boolean inTransaction() + { + return isTransactional() && _txnUpdateTime.get() > 0 && _transaction.getTransactionStartTime() > 0; + } + + private void incrementOutstandingTxnsIfNecessary() + { + if(isTransactional()) + { + //There can currently only be at most one outstanding transaction + //due to only having LocalTransaction support. Set value to 1 if 0. + _txnCount.compareAndSet(0,1); + } + } + + private void decrementOutstandingTxnsIfNecessary() + { + if(isTransactional()) + { + //There can currently only be at most one outstanding transaction + //due to only having LocalTransaction support. Set value to 0 if 1. + _txnCount.compareAndSet(1,0); + } + } + + public Long getTxnStarts() + { + return _txnStarts.get(); + } + + public Long getTxnCommits() + { + return _txnCommits.get(); + } + + public Long getTxnRejects() + { + return _txnRejects.get(); + } + + public Long getTxnCount() + { + return _txnCount.get(); + } + + public int getChannelId() + { + return _channelId; + } + + public void setPublishFrame(MessagePublishInfo info, final Exchange e) throws AMQSecurityException + { + if (!getVirtualHost().getSecurityManager().authorisePublish(info.isImmediate(), info.getRoutingKey().asString(), e.getName())) + { + throw new AMQSecurityException("Permission denied: " + e.getName()); + } + _currentMessage = new IncomingMessage(info); + _currentMessage.setExchange(e); + } + + public void publishContentHeader(ContentHeaderBody contentHeaderBody) + throws AMQException + { + if (_currentMessage == null) + { + throw new AMQException("Received content header without previously receiving a BasicPublish frame"); + } + else + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Content header received on channel " + _channelId); + } + + _currentMessage.setContentHeaderBody(contentHeaderBody); + + _currentMessage.setExpiration(); + + + MessageMetaData mmd = _currentMessage.headersReceived(); + final StoredMessage<MessageMetaData> handle = _messageStore.addMessage(mmd); + _currentMessage.setStoredMessage(handle); + + routeCurrentMessage(); + + + _transaction.addPostTransactionAction(new ServerTransaction.Action() + { + + public void postCommit() + { + } + + public void onRollback() + { + handle.remove(); + } + }); + + deliverCurrentMessageIfComplete(); + } + } + + private void deliverCurrentMessageIfComplete() + throws AMQException + { + // check and deliver if header says body length is zero + if (_currentMessage.allContentReceived()) + { + try + { + _currentMessage.getStoredMessage().flushToStore(); + + final ArrayList<? extends BaseQueue> destinationQueues = _currentMessage.getDestinationQueues(); + + if(!checkMessageUserId(_currentMessage.getContentHeader())) + { + _transaction.addPostTransactionAction(new WriteReturnAction(AMQConstant.ACCESS_REFUSED, "Access Refused", _currentMessage)); + } + else + { + if(destinationQueues == null || _currentMessage.getDestinationQueues().isEmpty()) + { + if (_currentMessage.isMandatory() || _currentMessage.isImmediate()) + { + _transaction.addPostTransactionAction(new WriteReturnAction(AMQConstant.NO_ROUTE, "No Route for message", _currentMessage)); + } + else + { + _logger.warn("MESSAGE DISCARDED: No routes for message - " + createAMQMessage(_currentMessage)); + } + + } + else + { + _transaction.enqueue(destinationQueues, _currentMessage, new MessageDeliveryAction(_currentMessage, destinationQueues, isTransactional())); + incrementOutstandingTxnsIfNecessary(); + updateTransactionalActivity(); + } + } + } + finally + { + long bodySize = _currentMessage.getSize(); + long timestamp = ((BasicContentHeaderProperties) _currentMessage.getContentHeader().getProperties()).getTimestamp(); + _session.registerMessageReceived(bodySize, timestamp); + _currentMessage = null; + } + } + + } + + public void publishContentBody(ContentBody contentBody) throws AMQException + { + if (_currentMessage == null) + { + throw new AMQException("Received content body without previously receiving a JmsPublishBody"); + } + + if (_logger.isDebugEnabled()) + { + _logger.debug(debugIdentity() + "Content body received on channel " + _channelId); + } + + try + { + + // returns true iff the message was delivered (i.e. if all data was + // received + final ContentChunk contentChunk = + _session.getMethodRegistry().getProtocolVersionMethodConverter().convertToContentChunk(contentBody); + + _currentMessage.addContentBodyFrame(contentChunk); + + deliverCurrentMessageIfComplete(); + } + catch (AMQException e) + { + // we want to make sure we don't keep a reference to the message in the + // event of an error + _currentMessage = null; + throw e; + } + } + + protected void routeCurrentMessage() throws AMQException + { + _currentMessage.route(); + } + + public long getNextDeliveryTag() + { + return ++_deliveryTag; + } + + public int getNextConsumerTag() + { + return ++_consumerTag; + } + + + public Subscription getSubscription(AMQShortString subscription) + { + return _tag2SubscriptionMap.get(subscription); + } + + /** + * Subscribe to a queue. We register all subscriptions in the channel so that if the channel is closed we can clean + * up all subscriptions, even if the client does not explicitly unsubscribe from all queues. + * + * @param tag the tag chosen by the client (if null, server will generate one) + * @param queue the queue to subscribe to + * @param acks Are acks enabled for this subscriber + * @param filters Filters to apply to this subscriber + * + * @param noLocal Flag stopping own messages being receivied. + * @param exclusive Flag requesting exclusive access to the queue + * @return the consumer tag. This is returned to the subscriber and used in subsequent unsubscribe requests + * + * @throws AMQException if something goes wrong + */ + public AMQShortString subscribeToQueue(AMQShortString tag, AMQQueue queue, boolean acks, + FieldTable filters, boolean noLocal, boolean exclusive) throws AMQException + { + if (tag == null) + { + tag = new AMQShortString("sgen_" + getNextConsumerTag()); + } + + if (_tag2SubscriptionMap.containsKey(tag)) + { + throw new AMQException("Consumer already exists with same tag: " + tag); + } + + Subscription subscription = + SubscriptionFactoryImpl.INSTANCE.createSubscription(_channelId, _session, tag, acks, filters, noLocal, _creditManager); + + + // So to keep things straight we put before the call and catch all exceptions from the register and tidy up. + // We add before we register as the Async Delivery process may AutoClose the subscriber + // so calling _cT2QM.remove before we have done put which was after the register succeeded. + // So to keep things straight we put before the call and catch all exceptions from the register and tidy up. + + _tag2SubscriptionMap.put(tag, subscription); + + try + { + queue.registerSubscription(subscription, exclusive); + } + catch (AMQException e) + { + _tag2SubscriptionMap.remove(tag); + throw e; + } + return tag; + } + + /** + * Unsubscribe a consumer from a queue. + * @param consumerTag + * @return true if the consumerTag had a mapped queue that could be unregistered. + * @throws AMQException + */ + public boolean unsubscribeConsumer(AMQShortString consumerTag) throws AMQException + { + + Subscription sub = _tag2SubscriptionMap.remove(consumerTag); + if (sub != null) + { + try + { + sub.getSendLock(); + sub.getQueue().unregisterSubscription(sub); + } + finally + { + sub.releaseSendLock(); + } + return true; + } + else + { + _logger.warn("Attempt to unsubscribe consumer with tag '"+consumerTag+"' which is not registered."); + } + return false; + } + + /** + * Called from the protocol session to close this channel and clean up. T + * + * @throws AMQException if there is an error during closure + */ + public void close() throws AMQException + { + if(!_closing.compareAndSet(false, true)) + { + //Channel is already closing + return; + } + + CurrentActor.get().message(_logSubject, ChannelMessages.CLOSE()); + + unsubscribeAllConsumers(); + _transaction.rollback(); + + try + { + requeue(); + } + catch (AMQException e) + { + _logger.error("Caught AMQException whilst attempting to reque:" + e); + } + + getConfigStore().removeConfiguredObject(this); + + } + + private void unsubscribeAllConsumers() throws AMQException + { + if (_logger.isInfoEnabled()) + { + if (!_tag2SubscriptionMap.isEmpty()) + { + _logger.info("Unsubscribing all consumers on channel " + toString()); + } + else + { + _logger.info("No consumers to unsubscribe on channel " + toString()); + } + } + + for (Map.Entry<AMQShortString, Subscription> me : _tag2SubscriptionMap.entrySet()) + { + if (_logger.isInfoEnabled()) + { + _logger.info("Unsubscribing consumer '" + me.getKey() + "' on channel " + toString()); + } + + Subscription sub = me.getValue(); + + try + { + sub.getSendLock(); + sub.getQueue().unregisterSubscription(sub); + } + finally + { + sub.releaseSendLock(); + } + + } + + _tag2SubscriptionMap.clear(); + } + + /** + * Add a message to the channel-based list of unacknowledged messages + * + * @param entry the record of the message on the queue that was delivered + * @param deliveryTag the delivery tag used when delivering the message (see protocol spec for description of the + * delivery tag) + * @param subscription The consumer that is to acknowledge this message. + */ + public void addUnacknowledgedMessage(QueueEntry entry, long deliveryTag, Subscription subscription) + { + if (_logger.isDebugEnabled()) + { + if (entry.getQueue() == null) + { + _logger.debug("Adding unacked message with a null queue:" + entry); + } + else + { + if (_logger.isDebugEnabled()) + { + _logger.debug(debugIdentity() + " Adding unacked message(" + entry.getMessage().toString() + " DT:" + deliveryTag + + ") with a queue(" + entry.getQueue() + ") for " + subscription); + } + } + } + + _unacknowledgedMessageMap.add(deliveryTag, entry); + + } + + private final String id = "(" + System.identityHashCode(this) + ")"; + + public String debugIdentity() + { + return _channelId + id; + } + + /** + * Called to attempt re-delivery all outstanding unacknowledged messages on the channel. May result in delivery to + * this same channel or to other subscribers. + * + * @throws org.apache.qpid.AMQException if the requeue fails + */ + public void requeue() throws AMQException + { + // we must create a new map since all the messages will get a new delivery tag when they are redelivered + Collection<QueueEntry> messagesToBeDelivered = _unacknowledgedMessageMap.cancelAllMessages(); + + if (!messagesToBeDelivered.isEmpty()) + { + if (_logger.isInfoEnabled()) + { + _logger.info("Requeuing " + messagesToBeDelivered.size() + " unacked messages. for " + toString()); + } + + } + + for (QueueEntry unacked : messagesToBeDelivered) + { + if (!unacked.isQueueDeleted()) + { + // Mark message redelivered + unacked.setRedelivered(); + + // Ensure message is released for redelivery + unacked.release(); + + } + else + { + unacked.discard(); + } + } + + } + + /** + * Requeue a single message + * + * @param deliveryTag The message to requeue + * + * @throws AMQException If something goes wrong. + */ + public void requeue(long deliveryTag) throws AMQException + { + QueueEntry unacked = _unacknowledgedMessageMap.remove(deliveryTag); + + if (unacked != null) + { + // Mark message redelivered + unacked.setRedelivered(); + + // Ensure message is released for redelivery + if (!unacked.isQueueDeleted()) + { + + // Ensure message is released for redelivery + unacked.release(); + + } + else + { + _logger.warn(System.identityHashCode(this) + " Requested requeue of message(" + unacked + + "):" + deliveryTag + " but no queue defined and no DeadLetter queue so DROPPING message."); + + unacked.discard(); + } + } + else + { + _logger.warn("Requested requeue of message:" + deliveryTag + " but no such delivery tag exists." + + _unacknowledgedMessageMap.size()); + + } + + } + + /** + * Called to resend all outstanding unacknowledged messages to this same channel. + * + * @param requeue Are the messages to be requeued or dropped. + * + * @throws AMQException When something goes wrong. + */ + public void resend(final boolean requeue) throws AMQException + { + + + final Map<Long, QueueEntry> msgToRequeue = new LinkedHashMap<Long, QueueEntry>(); + final Map<Long, QueueEntry> msgToResend = new LinkedHashMap<Long, QueueEntry>(); + + if (_logger.isDebugEnabled()) + { + _logger.debug("unacked map Size:" + _unacknowledgedMessageMap.size()); + } + + // Process the Unacked-Map. + // Marking messages who still have a consumer for to be resent + // and those that don't to be requeued. + _unacknowledgedMessageMap.visit(new ExtractResendAndRequeue(_unacknowledgedMessageMap, + msgToRequeue, + msgToResend, + requeue, + _messageStore)); + + + // Process Messages to Resend + if (_logger.isDebugEnabled()) + { + if (!msgToResend.isEmpty()) + { + _logger.debug("Preparing (" + msgToResend.size() + ") message to resend."); + } + else + { + _logger.debug("No message to resend."); + } + } + + for (Map.Entry<Long, QueueEntry> entry : msgToResend.entrySet()) + { + QueueEntry message = entry.getValue(); + long deliveryTag = entry.getKey(); + + + + ServerMessage msg = message.getMessage(); + AMQQueue queue = message.getQueue(); + + // Our Java Client will always suspend the channel when resending! + // If the client has requested the messages be resent then it is + // their responsibility to ensure that thay are capable of receiving them + // i.e. The channel hasn't been server side suspended. + // if (isSuspended()) + // { + // _logger.info("Channel is suspended so requeuing"); + // //move this message to requeue + // msgToRequeue.add(message); + // } + // else + // { + // release to allow it to be delivered + + // Without any details from the client about what has been processed we have to mark + // all messages in the unacked map as redelivered. + message.setRedelivered(); + + Subscription sub = message.getDeliveredSubscription(); + + if (sub != null) + { + + if(!queue.resend(message,sub)) + { + msgToRequeue.put(deliveryTag, message); + } + } + else + { + + if (_logger.isInfoEnabled()) + { + _logger.info("DeliveredSubscription not recorded so just requeueing(" + message.toString() + + ")to prevent loss"); + } + // move this message to requeue + msgToRequeue.put(deliveryTag, message); + } + } // for all messages + // } else !isSuspend + + if (_logger.isInfoEnabled()) + { + if (!msgToRequeue.isEmpty()) + { + _logger.info("Preparing (" + msgToRequeue.size() + ") message to requeue to."); + } + } + + // Process Messages to Requeue at the front of the queue + for (Map.Entry<Long, QueueEntry> entry : msgToRequeue.entrySet()) + { + QueueEntry message = entry.getValue(); + long deliveryTag = entry.getKey(); + _unacknowledgedMessageMap.remove(deliveryTag); + + message.setRedelivered(); + message.release(); + + } + } + + + /** + * Acknowledge one or more messages. + * + * @param deliveryTag the last delivery tag + * @param multiple if true will acknowledge all messages up to an including the delivery tag. if false only + * acknowledges the single message specified by the delivery tag + * + * @throws AMQException if the delivery tag is unknown (e.g. not outstanding) on this channel + */ + public void acknowledgeMessage(long deliveryTag, boolean multiple) throws AMQException + { + Collection<QueueEntry> ackedMessages = getAckedMessages(deliveryTag, multiple); + _transaction.dequeue(ackedMessages, new MessageAcknowledgeAction(ackedMessages)); + updateTransactionalActivity(); + } + + private Collection<QueueEntry> getAckedMessages(long deliveryTag, boolean multiple) + { + + Map<Long, QueueEntry> ackedMessageMap = new LinkedHashMap<Long,QueueEntry>(); + _unacknowledgedMessageMap.collect(deliveryTag, multiple, ackedMessageMap); + _unacknowledgedMessageMap.remove(ackedMessageMap); + return ackedMessageMap.values(); + } + + /** + * Used only for testing purposes. + * + * @return the map of unacknowledged messages + */ + public UnacknowledgedMessageMap getUnacknowledgedMessageMap() + { + return _unacknowledgedMessageMap; + } + + /** + * Called from the ChannelFlowHandler to suspend this Channel + * @param suspended boolean, should this Channel be suspended + */ + public void setSuspended(boolean suspended) + { + boolean wasSuspended = _suspended.getAndSet(suspended); + if (wasSuspended != suspended) + { + // Log Flow Started before we start the subscriptions + if (!suspended) + { + _actor.message(_logSubject, ChannelMessages.FLOW("Started")); + } + + + // This section takes two different approaches to perform to perform + // the same function. Ensuring that the Subscription has taken note + // of the change in Channel State + + // Here we have become unsuspended and so we ask each the queue to + // perform an Async delivery for each of the subscriptions in this + // Channel. The alternative would be to ensure that the subscription + // had received the change in suspension state. That way the logic + // behind decieding to start an async delivery was located with the + // Subscription. + if (wasSuspended) + { + // may need to deliver queued messages + for (Subscription s : _tag2SubscriptionMap.values()) + { + s.getQueue().deliverAsync(s); + } + } + + + // Here we have become suspended so we need to ensure that each of + // the Subscriptions has noticed this change so that we can be sure + // they are not still sending messages. Again the code here is a + // very simplistic approach to ensure that the change of suspension + // has been noticed by each of the Subscriptions. Unlike the above + // case we don't actually need to do anything else. + if (!wasSuspended) + { + // may need to deliver queued messages + for (Subscription s : _tag2SubscriptionMap.values()) + { + try + { + s.getSendLock(); + } + finally + { + s.releaseSendLock(); + } + } + } + + + // Log Suspension only after we have confirmed all suspensions are + // stopped. + if (suspended) + { + _actor.message(_logSubject, ChannelMessages.FLOW("Stopped")); + } + + } + } + + public boolean isSuspended() + { + return _suspended.get() || _closing.get() || _session.isClosing(); + } + + public void commit() throws AMQException + { + if (!isTransactional()) + { + throw new AMQException("Fatal error: commit called on non-transactional channel"); + } + + _transaction.commit(); + + _txnCommits.incrementAndGet(); + _txnStarts.incrementAndGet(); + decrementOutstandingTxnsIfNecessary(); + } + + public void rollback() throws AMQException + { + rollback(NULL_TASK); + } + + public void rollback(Runnable postRollbackTask) throws AMQException + { + if (!isTransactional()) + { + throw new AMQException("Fatal error: commit called on non-transactional channel"); + } + + // stop all subscriptions + _rollingBack = true; + boolean requiresSuspend = _suspended.compareAndSet(false,true); + + // ensure all subscriptions have seen the change to the channel state + for(Subscription sub : _tag2SubscriptionMap.values()) + { + sub.getSendLock(); + sub.releaseSendLock(); + } + + try + { + _transaction.rollback(); + } + finally + { + _rollingBack = false; + + _txnRejects.incrementAndGet(); + _txnStarts.incrementAndGet(); + decrementOutstandingTxnsIfNecessary(); + } + + postRollbackTask.run(); + + for(QueueEntry entry : _resendList) + { + Subscription sub = entry.getDeliveredSubscription(); + if(sub == null || sub.isClosed()) + { + entry.release(); + } + else + { + sub.getQueue().resend(entry, sub); + } + } + _resendList.clear(); + + if(requiresSuspend) + { + _suspended.set(false); + for(Subscription sub : _tag2SubscriptionMap.values()) + { + sub.getQueue().deliverAsync(sub); + } + + } + + + } + + /** + * Update last transaction activity timestamp + */ + private void updateTransactionalActivity() + { + if (isTransactional()) + { + _txnUpdateTime.set(System.currentTimeMillis()); + } + } + + public String toString() + { + return "["+_session.toString()+":"+_channelId+"]"; + } + + public void setDefaultQueue(AMQQueue queue) + { + _defaultQueue = queue; + } + + public AMQQueue getDefaultQueue() + { + return _defaultQueue; + } + + + public boolean isClosing() + { + return _closing.get(); + } + + public AMQProtocolSession getProtocolSession() + { + return _session; + } + + public FlowCreditManager getCreditManager() + { + return _creditManager; + } + + public void setCredit(final long prefetchSize, final int prefetchCount) + { + _actor.message(ChannelMessages.PREFETCH_SIZE(prefetchSize, prefetchCount)); + _creditManager.setCreditLimits(prefetchSize, prefetchCount); + } + + public MessageStore getMessageStore() + { + return _messageStore; + } + + private final ClientDeliveryMethod _clientDeliveryMethod = new ClientDeliveryMethod() + { + + public void deliverToClient(final Subscription sub, final QueueEntry entry, final long deliveryTag) + throws AMQException + { + getProtocolSession().getProtocolOutputConverter().writeDeliver(entry, getChannelId(), + deliveryTag, sub.getConsumerTag()); + _session.registerMessageDelivered(entry.getMessage().getSize()); + } + + }; + + public ClientDeliveryMethod getClientDeliveryMethod() + { + return _clientDeliveryMethod; + } + + private final RecordDeliveryMethod _recordDeliveryMethod = new RecordDeliveryMethod() + { + + public void recordMessageDelivery(final Subscription sub, final QueueEntry entry, final long deliveryTag) + { + addUnacknowledgedMessage(entry, deliveryTag, sub); + } + }; + + public RecordDeliveryMethod getRecordDeliveryMethod() + { + return _recordDeliveryMethod; + } + + + private AMQMessage createAMQMessage(IncomingMessage incomingMessage) + throws AMQException + { + + AMQMessage message = new AMQMessage(incomingMessage.getStoredMessage()); + + message.setExpiration(incomingMessage.getExpiration()); + message.setClientIdentifier(_session); + return message; + } + + private boolean checkMessageUserId(ContentHeaderBody header) + { + AMQShortString userID = + header.getProperties() instanceof BasicContentHeaderProperties + ? ((BasicContentHeaderProperties) header.getProperties()).getUserId() + : null; + + return (!MSG_AUTH || _session.getPrincipal().getName().equals(userID == null? "" : userID.toString())); + + } + + public Object getID() + { + return _channelId; + } + + public AMQConnectionModel getConnectionModel() + { + return _session; + } + + public String getClientID() + { + return String.valueOf(_session.getContextKey()); + } + + public LogSubject getLogSubject() + { + return _logSubject; + } + + private class MessageDeliveryAction implements ServerTransaction.Action + { + private IncomingMessage _incommingMessage; + private ArrayList<? extends BaseQueue> _destinationQueues; + + public MessageDeliveryAction(IncomingMessage currentMessage, + ArrayList<? extends BaseQueue> destinationQueues, + boolean transactional) + { + _incommingMessage = currentMessage; + _destinationQueues = destinationQueues; + } + + public void postCommit() + { + try + { + final boolean immediate = _incommingMessage.isImmediate(); + + final AMQMessage amqMessage = createAMQMessage(_incommingMessage); + MessageReference ref = amqMessage.newReference(); + + for(final BaseQueue queue : _destinationQueues) + { + BaseQueue.PostEnqueueAction action; + + if(immediate) + { + action = new ImmediateAction(queue); + } + else + { + action = null; + } + + queue.enqueue(amqMessage, action); + + if(queue instanceof AMQQueue) + { + ((AMQQueue)queue).checkCapacity(AMQChannel.this); + } + + } + ref.release(); + } + catch (AMQException e) + { + // TODO + throw new RuntimeException(e); + } + + + + + + } + + public void onRollback() + { + // Maybe keep track of entries that were created and then delete them here in case of failure + // to in memory enqueue + } + + private class ImmediateAction implements BaseQueue.PostEnqueueAction + { + private final BaseQueue _queue; + + public ImmediateAction(BaseQueue queue) + { + _queue = queue; + } + + public void onEnqueue(QueueEntry entry) + { + if (!entry.getDeliveredToConsumer() && entry.acquire()) + { + + + ServerTransaction txn = new LocalTransaction(_messageStore); + Collection<QueueEntry> entries = new ArrayList<QueueEntry>(1); + entries.add(entry); + final AMQMessage message = (AMQMessage) entry.getMessage(); + txn.dequeue(_queue, entry.getMessage(), + new MessageAcknowledgeAction(entries) + { + @Override + public void postCommit() + { + try + { + final + ProtocolOutputConverter outputConverter = + _session.getProtocolOutputConverter(); + + outputConverter.writeReturn(message.getMessagePublishInfo(), + message.getContentHeaderBody(), + message, + _channelId, + AMQConstant.NO_CONSUMERS.getCode(), + IMMEDIATE_DELIVERY_REPLY_TEXT); + } + catch (AMQException e) + { + throw new RuntimeException(e); + } + super.postCommit(); + } + } + ); + txn.commit(); + + + } + + } + } + } + + private class MessageAcknowledgeAction implements ServerTransaction.Action + { + private final Collection<QueueEntry> _ackedMessages; + + + public MessageAcknowledgeAction(Collection<QueueEntry> ackedMessages) + { + _ackedMessages = ackedMessages; + } + + public void postCommit() + { + try + { + for(QueueEntry entry : _ackedMessages) + { + entry.discard(); + } + } + finally + { + _acknowledgedMessages.clear(); + } + + } + + public void onRollback() + { + // explicit rollbacks resend the message after the rollback-ok is sent + if(_rollingBack) + { + _resendList.addAll(_ackedMessages); + } + else + { + try + { + for(QueueEntry entry : _ackedMessages) + { + entry.release(); + } + } + finally + { + _acknowledgedMessages.clear(); + } + } + + } + } + + private class WriteReturnAction implements ServerTransaction.Action + { + private final AMQConstant _errorCode; + private final IncomingMessage _message; + private final String _description; + + public WriteReturnAction(AMQConstant errorCode, + String description, + IncomingMessage message) + { + _errorCode = errorCode; + _message = message; + _description = description; + } + + public void postCommit() + { + try + { + _session.getProtocolOutputConverter().writeReturn(_message.getMessagePublishInfo(), + _message.getContentHeader(), + _message, + _channelId, + _errorCode.getCode(), + new AMQShortString(_description)); + } + catch (AMQException e) + { + //TODO + throw new RuntimeException(e); + } + + } + + public void onRollback() + { + //To change body of implemented methods use File | Settings | File Templates. + } + } + + + public LogActor getLogActor() + { + return _actor; + } + + public void block(AMQQueue queue) + { + if(_blockingQueues.putIfAbsent(queue, Boolean.TRUE) == null) + { + + if(_blocking.compareAndSet(false,true)) + { + _actor.message(_logSubject, ChannelMessages.FLOW_ENFORCED(queue.getNameShortString().toString())); + flow(false); + } + } + } + + public void unblock(AMQQueue queue) + { + if(_blockingQueues.remove(queue)) + { + if(_blocking.compareAndSet(true,false)) + { + _actor.message(_logSubject, ChannelMessages.FLOW_REMOVED()); + + flow(true); + } + } + } + + private void flow(boolean flow) + { + MethodRegistry methodRegistry = _session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createChannelFlowBody(flow); + _session.writeFrame(responseBody.generateFrame(_channelId)); + } + + public boolean getBlocking() + { + return _blocking.get(); + } + + public VirtualHost getVirtualHost() + { + return getProtocolSession().getVirtualHost(); + } + + + public ConfiguredObject getParent() + { + return getVirtualHost(); + } + + public SessionConfigType getConfigType() + { + return SessionConfigType.getInstance(); + } + + public int getChannel() + { + return getChannelId(); + } + + public boolean isAttached() + { + return true; + } + + public long getDetachedLifespan() + { + return 0; + } + + public ConnectionConfig getConnectionConfig() + { + return (AMQProtocolEngine)getProtocolSession(); + } + + public Long getExpiryTime() + { + return null; + } + + public Long getMaxClientRate() + { + return null; + } + + public boolean isDurable() + { + return false; + } + + public UUID getId() + { + return _id; + } + + public String getSessionName() + { + return getConnectionConfig().getAddress() + "/" + getChannelId(); + } + + public long getCreateTime() + { + return _createTime; + } + + public void mgmtClose() throws AMQException + { + _session.mgmtCloseChannel(_channelId); + } + + public void checkTransactionStatus(long openWarn, long openClose, long idleWarn, long idleClose) throws AMQException + { + if (inTransaction()) + { + long currentTime = System.currentTimeMillis(); + long openTime = currentTime - _transaction.getTransactionStartTime(); + long idleTime = currentTime - _txnUpdateTime.get(); + + // Log a warning on idle or open transactions + if (idleWarn > 0L && idleTime > idleWarn) + { + CurrentActor.get().message(_logSubject, ChannelMessages.IDLE_TXN(idleTime)); + _logger.warn("IDLE TRANSACTION ALERT " + _logSubject.toString() + " " + idleTime + " ms"); + } + else if (openWarn > 0L && openTime > openWarn) + { + CurrentActor.get().message(_logSubject, ChannelMessages.OPEN_TXN(openTime)); + _logger.warn("OPEN TRANSACTION ALERT " + _logSubject.toString() + " " + openTime + " ms"); + } + + // Close connection for idle or open transactions that have timed out + if (idleClose > 0L && idleTime > idleClose) + { + getConnectionModel().closeSession(this, AMQConstant.RESOURCE_ERROR, "Idle transaction timed out"); + } + else if (openClose > 0L && openTime > openClose) + { + getConnectionModel().closeSession(this, AMQConstant.RESOURCE_ERROR, "Open transaction timed out"); + } + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/ExtractResendAndRequeue.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/ExtractResendAndRequeue.java new file mode 100644 index 0000000000..9da02e0600 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/ExtractResendAndRequeue.java @@ -0,0 +1,133 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server; + +import org.apache.qpid.server.ack.UnacknowledgedMessageMap; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.store.TransactionLog; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.txn.AutoCommitTransaction; +import org.apache.qpid.AMQException; +import org.apache.log4j.Logger; + +import java.util.Map; + +public class ExtractResendAndRequeue implements UnacknowledgedMessageMap.Visitor +{ + private static final Logger _log = Logger.getLogger(ExtractResendAndRequeue.class); + + private final Map<Long, QueueEntry> _msgToRequeue; + private final Map<Long, QueueEntry> _msgToResend; + private final boolean _requeueIfUnabletoResend; + private final UnacknowledgedMessageMap _unacknowledgedMessageMap; + private final TransactionLog _transactionLog; + + public ExtractResendAndRequeue(UnacknowledgedMessageMap unacknowledgedMessageMap, + Map<Long, QueueEntry> msgToRequeue, + Map<Long, QueueEntry> msgToResend, + boolean requeueIfUnabletoResend, + TransactionLog txnLog) + { + _unacknowledgedMessageMap = unacknowledgedMessageMap; + _msgToRequeue = msgToRequeue; + _msgToResend = msgToResend; + _requeueIfUnabletoResend = requeueIfUnabletoResend; + _transactionLog = txnLog; + } + + public boolean callback(final long deliveryTag, QueueEntry message) throws AMQException + { + + message.setRedelivered(); + final Subscription subscription = message.getDeliveredSubscription(); + if (subscription != null) + { + // Consumer exists + if (!subscription.isClosed()) + { + _msgToResend.put(deliveryTag, message); + } + else // consumer has gone + { + _msgToRequeue.put(deliveryTag, message); + } + } + else + { + // Message has no consumer tag, so was "delivered" to a GET + // or consumer no longer registered + // cannot resend, so re-queue. + if (!message.isQueueDeleted()) + { + if (_requeueIfUnabletoResend) + { + _msgToRequeue.put(deliveryTag, message); + } + else + { + + dequeueEntry(message); + _log.info("No DeadLetter Queue and requeue not requested so dropping message:" + message); + } + } + else + { + dequeueEntry(message); + _log.warn("Message.queue is null and no DeadLetter Queue so dropping message:" + message); + } + } + + // false means continue processing + return false; + } + + + private void dequeueEntry(final QueueEntry node) + { + ServerTransaction txn = new AutoCommitTransaction(_transactionLog); + dequeueEntry(node, txn); + } + + private void dequeueEntry(final QueueEntry node, ServerTransaction txn) + { + txn.dequeue(node.getQueue(), node.getMessage(), + new ServerTransaction.Action() + { + + public void postCommit() + { + node.discard(); + } + + public void onRollback() + { + + } + }); + } + + public void visitComplete() + { + _unacknowledgedMessageMap.clear(); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/Main.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/Main.java new file mode 100644 index 0000000000..9d3c4dd2e8 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/Main.java @@ -0,0 +1,617 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.OptionBuilder; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.cli.PosixParser; +import org.apache.log4j.Logger; +import org.apache.log4j.PropertyConfigurator; +import org.apache.log4j.xml.QpidLog4JConfigurator; +import org.apache.qpid.common.QpidProperties; +import org.apache.qpid.framing.ProtocolVersion; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.configuration.management.ConfigurationManagementMBean; +import org.apache.qpid.server.information.management.ServerInformationMBean; +import org.apache.qpid.server.logging.SystemOutMessageLogger; +import org.apache.qpid.server.logging.actors.BrokerActor; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.GenericActor; +import org.apache.qpid.server.logging.management.LoggingManagementMBean; +import org.apache.qpid.server.logging.messages.BrokerMessages; +import org.apache.qpid.server.protocol.AMQProtocolEngineFactory; +import org.apache.qpid.server.protocol.MultiVersionProtocolEngineFactory; +import org.apache.qpid.server.protocol.MultiVersionProtocolEngineFactory.VERSION; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.ConfigurationFileApplicationRegistry; +import org.apache.qpid.server.transport.QpidAcceptor; +import org.apache.qpid.ssl.SSLContextFactory; +import org.apache.qpid.transport.NetworkDriver; +import org.apache.qpid.transport.network.mina.MINANetworkDriver; + +/** + * Main entry point for AMQPD. + * + */ +public class Main +{ + private static Logger _logger; + + private static final String DEFAULT_CONFIG_FILE = "etc/config.xml"; + + public static final String DEFAULT_LOG_CONFIG_FILENAME = "log4j.xml"; + public static final String QPID_HOME = "QPID_HOME"; + private static final int IPV4_ADDRESS_LENGTH = 4; + + private static final char IPV4_LITERAL_SEPARATOR = '.'; + + protected static class InitException extends Exception + { + InitException(String msg, Throwable cause) + { + super(msg, cause); + } + } + + protected final Options options = new Options(); + protected CommandLine commandLine; + + protected Main(String[] args) + { + setOptions(options); + if (parseCommandline(args)) + { + execute(); + } + } + + protected boolean parseCommandline(String[] args) + { + try + { + commandLine = new PosixParser().parse(options, args); + + return true; + } + catch (ParseException e) + { + System.err.println("Error: " + e.getMessage()); + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("Qpid", options, true); + + return false; + } + } + + @SuppressWarnings("static-access") + protected void setOptions(Options options) + { + Option help = new Option("h", "help", false, "print this message"); + Option version = new Option("v", "version", false, "print the version information and exit"); + Option configFile = + OptionBuilder.withArgName("file").hasArg().withDescription("use given configuration file").withLongOpt("config") + .create("c"); + Option port = + OptionBuilder.withArgName("port").hasArg() + .withDescription("listen on the specified port. Overrides any value in the config file") + .withLongOpt("port").create("p"); + + Option exclude0_10 = + OptionBuilder.withArgName("exclude-0-10").hasArg() + .withDescription("when listening on the specified port do not accept AMQP0-10 connections. The specified port must be one specified on the command line") + .withLongOpt("exclude-0-10").create(); + + Option exclude0_9_1 = + OptionBuilder.withArgName("exclude-0-9-1").hasArg() + .withDescription("when listening on the specified port do not accept AMQP0-9-1 connections. The specified port must be one specified on the command line") + .withLongOpt("exclude-0-9-1").create(); + + + Option exclude0_9 = + OptionBuilder.withArgName("exclude-0-9").hasArg() + .withDescription("when listening on the specified port do not accept AMQP0-9 connections. The specified port must be one specified on the command line") + .withLongOpt("exclude-0-9").create(); + + + Option exclude0_8 = + OptionBuilder.withArgName("exclude-0-8").hasArg() + .withDescription("when listening on the specified port do not accept AMQP0-8 connections. The specified port must be one specified on the command line") + .withLongOpt("exclude-0-8").create(); + + + Option mport = + OptionBuilder.withArgName("mport").hasArg() + .withDescription("listen on the specified management port. Overrides any value in the config file") + .withLongOpt("mport").create("m"); + + + Option bind = + OptionBuilder.withArgName("bind").hasArg() + .withDescription("bind to the specified address. Overrides any value in the config file") + .withLongOpt("bind").create("b"); + Option logconfig = + OptionBuilder.withArgName("logconfig").hasArg() + .withDescription("use the specified log4j xml configuration file. By " + + "default looks for a file named " + DEFAULT_LOG_CONFIG_FILENAME + + " in the same directory as the configuration file").withLongOpt("logconfig").create("l"); + Option logwatchconfig = + OptionBuilder.withArgName("logwatch").hasArg() + .withDescription("monitor the log file configuration file for changes. Units are seconds. " + + "Zero means do not check for changes.").withLongOpt("logwatch").create("w"); + + options.addOption(help); + options.addOption(version); + options.addOption(configFile); + options.addOption(logconfig); + options.addOption(logwatchconfig); + options.addOption(port); + options.addOption(exclude0_10); + options.addOption(exclude0_9_1); + options.addOption(exclude0_9); + options.addOption(exclude0_8); + options.addOption(mport); + options.addOption(bind); + } + + protected void execute() + { + // note this understands either --help or -h. If an option only has a long name you can use that but if + // an option has a short name and a long name you must use the short name here. + if (commandLine.hasOption("h")) + { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("Qpid", options, true); + } + else if (commandLine.hasOption("v")) + { + String ver = QpidProperties.getVersionString(); + + StringBuilder protocol = new StringBuilder("AMQP version(s) [major.minor]: "); + + boolean first = true; + for (ProtocolVersion pv : ProtocolVersion.getSupportedProtocolVersions()) + { + if (first) + { + first = false; + } + else + { + protocol.append(", "); + } + + protocol.append(pv.getMajorVersion()).append('-').append(pv.getMinorVersion()); + + } + + System.out.println(ver + " (" + protocol + ")"); + } + else + { + try + { + CurrentActor.set(new BrokerActor(new SystemOutMessageLogger())); + startup(); + CurrentActor.remove(); + } + catch (InitException e) + { + System.out.println("Initialisation Error : " + e.getMessage()); + shutdown(1); + } + catch (Throwable e) + { + System.out.println("Error initialising message broker: " + e); + e.printStackTrace(); + shutdown(1); + } + } + } + + protected void shutdown(int status) + { + ApplicationRegistry.removeAll(); + System.exit(status); + } + + protected void startup() throws Exception + { + final String QpidHome = System.getProperty(QPID_HOME); + final File defaultConfigFile = new File(QpidHome, DEFAULT_CONFIG_FILE); + final File configFile = new File(commandLine.getOptionValue("c", defaultConfigFile.getPath())); + if (!configFile.exists()) + { + String error = "File " + configFile + " could not be found. Check the file exists and is readable."; + + if (QpidHome == null) + { + error = error + "\nNote: " + QPID_HOME + " is not set."; + } + + throw new InitException(error, null); + } + else + { + CurrentActor.get().message(BrokerMessages.CONFIG(configFile.getAbsolutePath())); + } + + String logConfig = commandLine.getOptionValue("l"); + String logWatchConfig = commandLine.getOptionValue("w", "0"); + + int logWatchTime = 0; + try + { + logWatchTime = Integer.parseInt(logWatchConfig); + } + catch (NumberFormatException e) + { + System.err.println("Log watch configuration value of " + logWatchConfig + " is invalid. Must be " + + "a non-negative integer. Using default of zero (no watching configured"); + } + + File logConfigFile; + if (logConfig != null) + { + logConfigFile = new File(logConfig); + configureLogging(logConfigFile, logWatchTime); + } + else + { + File configFileDirectory = configFile.getParentFile(); + logConfigFile = new File(configFileDirectory, DEFAULT_LOG_CONFIG_FILENAME); + configureLogging(logConfigFile, logWatchTime); + } + + ConfigurationFileApplicationRegistry config = new ConfigurationFileApplicationRegistry(configFile); + ServerConfiguration serverConfig = config.getConfiguration(); + updateManagementPort(serverConfig, commandLine.getOptionValue("m")); + + ApplicationRegistry.initialise(config); + + // We have already loaded the BrokerMessages class by this point so we + // need to refresh the locale setting incase we had a different value in + // the configuration. + BrokerMessages.reload(); + + // AR.initialise() sets and removes its own actor so we now need to set the actor + // for the remainder of the startup, and the default actor if the stack is empty + CurrentActor.set(new BrokerActor(config.getCompositeStartupMessageLogger())); + CurrentActor.setDefault(new BrokerActor(config.getRootMessageLogger())); + GenericActor.setDefaultMessageLogger(config.getRootMessageLogger()); + + + try + { + configureLoggingManagementMBean(logConfigFile, logWatchTime); + + ConfigurationManagementMBean configMBean = new ConfigurationManagementMBean(); + configMBean.register(); + + ServerInformationMBean sysInfoMBean = new ServerInformationMBean(config); + sysInfoMBean.register(); + + + String[] portStr = commandLine.getOptionValues("p"); + + Set<Integer> ports = new HashSet<Integer>(); + Set<Integer> exclude_0_10 = new HashSet<Integer>(); + Set<Integer> exclude_0_9_1 = new HashSet<Integer>(); + Set<Integer> exclude_0_9 = new HashSet<Integer>(); + Set<Integer> exclude_0_8 = new HashSet<Integer>(); + + if(portStr == null || portStr.length == 0) + { + + parsePortList(ports, serverConfig.getPorts()); + parsePortList(exclude_0_10, serverConfig.getPortExclude010()); + parsePortList(exclude_0_9_1, serverConfig.getPortExclude091()); + parsePortList(exclude_0_9, serverConfig.getPortExclude09()); + parsePortList(exclude_0_8, serverConfig.getPortExclude08()); + + } + else + { + parsePortArray(ports, portStr); + parsePortArray(exclude_0_10, commandLine.getOptionValues("exclude-0-10")); + parsePortArray(exclude_0_9_1, commandLine.getOptionValues("exclude-0-9-1")); + parsePortArray(exclude_0_9, commandLine.getOptionValues("exclude-0-9")); + parsePortArray(exclude_0_8, commandLine.getOptionValues("exclude-0-8")); + + } + + + + + String bindAddr = commandLine.getOptionValue("b"); + if (bindAddr == null) + { + bindAddr = serverConfig.getBind(); + } + InetAddress bindAddress = null; + + + + if (bindAddr.equals("wildcard")) + { + bindAddress = new InetSocketAddress(0).getAddress(); + } + else + { + bindAddress = InetAddress.getByAddress(parseIP(bindAddr)); + } + + String hostName = bindAddress.getCanonicalHostName(); + + + String keystorePath = serverConfig.getKeystorePath(); + String keystorePassword = serverConfig.getKeystorePassword(); + String certType = serverConfig.getCertType(); + SSLContextFactory sslFactory = null; + + if (!serverConfig.getSSLOnly()) + { + + for(int port : ports) + { + + NetworkDriver driver = new MINANetworkDriver(); + + Set<VERSION> supported = EnumSet.allOf(VERSION.class); + + if(exclude_0_10.contains(port)) + { + supported.remove(VERSION.v0_10); + } + + if(exclude_0_9_1.contains(port)) + { + supported.remove(VERSION.v0_9_1); + } + if(exclude_0_9.contains(port)) + { + supported.remove(VERSION.v0_9); + } + if(exclude_0_8.contains(port)) + { + supported.remove(VERSION.v0_8); + } + + MultiVersionProtocolEngineFactory protocolEngineFactory = + new MultiVersionProtocolEngineFactory(hostName, supported); + + + + driver.bind(port, new InetAddress[]{bindAddress}, protocolEngineFactory, + serverConfig.getNetworkConfiguration(), null); + ApplicationRegistry.getInstance().addAcceptor(new InetSocketAddress(bindAddress, port), + new QpidAcceptor(driver,"TCP")); + CurrentActor.get().message(BrokerMessages.LISTENING("TCP", port)); + + } + + } + + if (serverConfig.getEnableSSL()) + { + sslFactory = new SSLContextFactory(keystorePath, keystorePassword, certType); + NetworkDriver driver = new MINANetworkDriver(); + driver.bind(serverConfig.getSSLPort(), new InetAddress[]{bindAddress}, + new AMQProtocolEngineFactory(), serverConfig.getNetworkConfiguration(), sslFactory); + ApplicationRegistry.getInstance().addAcceptor(new InetSocketAddress(bindAddress, serverConfig.getSSLPort()), + new QpidAcceptor(driver,"TCP")); + CurrentActor.get().message(BrokerMessages.LISTENING("TCP/SSL", serverConfig.getSSLPort())); + } + + CurrentActor.get().message(BrokerMessages.READY()); + + } + finally + { + // Startup is complete so remove the AR initialised Startup actor + CurrentActor.remove(); + } + + + + } + + private void parsePortArray(Set<Integer> ports, String[] portStr) + throws InitException + { + if(portStr != null) + { + for(int i = 0; i < portStr.length; i++) + { + try + { + ports.add(Integer.parseInt(portStr[i])); + } + catch (NumberFormatException e) + { + throw new InitException("Invalid port: " + portStr[i], e); + } + } + } + } + + private void parsePortList(Set<Integer> output, List input) + throws InitException + { + if(input != null) + { + for(Object port : input) + { + try + { + output.add(Integer.parseInt(String.valueOf(port))); + } + catch (NumberFormatException e) + { + throw new InitException("Invalid port: " + port, e); + } + } + } + } + + /** + * Update the configuration data with the management port. + * @param configuration + * @param managementPort The string from the command line + */ + private void updateManagementPort(ServerConfiguration configuration, String managementPort) + { + if (managementPort != null) + { + try + { + configuration.setJMXManagementPort(Integer.parseInt(managementPort)); + } + catch (NumberFormatException e) + { + _logger.warn("Invalid management port: " + managementPort + " will use:" + configuration.getJMXManagementPort(), e); + } + } + } + + public static void main(String[] args) + { + //if the -Dlog4j.configuration property has not been set, enable the init override + //to stop Log4J wondering off and picking up the first log4j.xml/properties file it + //finds from the classpath when we get the first Loggers + if(System.getProperty("log4j.configuration") == null) + { + System.setProperty("log4j.defaultInitOverride", "true"); + } + + //now that the override status is know, we can instantiate the Loggers + _logger = Logger.getLogger(Main.class); + + new Main(args); + } + + private byte[] parseIP(String address) throws Exception + { + char[] literalBuffer = address.toCharArray(); + int byteCount = 0; + int currByte = 0; + byte[] ip = new byte[IPV4_ADDRESS_LENGTH]; + for (int i = 0; i < literalBuffer.length; i++) + { + char currChar = literalBuffer[i]; + if ((currChar >= '0') && (currChar <= '9')) + { + currByte = (currByte * 10) + (Character.digit(currChar, 10) & 0xFF); + } + + if (currChar == IPV4_LITERAL_SEPARATOR || (i + 1 == literalBuffer.length)) + { + ip[byteCount++] = (byte) currByte; + currByte = 0; + } + } + + if (byteCount != 4) + { + throw new Exception("Invalid IP address: " + address); + } + return ip; + } + + private void configureLogging(File logConfigFile, int logWatchTime) throws InitException, IOException + { + if (logConfigFile.exists() && logConfigFile.canRead()) + { + CurrentActor.get().message(BrokerMessages.LOG_CONFIG(logConfigFile.getAbsolutePath())); + + if (logWatchTime > 0) + { + System.out.println("log file " + logConfigFile.getAbsolutePath() + " will be checked for changes every " + + logWatchTime + " seconds"); + // log4j expects the watch interval in milliseconds + try + { + QpidLog4JConfigurator.configureAndWatch(logConfigFile.getPath(), logWatchTime * 1000); + } + catch (Exception e) + { + throw new InitException(e.getMessage(),e); + } + } + else + { + try + { + QpidLog4JConfigurator.configure(logConfigFile.getPath()); + } + catch (Exception e) + { + throw new InitException(e.getMessage(),e); + } + } + } + else + { + System.err.println("Logging configuration error: unable to read file " + logConfigFile.getAbsolutePath()); + System.err.println("Using the fallback internal log4j.properties configuration"); + + InputStream propsFile = this.getClass().getResourceAsStream("/log4j.properties"); + if(propsFile == null) + { + throw new IOException("Unable to load the fallback internal log4j.properties configuration file"); + } + else + { + try + { + Properties fallbackProps = new Properties(); + fallbackProps.load(propsFile); + PropertyConfigurator.configure(fallbackProps); + } + finally + { + propsFile.close(); + } + } + } + } + + private void configureLoggingManagementMBean(File logConfigFile, int logWatchTime) throws Exception + { + LoggingManagementMBean blm = new LoggingManagementMBean(logConfigFile.getPath(),logWatchTime); + + blm.register(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMap.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMap.java new file mode 100644 index 0000000000..3bad73d86d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMap.java @@ -0,0 +1,72 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.ack; + +import java.util.Collection; +import java.util.Set; +import java.util.Map; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.QueueEntry; + + +public interface UnacknowledgedMessageMap +{ + public interface Visitor + { + /** + * @param deliveryTag + *@param message the message being iterated over @return true to stop iteration, false to continue + * @throws AMQException + */ + boolean callback(final long deliveryTag, QueueEntry message) throws AMQException; + + void visitComplete(); + } + + void visit(Visitor visitor) throws AMQException; + + void add(long deliveryTag, QueueEntry message); + + void collect(long deliveryTag, boolean multiple, Map<Long, QueueEntry> msgs); + + void remove(Map<Long,QueueEntry> msgs); + + QueueEntry remove(long deliveryTag); + + Collection<QueueEntry> cancelAllMessages(); + + int size(); + + void clear(); + + QueueEntry get(long deliveryTag); + + /** + * Get the set of delivery tags that are outstanding. + * + * @return a set of delivery tags + */ + Set<Long> getDeliveryTags(); + +} + + diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMapImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMapImpl.java new file mode 100644 index 0000000000..d920d97c1a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/ack/UnacknowledgedMessageMapImpl.java @@ -0,0 +1,175 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.ack; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.QueueEntry; + +public class UnacknowledgedMessageMapImpl implements UnacknowledgedMessageMap +{ + private final Object _lock = new Object(); + + private long _unackedSize; + + private Map<Long, QueueEntry> _map; + + private long _lastDeliveryTag; + + private final int _prefetchLimit; + + public UnacknowledgedMessageMapImpl(int prefetchLimit) + { + _prefetchLimit = prefetchLimit; + _map = new LinkedHashMap<Long, QueueEntry>(prefetchLimit); + } + + public void collect(long deliveryTag, boolean multiple, Map<Long, QueueEntry> msgs) + { + if (multiple) + { + collect(deliveryTag, msgs); + } + else + { + final QueueEntry entry = get(deliveryTag); + if(entry != null) + { + msgs.put(deliveryTag, entry); + } + } + + } + + public void remove(Map<Long,QueueEntry> msgs) + { + synchronized (_lock) + { + for (Long deliveryTag : msgs.keySet()) + { + remove(deliveryTag); + } + } + } + + public QueueEntry remove(long deliveryTag) + { + synchronized (_lock) + { + + QueueEntry message = _map.remove(deliveryTag); + if(message != null) + { + _unackedSize -= message.getMessage().getSize(); + + } + + return message; + } + } + + public void visit(Visitor visitor) throws AMQException + { + synchronized (_lock) + { + Set<Map.Entry<Long, QueueEntry>> currentEntries = _map.entrySet(); + for (Map.Entry<Long, QueueEntry> entry : currentEntries) + { + visitor.callback(entry.getKey().longValue(), entry.getValue()); + } + visitor.visitComplete(); + } + } + + public void add(long deliveryTag, QueueEntry message) + { + synchronized (_lock) + { + _map.put(deliveryTag, message); + _unackedSize += message.getMessage().getSize(); + _lastDeliveryTag = deliveryTag; + } + } + + public Collection<QueueEntry> cancelAllMessages() + { + synchronized (_lock) + { + Collection<QueueEntry> currentEntries = _map.values(); + _map = new LinkedHashMap<Long, QueueEntry>(_prefetchLimit); + _unackedSize = 0l; + return currentEntries; + } + } + + public int size() + { + synchronized (_lock) + { + return _map.size(); + } + } + + public void clear() + { + synchronized (_lock) + { + _map.clear(); + _unackedSize = 0l; + } + } + + public QueueEntry get(long key) + { + synchronized (_lock) + { + return _map.get(key); + } + } + + public Set<Long> getDeliveryTags() + { + synchronized (_lock) + { + return _map.keySet(); + } + } + + private void collect(long key, Map<Long, QueueEntry> msgs) + { + synchronized (_lock) + { + for (Map.Entry<Long, QueueEntry> entry : _map.entrySet()) + { + msgs.put(entry.getKey(),entry.getValue()); + if (entry.getKey() == key) + { + break; + } + } + } + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/binding/Binding.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/binding/Binding.java new file mode 100644 index 0000000000..60c9a86b76 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/binding/Binding.java @@ -0,0 +1,118 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.binding; + +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.queue.AMQQueue; + +import java.util.Collections; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; + +public class Binding +{ + private final String _bindingKey; + private final AMQQueue _queue; + private final Exchange _exchange; + private final Map<String, Object> _arguments; + private final UUID _id; + private final AtomicLong _matches = new AtomicLong(); + + public Binding(UUID id, final String bindingKey, final AMQQueue queue, final Exchange exchange, final Map<String, Object> arguments) + { + _id = id; + _bindingKey = bindingKey; + _queue = queue; + _exchange = exchange; + _arguments = arguments == null ? Collections.EMPTY_MAP : Collections.unmodifiableMap(arguments); + } + + public UUID getId() + { + return _id; + } + + public String getBindingKey() + { + return _bindingKey; + } + + public AMQQueue getQueue() + { + return _queue; + } + + public Exchange getExchange() + { + return _exchange; + } + + public Map<String, Object> getArguments() + { + return _arguments; + } + + public void incrementMatches() + { + _matches.incrementAndGet(); + } + + public long getMatches() + { + return _matches.get(); + } + + boolean isDurable() + { + return _queue.isDurable() && _exchange.isDurable(); + } + + @Override + public boolean equals(final Object o) + { + if (this == o) + { + return true; + } + + if (o == null || !(o instanceof Binding)) + { + return false; + } + + final Binding binding = (Binding) o; + + return (_bindingKey == null ? binding.getBindingKey() == null : _bindingKey.equals(binding.getBindingKey())) + && (_exchange == null ? binding.getExchange() == null : _exchange.equals(binding.getExchange())) + && (_queue == null ? binding.getQueue() == null : _queue.equals(binding.getQueue())); + } + + @Override + public int hashCode() + { + int result = _bindingKey == null ? 1 : _bindingKey.hashCode(); + result = 31 * result + (_queue == null ? 3 : _queue.hashCode()); + result = 31 * result + (_exchange == null ? 5 : _exchange.hashCode()); + return result; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/binding/BindingFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/binding/BindingFactory.java new file mode 100644 index 0000000000..400ce50bc4 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/binding/BindingFactory.java @@ -0,0 +1,289 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.binding; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQInternalException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.configuration.BindingConfig; +import org.apache.qpid.server.configuration.BindingConfigType; +import org.apache.qpid.server.configuration.ConfigStore; +import org.apache.qpid.server.configuration.ConfiguredObject; +import org.apache.qpid.server.configuration.QueueConfiguration; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.BindingMessages; +import org.apache.qpid.server.logging.subjects.BindingLogSubject; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.store.DurableConfigurationStore; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class BindingFactory +{ + private final VirtualHost _virtualHost; + private final DurableConfigurationStore.Source _configSource; + private final Exchange _defaultExchange; + + private final ConcurrentHashMap<BindingImpl, BindingImpl> _bindings = new ConcurrentHashMap<BindingImpl, BindingImpl>(); + + + public BindingFactory(final VirtualHost vhost) + { + this(vhost, vhost.getExchangeRegistry().getDefaultExchange()); + } + + public BindingFactory(final DurableConfigurationStore.Source configSource, final Exchange defaultExchange) + { + _configSource = configSource; + _defaultExchange = defaultExchange; + if (configSource instanceof VirtualHost) + { + _virtualHost = (VirtualHost) configSource; + } + else + { + _virtualHost = null; + } + } + + public VirtualHost getVirtualHost() + { + return _virtualHost; + } + + + + private final class BindingImpl extends Binding implements AMQQueue.Task, Exchange.Task, BindingConfig + { + private final BindingLogSubject _logSubject; + //TODO : persist creation time + private long _createTime = System.currentTimeMillis(); + + private BindingImpl(String bindingKey, final AMQQueue queue, final Exchange exchange, final Map<String, Object> arguments) + { + super(queue.getVirtualHost().getConfigStore().createId(), bindingKey, queue, exchange, arguments); + _logSubject = new BindingLogSubject(bindingKey,exchange,queue); + + } + + + public void doTask(final AMQQueue queue) throws AMQException + { + removeBinding(this); + } + + public void onClose(final Exchange exchange) throws AMQSecurityException, AMQInternalException + { + removeBinding(this); + } + + void logCreation() + { + CurrentActor.get().message(_logSubject, BindingMessages.CREATED(String.valueOf(getArguments()), getArguments() != null && !getArguments().isEmpty())); + } + + void logDestruction() + { + CurrentActor.get().message(_logSubject, BindingMessages.DELETED()); + } + + public String getOrigin() + { + return (String) getArguments().get("qpid.fed.origin"); + } + + public long getCreateTime() + { + return _createTime; + } + + public BindingConfigType getConfigType() + { + return BindingConfigType.getInstance(); + } + + public ConfiguredObject getParent() + { + return _virtualHost; + } + + public boolean isDurable() + { + return getQueue().isDurable() && getExchange().isDurable(); + } + + } + + + + public boolean addBinding(String bindingKey, AMQQueue queue, Exchange exchange, Map<String, Object> arguments) throws AMQSecurityException, AMQInternalException + { + return makeBinding(bindingKey, queue, exchange, arguments, false, false); + } + + + public boolean replaceBinding(final String bindingKey, + final AMQQueue queue, + final Exchange exchange, + final Map<String, Object> arguments) throws AMQSecurityException, AMQInternalException + { + return makeBinding(bindingKey, queue, exchange, arguments, false, true); + } + + private boolean makeBinding(String bindingKey, AMQQueue queue, Exchange exchange, Map<String, Object> arguments, boolean restore, boolean force) throws AMQSecurityException, AMQInternalException + { + assert queue != null; + if (bindingKey == null) + { + bindingKey = ""; + } + if (exchange == null) + { + exchange = _defaultExchange; + } + if (arguments == null) + { + arguments = Collections.emptyMap(); + } + + //Perform ACLs + if (!getVirtualHost().getSecurityManager().authoriseBind(exchange, queue, new AMQShortString(bindingKey))) + { + throw new AMQSecurityException("Permission denied: binding " + bindingKey); + } + + BindingImpl b = new BindingImpl(bindingKey,queue,exchange,arguments); + BindingImpl existingMapping = _bindings.putIfAbsent(b,b); + if (existingMapping == null || force) + { + if (existingMapping != null) + { + removeBinding(existingMapping); + } + + if (b.isDurable() && !restore) + { + _configSource.getDurableConfigurationStore().bindQueue(exchange,new AMQShortString(bindingKey),queue,FieldTable.convertToFieldTable(arguments)); + } + + queue.addQueueDeleteTask(b); + exchange.addCloseTask(b); + queue.addBinding(b); + exchange.addBinding(b); + getConfigStore().addConfiguredObject(b); + b.logCreation(); + + return true; + } + else + { + return false; + } + } + + private ConfigStore getConfigStore() + { + return getVirtualHost().getConfigStore(); + } + + public void restoreBinding(final String bindingKey, final AMQQueue queue, final Exchange exchange, final Map<String, Object> argumentMap) throws AMQSecurityException, AMQInternalException + { + makeBinding(bindingKey,queue,exchange,argumentMap,true, false); + } + + public void removeBinding(final Binding b) throws AMQSecurityException, AMQInternalException + { + removeBinding(b.getBindingKey(), b.getQueue(), b.getExchange(), b.getArguments()); + } + + + public Binding removeBinding(String bindingKey, AMQQueue queue, Exchange exchange, Map<String, Object> arguments) throws AMQSecurityException, AMQInternalException + { + assert queue != null; + if (bindingKey == null) + { + bindingKey = ""; + } + if (exchange == null) + { + exchange = _defaultExchange; + } + if (arguments == null) + { + arguments = Collections.emptyMap(); + } + + // Check access + if (!getVirtualHost().getSecurityManager().authoriseUnbind(exchange, new AMQShortString(bindingKey), queue)) + { + throw new AMQSecurityException("Permission denied: binding " + bindingKey); + } + + BindingImpl b = _bindings.remove(new BindingImpl(bindingKey,queue,exchange,arguments)); + + if (b != null) + { + exchange.removeBinding(b); + queue.removeBinding(b); + exchange.removeCloseTask(b); + queue.removeQueueDeleteTask(b); + + if (b.isDurable()) + { + _configSource.getDurableConfigurationStore().unbindQueue(exchange, + new AMQShortString(bindingKey), + queue, + FieldTable.convertToFieldTable(arguments)); + } + b.logDestruction(); + getConfigStore().removeConfiguredObject(b); + } + + return b; + } + + public Binding getBinding(String bindingKey, AMQQueue queue, Exchange exchange, Map<String, Object> arguments) + { + assert queue != null; + if(bindingKey == null) + { + bindingKey = ""; + } + if(exchange == null) + { + exchange = _defaultExchange; + } + if(arguments == null) + { + arguments = Collections.emptyMap(); + } + + BindingImpl b = new BindingImpl(bindingKey,queue,exchange,arguments); + return _bindings.get(b); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/BindingConfig.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/BindingConfig.java new file mode 100644 index 0000000000..233134abc5 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/BindingConfig.java @@ -0,0 +1,41 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import java.util.Map; + + +public interface BindingConfig extends ConfiguredObject<BindingConfigType, BindingConfig> +{ + + ExchangeConfig getExchange(); + + QueueConfig getQueue(); + + String getBindingKey(); + + Map<String, Object> getArguments(); + + String getOrigin(); + + long getMatches(); +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/BindingConfigType.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/BindingConfigType.java new file mode 100644 index 0000000000..5cd064ff42 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/BindingConfigType.java @@ -0,0 +1,112 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import org.apache.qpid.server.exchange.ExchangeType; + +import java.util.*; + +public final class BindingConfigType extends ConfigObjectType<BindingConfigType, BindingConfig> +{ + private static final List<BindingProperty<?>> BINDING_PROPERTIES = new ArrayList<BindingProperty<?>>(); + + public static interface BindingProperty<S> extends ConfigProperty<BindingConfigType, BindingConfig, S> + { + } + + private abstract static class BindingReadWriteProperty<S> extends ConfigProperty.ReadWriteConfigProperty<BindingConfigType, BindingConfig, S> implements BindingProperty<S> + { + public BindingReadWriteProperty(String name) + { + super(name); + BINDING_PROPERTIES.add(this); + } + } + + private abstract static class BindingReadOnlyProperty<S> extends ConfigProperty.ReadOnlyConfigProperty<BindingConfigType, BindingConfig, S> implements BindingProperty<S> + { + public BindingReadOnlyProperty(String name) + { + super(name); + BINDING_PROPERTIES.add(this); + } + } + + public static final BindingReadOnlyProperty<ExchangeConfig> EXCHANGE_PROPERTY = new BindingReadOnlyProperty<ExchangeConfig>("exchange") + { + public ExchangeConfig getValue(BindingConfig object) + { + return object.getExchange(); + } + }; + + public static final BindingReadOnlyProperty<QueueConfig> QUEUE_PROPERTY = new BindingReadOnlyProperty<QueueConfig>("queue") + { + public QueueConfig getValue(BindingConfig object) + { + return object.getQueue(); + } + }; + + public static final BindingReadOnlyProperty<String> BINDING_KEY_PROPERTY = new BindingReadOnlyProperty<String>("bindingKey") + { + public String getValue(BindingConfig object) + { + return object.getBindingKey(); + } + }; + + public static final BindingReadOnlyProperty<Map<String,Object>> ARGUMENTS = new BindingReadOnlyProperty<Map<String,Object>>("arguments") + { + public Map<String,Object> getValue(BindingConfig object) + { + return object.getArguments(); + } + }; + + public static final BindingReadOnlyProperty<String> ORIGIN_PROPERTY = new BindingReadOnlyProperty<String>("origin") + { + public String getValue(BindingConfig object) + { + return object.getOrigin(); + } + }; + + private static final BindingConfigType INSTANCE = new BindingConfigType(); + + private BindingConfigType() + { + } + + public Collection<BindingProperty<?>> getProperties() + { + return Collections.unmodifiableList(BINDING_PROPERTIES); + } + + public static BindingConfigType getInstance() + { + return INSTANCE; + } + + + +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/BridgeConfig.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/BridgeConfig.java new file mode 100644 index 0000000000..00ed5fd0dd --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/BridgeConfig.java @@ -0,0 +1,48 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +public interface BridgeConfig extends ConfiguredObject<BridgeConfigType, BridgeConfig> +{ + + boolean isDynamic(); + + boolean isQueueBridge(); + + boolean isLocalSource(); + + String getSource(); + + String getDestination(); + + String getKey(); + + String getTag(); + + String getExcludes(); + + LinkConfig getLink(); + + Integer getChannelId(); + + int getAckBatching(); +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/BridgeConfigType.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/BridgeConfigType.java new file mode 100644 index 0000000000..a8d3cd9ec3 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/BridgeConfigType.java @@ -0,0 +1,169 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import org.apache.qpid.server.exchange.ExchangeType; + +import java.util.*; + +public final class BridgeConfigType extends ConfigObjectType<BridgeConfigType, BridgeConfig> +{ + private static final List<BridgeProperty<?>> BRIDGE_PROPERTIES = new ArrayList<BridgeProperty<?>>(); + + public static interface BridgeProperty<S> extends ConfigProperty<BridgeConfigType, BridgeConfig, S> + { + } + + private abstract static class BridgeReadWriteProperty<S> extends ConfigProperty.ReadWriteConfigProperty<BridgeConfigType, BridgeConfig, S> implements BridgeProperty<S> + { + public BridgeReadWriteProperty(String name) + { + super(name); + BRIDGE_PROPERTIES.add(this); + } + } + + private abstract static class BridgeReadOnlyProperty<S> extends ConfigProperty.ReadOnlyConfigProperty<BridgeConfigType, BridgeConfig, S> implements BridgeProperty<S> + { + public BridgeReadOnlyProperty(String name) + { + super(name); + BRIDGE_PROPERTIES.add(this); + } + } + + public static final BridgeReadOnlyProperty<LinkConfig> LINK_PROPERTY = new BridgeReadOnlyProperty<LinkConfig>("link") + { + public LinkConfig getValue(BridgeConfig object) + { + return object.getLink(); + } + }; + + public static final BridgeReadOnlyProperty<Integer> CHANNEL_ID_PROPERTY = new BridgeReadOnlyProperty<Integer>("channelId") + { + public Integer getValue(BridgeConfig object) + { + return object.getChannelId(); + } + }; + + public static final BridgeReadOnlyProperty<Boolean> DURABLE_PROPERTY = new BridgeReadOnlyProperty<Boolean>("durable") + { + public Boolean getValue(BridgeConfig object) + { + return object.isDurable(); + } + }; + + public static final BridgeReadOnlyProperty<String> SOURCE_PROPERTY = new BridgeReadOnlyProperty<String>("source") + { + public String getValue(BridgeConfig object) + { + return object.getSource(); + } + }; + + public static final BridgeReadOnlyProperty<String> DESTINATION_PROPERTY = new BridgeReadOnlyProperty<String>("destination") + { + public String getValue(BridgeConfig object) + { + return object.getDestination(); + } + }; + + public static final BridgeReadOnlyProperty<String> KEY_PROPERTY = new BridgeReadOnlyProperty<String>("key") + { + public String getValue(BridgeConfig object) + { + return object.getKey(); + } + }; + + public static final BridgeReadOnlyProperty<Boolean> QUEUE_BRIDGE_PROPERTY = new BridgeReadOnlyProperty<Boolean>("queueBridge") + { + public Boolean getValue(BridgeConfig object) + { + return object.isQueueBridge(); + } + }; + + public static final BridgeReadOnlyProperty<Boolean> LOCAL_SOURCE_PROPERTY = new BridgeReadOnlyProperty<Boolean>("localSource") + { + public Boolean getValue(BridgeConfig object) + { + return object.isLocalSource(); + } + }; + + public static final BridgeReadOnlyProperty<String> TAG_PROPERTY = new BridgeReadOnlyProperty<String>("tag") + { + public String getValue(BridgeConfig object) + { + return object.getTag(); + } + }; + + public static final BridgeReadOnlyProperty<String> EXCLUDES_PROPERTY = new BridgeReadOnlyProperty<String>("excludes") + { + public String getValue(BridgeConfig object) + { + return object.getExcludes(); + } + }; + + public static final BridgeReadOnlyProperty<Boolean> DYNAMIC_PROPERTY = new BridgeReadOnlyProperty<Boolean>("dynamic") + { + public Boolean getValue(BridgeConfig object) + { + return object.isDynamic(); + } + }; + + public static final BridgeReadOnlyProperty<Integer> ACK_BATCHING_PROPERTY = new BridgeReadOnlyProperty<Integer>("ackBatching") + { + public Integer getValue(BridgeConfig object) + { + return object.getAckBatching(); + } + }; + + + private static final BridgeConfigType INSTANCE = new BridgeConfigType(); + + private BridgeConfigType() + { + } + + public Collection<BridgeProperty<?>> getProperties() + { + return Collections.unmodifiableList(BRIDGE_PROPERTIES); + } + + public static BridgeConfigType getInstance() + { + return INSTANCE; + } + + + +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/BrokerConfig.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/BrokerConfig.java new file mode 100644 index 0000000000..5cdb886821 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/BrokerConfig.java @@ -0,0 +1,57 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + + +public interface BrokerConfig extends ConfiguredObject<BrokerConfigType,BrokerConfig> +{ + void setSystem(SystemConfig system); + + SystemConfig getSystem(); + + Integer getPort(); + + Integer getWorkerThreads(); + + Integer getMaxConnections(); + + Integer getConnectionBacklogLimit(); + + Long getStagingThreshold(); + + Integer getManagementPublishInterval(); + + String getVersion(); + + String getDataDirectory(); + + void addVirtualHost(VirtualHostConfig virtualHost); + + void createBrokerConnection(String transport, + String host, + int port, + boolean durable, + String authMechanism, + String username, String password); + + String getFederationTag(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/BrokerConfigType.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/BrokerConfigType.java new file mode 100644 index 0000000000..82b2fc82d2 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/BrokerConfigType.java @@ -0,0 +1,143 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import java.util.*; +import java.io.File; + +public final class BrokerConfigType extends ConfigObjectType<BrokerConfigType, BrokerConfig> +{ + private static final List<BrokerProperty<?>> BROKER_PROPERTIES = new ArrayList<BrokerProperty<?>>(); + + public static interface BrokerProperty<S> extends ConfigProperty<BrokerConfigType, BrokerConfig, S> + { + } + + private abstract static class BrokerReadWriteProperty<S> extends ConfigProperty.ReadWriteConfigProperty<BrokerConfigType, BrokerConfig, S> implements BrokerProperty<S> + { + public BrokerReadWriteProperty(String name) + { + super(name); + BROKER_PROPERTIES.add(this); + } + } + + private abstract static class BrokerReadOnlyProperty<S> extends ConfigProperty.ReadOnlyConfigProperty<BrokerConfigType, BrokerConfig, S> implements BrokerProperty<S> + { + public BrokerReadOnlyProperty(String name) + { + super(name); + BROKER_PROPERTIES.add(this); + } + } + + public static final BrokerReadOnlyProperty<SystemConfig> SYSTEM_PROPERTY = new BrokerReadOnlyProperty<SystemConfig>("system") + { + public SystemConfig getValue(BrokerConfig object) + { + return object.getSystem(); + } + }; + + public static final BrokerReadOnlyProperty<Integer> PORT_PROPERTY = new BrokerReadOnlyProperty<Integer>("port") + { + public Integer getValue(BrokerConfig object) + { + return object.getPort(); + } + }; + + public static final BrokerReadOnlyProperty<Integer> WORKER_THREADS_PROPERTY = new BrokerReadOnlyProperty<Integer>("workerThreads") + { + public Integer getValue(BrokerConfig object) + { + return object.getWorkerThreads(); + } + }; + + public static final BrokerReadOnlyProperty<Integer> MAX_CONNECTIONS_PROPERTY = new BrokerReadOnlyProperty<Integer>("maxConnections") + { + public Integer getValue(BrokerConfig object) + { + return object.getMaxConnections(); + } + }; + + public static final BrokerReadOnlyProperty<Integer> CONNECTION_BACKLOG_LIMIT_PROPERTY = new BrokerReadOnlyProperty<Integer>("connectionBacklog") + { + public Integer getValue(BrokerConfig object) + { + return object.getConnectionBacklogLimit(); + } + }; + + public static final BrokerReadOnlyProperty<Long> STAGING_THRESHOLD_PROPERTY = new BrokerReadOnlyProperty<Long>("stagingThreshold") + { + public Long getValue(BrokerConfig object) + { + return object.getStagingThreshold(); + } + }; + + public static final BrokerReadOnlyProperty<Integer> MANAGEMENT_PUBLISH_INTERVAL_PROPERTY = new BrokerReadOnlyProperty<Integer>("mgmtPublishInterval") + { + public Integer getValue(BrokerConfig object) + { + return object.getManagementPublishInterval(); + } + }; + + public static final BrokerReadOnlyProperty<String> VERSION_PROPERTY = new BrokerReadOnlyProperty<String>("version") + { + public String getValue(BrokerConfig object) + { + return object.getVersion(); + } + }; + + public static final BrokerReadOnlyProperty<String> DATA_DIR_PROPERTY = new BrokerReadOnlyProperty<String>("dataDirectory") + { + public String getValue(BrokerConfig object) + { + return object.getDataDirectory(); + } + }; + + private static final BrokerConfigType INSTANCE = new BrokerConfigType(); + + private BrokerConfigType() + { + } + + public Collection<BrokerProperty<?>> getProperties() + { + return Collections.unmodifiableList(BROKER_PROPERTIES); + } + + public static BrokerConfigType getInstance() + { + return INSTANCE; + } + + + +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConfigObjectType.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConfigObjectType.java new file mode 100644 index 0000000000..c45aaaf1ee --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConfigObjectType.java @@ -0,0 +1,30 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import java.util.Collection; + +public abstract class ConfigObjectType<T extends ConfigObjectType<T,C>, C extends ConfiguredObject<T,C>> +{ + public abstract Collection<? extends ConfigProperty<T, C, ?>> getProperties(); + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConfigProperty.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConfigProperty.java new file mode 100644 index 0000000000..2d88ba00a0 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConfigProperty.java @@ -0,0 +1,66 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +public interface ConfigProperty<T extends ConfigObjectType<T,C>, C extends ConfiguredObject<T,C>, S> +{ + public String getName(); + + public S getValue(C object); + + public void setValue(C object, S value); + + public void clearValue(C object); + + public abstract static class ReadWriteConfigProperty<T extends ConfigObjectType<T,C>, C extends ConfiguredObject<T,C>,S> implements ConfigProperty<T, C, S> + { + private final String _name; + + protected ReadWriteConfigProperty(String name) + { + _name = name; + } + + public final String getName() + { + return _name; + } + } + + public abstract static class ReadOnlyConfigProperty<T extends ConfigObjectType<T,C>, C extends ConfiguredObject<T,C>, S> extends ReadWriteConfigProperty<T, C, S> + { + protected ReadOnlyConfigProperty(String name) + { + super(name); + } + + public final void setValue(C object, S value) + { + throw new UnsupportedOperationException("Cannot set value '"+getName()+"' as this property is read-only"); + } + + public final void clearValue(C object) + { + throw new UnsupportedOperationException("Cannot set value '"+getName()+"' as this property is read-only"); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConfigStore.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConfigStore.java new file mode 100644 index 0000000000..0e03e33be8 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConfigStore.java @@ -0,0 +1,184 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import java.util.UUID; +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +public class ConfigStore +{ + private ConcurrentHashMap<ConfigObjectType, ConcurrentHashMap<UUID, ConfiguredObject>> _typeMap = + new ConcurrentHashMap<ConfigObjectType, ConcurrentHashMap<UUID, ConfiguredObject>>(); + + private ConcurrentHashMap<ConfigObjectType, CopyOnWriteArrayList<ConfigEventListener>> _listenerMap = + new ConcurrentHashMap<ConfigObjectType, CopyOnWriteArrayList<ConfigEventListener>>(); + + private AtomicReference<SystemConfig> _root = new AtomicReference<SystemConfig>(null); + + private final AtomicLong _objectIdSource = new AtomicLong(0l); + + + public enum Event + { + CREATED, DELETED + } + + public interface ConfigEventListener<T extends ConfigObjectType<T,C>, C extends ConfiguredObject<T, C>> + { + void onEvent(C object, Event evt); + } + + private ConfigStore() + { + } + + public <T extends ConfigObjectType<T, C>, C extends ConfiguredObject<T, C>> ConfiguredObject<T, C> getConfiguredObject(ConfigObjectType<T,C> type, UUID id) + { + ConcurrentHashMap<UUID, ConfiguredObject> typeMap = _typeMap.get(type); + if(typeMap != null) + { + return typeMap.get(id); + } + else + { + return null; + } + + } + + public <T extends ConfigObjectType<T, C>, C extends ConfiguredObject<T, C>> Collection<? extends C> getConfiguredObjects(ConfigObjectType<T,C> type) + { + ConcurrentHashMap typeMap = _typeMap.get(type); + if(typeMap != null) + { + return typeMap.values(); + } + else + { + return Collections.EMPTY_LIST; + } + + } + + public <T extends ConfigObjectType<T, C>, C extends ConfiguredObject<T, C>> void addConfiguredObject(ConfiguredObject<T, C> object) + { + ConcurrentHashMap typeMap = _typeMap.get(object.getConfigType()); + if(typeMap == null) + { + typeMap = new ConcurrentHashMap(); + ConcurrentHashMap oldMap = _typeMap.putIfAbsent(object.getConfigType(), typeMap); + if(oldMap != null) + { + typeMap = oldMap; + } + + } + + typeMap.put(object.getId(), object); + sendEvent(Event.CREATED, object); + } + + + public <T extends ConfigObjectType<T, C>, C extends ConfiguredObject<T, C>> void removeConfiguredObject(ConfiguredObject<T, C> object) + { + ConcurrentHashMap typeMap = _typeMap.get(object.getConfigType()); + if(typeMap != null) + { + typeMap.remove(object.getId()); + sendEvent(Event.DELETED, object); + } + } + + public <T extends ConfigObjectType<T, C>, C extends ConfiguredObject<T, C>> void addConfigEventListener(T type, ConfigEventListener<T,C> listener) + { + CopyOnWriteArrayList listeners = _listenerMap.get(type); + if(listeners == null) + { + listeners = new CopyOnWriteArrayList(); + CopyOnWriteArrayList oldListeners = _listenerMap.putIfAbsent(type, listeners); + if(oldListeners != null) + { + listeners = oldListeners; + } + + } + + listeners.add(listener); + + } + + public <T extends ConfigObjectType<T, C>, C extends ConfiguredObject<T, C>> void removeConfigEventListener(T type, ConfigEventListener<T,C> listener) + { + CopyOnWriteArrayList listeners = _listenerMap.get(type); + if(listeners != null) + { + listeners.remove(listener); + } + } + + private void sendEvent(Event e, ConfiguredObject o) + { + CopyOnWriteArrayList<ConfigEventListener> listeners = _listenerMap.get(o.getConfigType()); + if(listeners != null) + { + for(ConfigEventListener listener : listeners) + { + listener.onEvent(o, e); + } + } + } + + public boolean setRoot(SystemConfig object) + { + if(_root.compareAndSet(null,object)) + { + addConfiguredObject(object); + return true; + } + else + { + return false; + } + } + + public UUID createId() + { + return new UUID(0l, _objectIdSource.getAndIncrement()); + } + + + public SystemConfig getRoot() + { + return _root.get(); + } + + public static ConfigStore newInstance() + { + return new ConfigStore(); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConfigurationManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConfigurationManager.java new file mode 100644 index 0000000000..2c492ff6b9 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConfigurationManager.java @@ -0,0 +1,53 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory; +import org.apache.qpid.server.registry.ApplicationRegistry; + +public class ConfigurationManager +{ + public List<ConfigurationPlugin> getConfigurationPlugins(String configurationElement, Configuration configuration) throws ConfigurationException + { + List<ConfigurationPlugin> plugins = new ArrayList<ConfigurationPlugin>(); + Map<List<String>, ConfigurationPluginFactory> factories = + ApplicationRegistry.getInstance().getPluginManager().getConfigurationPlugins(); + + for (Entry<List<String>, ConfigurationPluginFactory> entry : factories.entrySet()) + { + if (entry.getKey().contains(configurationElement)) + { + ConfigurationPluginFactory factory = entry.getValue(); + plugins.add(factory.newInstance(configurationElement, configuration)); + } + } + + return plugins; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConfiguredObject.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConfiguredObject.java new file mode 100644 index 0000000000..78666a3f93 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConfiguredObject.java @@ -0,0 +1,37 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import java.util.UUID; + +public interface ConfiguredObject<T extends ConfigObjectType<T,C>, C extends ConfiguredObject<T, C>> +{ + public UUID getId(); + + public T getConfigType(); + + public ConfiguredObject<T,C> getParent(); + + public boolean isDurable(); + + long getCreateTime(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConnectionConfig.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConnectionConfig.java new file mode 100644 index 0000000000..0dd36fe1fe --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConnectionConfig.java @@ -0,0 +1,49 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +public interface ConnectionConfig extends ConfiguredObject<ConnectionConfigType, ConnectionConfig> +{ + VirtualHostConfig getVirtualHost(); + + String getAddress(); + + Boolean isIncoming(); + + Boolean isSystemConnection(); + + Boolean isFederationLink(); + + String getAuthId(); + + String getRemoteProcessName(); + + Integer getRemotePID(); + + Integer getRemoteParentPID(); + + ConfigStore getConfigStore(); + + Boolean isShadow(); + + void mgmtClose(); +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConnectionConfigType.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConnectionConfigType.java new file mode 100644 index 0000000000..9750b12dea --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ConnectionConfigType.java @@ -0,0 +1,145 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import org.apache.qpid.server.exchange.ExchangeType; + +import java.util.*; + +public final class ConnectionConfigType extends ConfigObjectType<ConnectionConfigType, ConnectionConfig> +{ + private static final List<ConnectionProperty<?>> CONNECTION_PROPERTIES = new ArrayList<ConnectionProperty<?>>(); + + public static interface ConnectionProperty<S> extends ConfigProperty<ConnectionConfigType, ConnectionConfig, S> + { + } + + private abstract static class ConnectionReadWriteProperty<S> extends ConfigProperty.ReadWriteConfigProperty<ConnectionConfigType, ConnectionConfig, S> implements ConnectionProperty<S> + { + public ConnectionReadWriteProperty(String name) + { + super(name); + CONNECTION_PROPERTIES.add(this); + } + } + + private abstract static class ConnectionReadOnlyProperty<S> extends ConfigProperty.ReadOnlyConfigProperty<ConnectionConfigType, ConnectionConfig, S> implements ConnectionProperty<S> + { + public ConnectionReadOnlyProperty(String name) + { + super(name); + CONNECTION_PROPERTIES.add(this); + } + } + + public static final ConnectionReadOnlyProperty<VirtualHostConfig> VIRTUAL_HOST_PROPERTY = new ConnectionReadOnlyProperty<VirtualHostConfig>("virtualHost") + { + public VirtualHostConfig getValue(ConnectionConfig object) + { + return object.getVirtualHost(); + } + }; + + public static final ConnectionReadOnlyProperty<String> ADDRESS_PROPERTY = new ConnectionReadOnlyProperty<String>("address") + { + public String getValue(ConnectionConfig object) + { + return object.getAddress(); + } + }; + + public static final ConnectionReadOnlyProperty<Boolean> INCOMING_PROPERTY = new ConnectionReadOnlyProperty<Boolean>("incoming") + { + public Boolean getValue(ConnectionConfig object) + { + return object.isIncoming(); + } + }; + + public static final ConnectionReadOnlyProperty<Boolean> SYSTEM_CONNECTION_PROPERTY = new ConnectionReadOnlyProperty<Boolean>("systemConnection") + { + public Boolean getValue(ConnectionConfig object) + { + return object.isSystemConnection(); + } + }; + + public static final ConnectionReadOnlyProperty<Boolean> FEDERATION_LINK_PROPERTY = new ConnectionReadOnlyProperty<Boolean>("federationLink") + { + public Boolean getValue(ConnectionConfig object) + { + return object.isFederationLink(); + } + }; + + public static final ConnectionReadOnlyProperty<String> AUTH_ID_PROPERTY = new ConnectionReadOnlyProperty<String>("authId") + { + public String getValue(ConnectionConfig object) + { + return object.getAuthId(); + } + }; + + public static final ConnectionReadOnlyProperty<String> REMOTE_PROCESS_NAME_PROPERTY = new ConnectionReadOnlyProperty<String>("remoteProcessName") + { + public String getValue(ConnectionConfig object) + { + return object.getRemoteProcessName(); + } + }; + + + public static final ConnectionReadOnlyProperty<Integer> REMOTE_PID_PROPERTY = new ConnectionReadOnlyProperty<Integer>("remotePid") + { + public Integer getValue(ConnectionConfig object) + { + return object.getRemotePID(); + } + }; + + public static final ConnectionReadOnlyProperty<Integer> REMOTE_PARENT_PID_PROPERTY = new ConnectionReadOnlyProperty<Integer>("remoteParentPid") + { + public Integer getValue(ConnectionConfig object) + { + return object.getRemoteParentPID(); + } + }; + + private static final ConnectionConfigType INSTANCE = new ConnectionConfigType(); + + private ConnectionConfigType() + { + } + + public Collection<ConnectionProperty<?>> getProperties() + { + return Collections.unmodifiableList(CONNECTION_PROPERTIES); + } + + public static ConnectionConfigType getInstance() + { + return INSTANCE; + } + + + +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ExchangeConfig.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ExchangeConfig.java new file mode 100644 index 0000000000..41c51d9684 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ExchangeConfig.java @@ -0,0 +1,55 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import org.apache.qpid.server.exchange.ExchangeType; + +import java.util.Map; + + +public interface ExchangeConfig extends ConfiguredObject<ExchangeConfigType, ExchangeConfig> +{ + VirtualHostConfig getVirtualHost(); + + String getName(); + + ExchangeType getType(); + + boolean isAutoDelete(); + + ExchangeConfig getAlternateExchange(); + + Map<String, Object> getArguments(); + + + long getBindingCount(); + + long getBindingCountHigh(); + + long getMsgReceives(); + + long getMsgRoutes(); + + long getByteReceives(); + + long getByteRoutes(); +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ExchangeConfigType.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ExchangeConfigType.java new file mode 100644 index 0000000000..2095301ad6 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ExchangeConfigType.java @@ -0,0 +1,113 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import org.apache.qpid.server.exchange.ExchangeType; + +import java.util.*; + +public final class ExchangeConfigType extends ConfigObjectType<ExchangeConfigType, ExchangeConfig> +{ + private static final List<ExchangeProperty<?>> EXCHANGE_PROPERTIES = new ArrayList<ExchangeProperty<?>>(); + + public static interface ExchangeProperty<S> extends ConfigProperty<ExchangeConfigType, ExchangeConfig, S> + { + } + + private abstract static class ExchangeReadWriteProperty<S> extends ConfigProperty.ReadWriteConfigProperty<ExchangeConfigType, ExchangeConfig, S> implements ExchangeProperty<S> + { + public ExchangeReadWriteProperty(String name) + { + super(name); + EXCHANGE_PROPERTIES.add(this); + } + } + + private abstract static class ExchangeReadOnlyProperty<S> extends ConfigProperty.ReadOnlyConfigProperty<ExchangeConfigType, ExchangeConfig, S> implements ExchangeProperty<S> + { + public ExchangeReadOnlyProperty(String name) + { + super(name); + EXCHANGE_PROPERTIES.add(this); + } + } + + public static final ExchangeReadOnlyProperty<VirtualHostConfig> VIRTUAL_HOST_PROPERTY = new ExchangeReadOnlyProperty<VirtualHostConfig>("virtualHost") + { + public VirtualHostConfig getValue(ExchangeConfig object) + { + return object.getVirtualHost(); + } + }; + + public static final ExchangeReadOnlyProperty<String> NAME_PROPERTY = new ExchangeReadOnlyProperty<String>("name") + { + public String getValue(ExchangeConfig object) + { + return object.getName(); + } + }; + + public static final ExchangeReadOnlyProperty<Boolean> AUTODELETE_PROPERTY = new ExchangeReadOnlyProperty<Boolean>("autodelete") + { + public Boolean getValue(ExchangeConfig object) + { + return object.isAutoDelete(); + } + }; + + + public static final ExchangeReadOnlyProperty<ExchangeConfig> ALTERNATE_EXCHANGE_PROPERTY = new ExchangeReadOnlyProperty<ExchangeConfig>("alternateExchange") + { + public ExchangeConfig getValue(ExchangeConfig object) + { + return object.getAlternateExchange(); + } + }; + + public static final ExchangeReadOnlyProperty<Map<String,Object>> ARGUMENTS = new ExchangeReadOnlyProperty<Map<String,Object>>("arguments") + { + public Map<String,Object> getValue(ExchangeConfig object) + { + return object.getArguments(); + } + }; + + private static final ExchangeConfigType INSTANCE = new ExchangeConfigType(); + + private ExchangeConfigType() + { + } + + public Collection<ExchangeProperty<?>> getProperties() + { + return Collections.unmodifiableList(EXCHANGE_PROPERTIES); + } + + public static ExchangeConfigType getInstance() + { + return INSTANCE; + } + + + +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ExchangeConfiguration.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ExchangeConfiguration.java new file mode 100644 index 0000000000..c7cf0c0892 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ExchangeConfiguration.java @@ -0,0 +1,58 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import org.apache.commons.configuration.Configuration; + + +public class ExchangeConfiguration +{ + + private Configuration _config; + private String _name; + + public ExchangeConfiguration(String exchName, Configuration subset) + { + _name = exchName; + _config = subset; + } + + public String getName() + { + return _name; + } + + public String getType() + { + return _config.getString("type","direct"); + } + + public boolean getDurable() + { + return _config.getBoolean("durable", false); + } + + public boolean getAutoDelete() + { + return _config.getBoolean("autodelete",false); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ExchangeConfigurationPlugin.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ExchangeConfigurationPlugin.java new file mode 100644 index 0000000000..bfb2de4235 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ExchangeConfigurationPlugin.java @@ -0,0 +1,29 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.queue.AMQQueue; + +public interface ExchangeConfigurationPlugin +{ + ConfigurationPlugin getConfiguration(AMQQueue queue); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/LinkConfig.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/LinkConfig.java new file mode 100644 index 0000000000..5a6159df34 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/LinkConfig.java @@ -0,0 +1,57 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import org.apache.qpid.server.exchange.ExchangeType; + +import java.util.Map; + + +public interface LinkConfig extends ConfiguredObject<LinkConfigType, LinkConfig> +{ + VirtualHostConfig getVirtualHost(); + + + String getTransport(); + + String getHost(); + + int getPort(); + + String getRemoteVhost(); + + String getAuthMechanism(); + + String getUsername(); + + String getPassword(); + + void close(); + + void createBridge(boolean durable, + boolean dynamic, + boolean srcIsQueue, + boolean srcIsLocal, + String src, + String dest, + String key, String tag, String excludes); +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/LinkConfigType.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/LinkConfigType.java new file mode 100644 index 0000000000..4dc46b70c9 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/LinkConfigType.java @@ -0,0 +1,136 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import org.apache.qpid.server.exchange.ExchangeType; + +import java.util.*; + +public final class LinkConfigType extends ConfigObjectType<LinkConfigType, LinkConfig> +{ + private static final List<LinkProperty<?>> LINK_PROPERTIES = new ArrayList<LinkProperty<?>>(); + + public static interface LinkProperty<S> extends ConfigProperty<LinkConfigType, LinkConfig, S> + { + } + + private abstract static class LinkReadWriteProperty<S> extends ConfigProperty.ReadWriteConfigProperty<LinkConfigType, LinkConfig, S> implements LinkProperty<S> + { + public LinkReadWriteProperty(String name) + { + super(name); + LINK_PROPERTIES.add(this); + } + } + + private abstract static class LinkReadOnlyProperty<S> extends ConfigProperty.ReadOnlyConfigProperty<LinkConfigType, LinkConfig, S> implements LinkProperty<S> + { + public LinkReadOnlyProperty(String name) + { + super(name); + LINK_PROPERTIES.add(this); + } + } + + public static final LinkReadOnlyProperty<VirtualHostConfig> VIRTUAL_HOST_PROPERTY = new LinkReadOnlyProperty<VirtualHostConfig>("virtualHost") + { + public VirtualHostConfig getValue(LinkConfig object) + { + return object.getVirtualHost(); + } + }; + + public static final LinkReadOnlyProperty<String> TRANSPORT_PROPERTY = new LinkReadOnlyProperty<String>("transport") + { + public String getValue(LinkConfig object) + { + return object.getTransport(); + } + }; + + public static final LinkReadOnlyProperty<String> HOST_PROPERTY = new LinkReadOnlyProperty<String>("host") + { + public String getValue(LinkConfig object) + { + return object.getHost(); + } + }; + + public static final LinkReadOnlyProperty<Integer> PORT_PROPERTY = new LinkReadOnlyProperty<Integer>("host") + { + public Integer getValue(LinkConfig object) + { + return object.getPort(); + } + }; + + public static final LinkReadOnlyProperty<String> REMOTE_VHOST_PROPERTY = new LinkReadOnlyProperty<String>("remoteVhost") + { + public String getValue(LinkConfig object) + { + return object.getRemoteVhost(); + } + }; + + public static final LinkReadOnlyProperty<String> AUTH_MECHANISM_PROPERTY = new LinkReadOnlyProperty<String>("authMechanism") + { + public String getValue(LinkConfig object) + { + return object.getAuthMechanism(); + } + }; + + public static final LinkReadOnlyProperty<String> USERNAME_PROPERTY = new LinkReadOnlyProperty<String>("username") + { + public String getValue(LinkConfig object) + { + return object.getUsername(); + } + }; + + public static final LinkReadOnlyProperty<String> PASSWORD_PROPERTY = new LinkReadOnlyProperty<String>("password") + { + public String getValue(LinkConfig object) + { + return object.getPassword(); + } + }; + + private static final LinkConfigType INSTANCE = new LinkConfigType(); + + private LinkConfigType() + { + } + + public Collection<LinkProperty<?>> getProperties() + { + return Collections.unmodifiableList(LINK_PROPERTIES); + } + + public static LinkConfigType getInstance() + { + return INSTANCE; + } + + + +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/QueueConfig.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/QueueConfig.java new file mode 100644 index 0000000000..be34c8d63d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/QueueConfig.java @@ -0,0 +1,86 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import java.util.Map; + +import org.apache.qpid.AMQException; + + +public interface QueueConfig extends ConfiguredObject<QueueConfigType, QueueConfig> +{ + VirtualHostConfig getVirtualHost(); + + String getName(); + + boolean isExclusive(); + + boolean isAutoDelete(); + + ExchangeConfig getAlternateExchange(); + + Map<String, Object> getArguments(); + + long getReceivedMessageCount(); + + int getMessageCount(); + + long getQueueDepth(); + + int getConsumerCount(); + + int getConsumerCountHigh(); + + int getBindingCount(); + + int getBindingCountHigh(); + + ConfigStore getConfigStore(); + + long getMessageDequeueCount(); + + long getTotalEnqueueSize(); + + long getTotalDequeueSize(); + + long getByteTxnEnqueues(); + + long getByteTxnDequeues(); + + long getMsgTxnEnqueues(); + + long getMsgTxnDequeues(); + + long getPersistentByteEnqueues(); + + long getPersistentByteDequeues(); + + long getPersistentMsgEnqueues(); + + long getPersistentMsgDequeues(); + + long getUnackedMessageCount(); + + long getUnackedMessageCountHigh(); + + void purge(long request) throws AMQException; +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/QueueConfigType.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/QueueConfigType.java new file mode 100644 index 0000000000..a794ed9747 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/QueueConfigType.java @@ -0,0 +1,121 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import org.apache.qpid.server.exchange.ExchangeType; + +import java.util.*; + +public final class QueueConfigType extends ConfigObjectType<QueueConfigType, QueueConfig> +{ + private static final List<QueueProperty<?>> QUEUE_PROPERTIES = new ArrayList<QueueProperty<?>>(); + + public static interface QueueProperty<S> extends ConfigProperty<QueueConfigType, QueueConfig, S> + { + } + + private abstract static class QueueReadWriteProperty<S> extends ConfigProperty.ReadWriteConfigProperty<QueueConfigType, QueueConfig, S> implements QueueProperty<S> + { + public QueueReadWriteProperty(String name) + { + super(name); + QUEUE_PROPERTIES.add(this); + } + } + + private abstract static class QueueReadOnlyProperty<S> extends ConfigProperty.ReadOnlyConfigProperty<QueueConfigType, QueueConfig, S> implements QueueProperty<S> + { + public QueueReadOnlyProperty(String name) + { + super(name); + QUEUE_PROPERTIES.add(this); + } + } + + public static final QueueReadOnlyProperty<VirtualHostConfig> VISTUAL_HOST_PROPERTY = new QueueReadOnlyProperty<VirtualHostConfig>("virtualHost") + { + public VirtualHostConfig getValue(QueueConfig object) + { + return object.getVirtualHost(); + } + }; + + public static final QueueReadOnlyProperty<String> NAME_PROPERTY = new QueueReadOnlyProperty<String>("name") + { + public String getValue(QueueConfig object) + { + return object.getName(); + } + }; + + public static final QueueReadOnlyProperty<Boolean> AUTODELETE_PROPERTY = new QueueReadOnlyProperty<Boolean>("autodelete") + { + public Boolean getValue(QueueConfig object) + { + return object.isAutoDelete(); + } + }; + + public static final QueueReadOnlyProperty<Boolean> EXCLUSIVE_PROPERTY = new QueueReadOnlyProperty<Boolean>("exclusive") + { + public Boolean getValue(QueueConfig object) + { + return object.isExclusive(); + } + }; + + public static final QueueReadOnlyProperty<ExchangeConfig> ALTERNATE_EXCHANGE_PROPERTY = new QueueReadOnlyProperty<ExchangeConfig>("alternateExchange") + { + public ExchangeConfig getValue(QueueConfig object) + { + return object.getAlternateExchange(); + } + }; + + public static final QueueReadOnlyProperty<Map<String,Object>> ARGUMENTS = new QueueReadOnlyProperty<Map<String,Object>>("arguments") + { + public Map<String,Object> getValue(QueueConfig object) + { + return object.getArguments(); + } + }; + + + private static final QueueConfigType INSTANCE = new QueueConfigType(); + + private QueueConfigType() + { + } + + public Collection<QueueProperty<?>> getProperties() + { + return Collections.unmodifiableList(QUEUE_PROPERTIES); + } + + public static QueueConfigType getInstance() + { + return INSTANCE; + } + + + +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/QueueConfiguration.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/QueueConfiguration.java new file mode 100644 index 0000000000..4512de6fb4 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/QueueConfiguration.java @@ -0,0 +1,208 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import java.util.List; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; + +public class QueueConfiguration extends ConfigurationPlugin +{ + private String _name; + private VirtualHostConfiguration _vHostConfig; + + public QueueConfiguration(String name, VirtualHostConfiguration virtualHostConfiguration) throws ConfigurationException + { + _vHostConfig = virtualHostConfiguration; + _name = name; + + CompositeConfiguration mungedConf = new CompositeConfiguration(); + mungedConf.addConfiguration(_vHostConfig.getConfig().subset("queues.queue." + name)); + mungedConf.addConfiguration(_vHostConfig.getConfig().subset("queues")); + + setConfiguration("virtualhosts.virtualhost.queues.queue", mungedConf); + } + + public String[] getElementsProcessed() + { + return new String[]{"maximumMessageSize", + "maximumQueueDepth", + "maximumMessageCount", + "maximumMessageAge", + "minimumAlertRepeatGap", + "durable", + "exchange", + "exclusive", + "queue", + "autodelete", + "priority", + "priorities", + "routingKey", + "capacity", + "flowResumeCapacity", + "lvq", + "lvqKey" + }; + } + + @Override + public void validateConfiguration() throws ConfigurationException + { + //Currently doesn't do validation + } + + public VirtualHostConfiguration getVirtualHostConfiguration() + { + return _vHostConfig; + } + + public boolean getDurable() + { + return getBooleanValue("durable"); + } + + public boolean getExclusive() + { + return getBooleanValue("exclusive"); + } + + public boolean getAutoDelete() + { + return getBooleanValue("autodelete"); + } + + public String getOwner() + { + return getStringValue("owner", null); + } + + public boolean getPriority() + { + return getBooleanValue("priority"); + } + + public int getPriorities() + { + return getIntValue("priorities", -1); + } + + public String getExchange() + { + return getStringValue("exchange", ExchangeDefaults.DEFAULT_EXCHANGE_NAME.asString()); + } + + public List getRoutingKeys() + { + return getListValue("routingKey"); + } + + public String getName() + { + return _name; + } + + public int getMaximumMessageAge() + { + return getIntValue("maximumMessageAge", _vHostConfig.getMaximumMessageAge()); + } + + public long getMaximumQueueDepth() + { + return getLongValue("maximumQueueDepth", _vHostConfig.getMaximumQueueDepth()); + } + + public long getMaximumMessageSize() + { + return getLongValue("maximumMessageSize", _vHostConfig.getMaximumMessageSize()); + } + + public long getMaximumMessageCount() + { + return getLongValue("maximumMessageCount", _vHostConfig.getMaximumMessageCount()); + } + + public long getMinimumAlertRepeatGap() + { + return getLongValue("minimumAlertRepeatGap", _vHostConfig.getMinimumAlertRepeatGap()); + } + + public long getCapacity() + { + return getLongValue("capacity", _vHostConfig.getCapacity()); + } + + public long getFlowResumeCapacity() + { + return getLongValue("flowResumeCapacity", _vHostConfig.getFlowResumeCapacity()); + } + + public boolean isLVQ() + { + return getBooleanValue("lvq"); + } + + public String getLVQKey() + { + return getStringValue("lvqKey", null); + } + + + public static class QueueConfig extends ConfigurationPlugin + { + @Override + public String[] getElementsProcessed() + { + return new String[]{"name"}; + } + + public String getName() + { + return getStringValue("name"); + } + + + public void validateConfiguration() throws ConfigurationException + { + if (_configuration.isEmpty()) + { + throw new ConfigurationException("Queue section cannot be empty."); + } + + if (getName() == null) + { + throw new ConfigurationException("Queue section must have a 'name' element."); + } + + } + + + @Override + public String formatToString() + { + return "Name:"+getName(); + } + + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ServerConfiguration.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ServerConfiguration.java new file mode 100644 index 0000000000..297f7abdb8 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/ServerConfiguration.java @@ -0,0 +1,860 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.ConfigurationFactory; +import org.apache.commons.configuration.HierarchicalConfiguration; +import org.apache.commons.configuration.SystemConfiguration; +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.log4j.Logger; +import org.apache.qpid.server.configuration.management.ConfigurationManagementMBean; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; +import org.apache.qpid.transport.NetworkDriverConfiguration; + +import sun.misc.Signal; +import sun.misc.SignalHandler; + +public class ServerConfiguration extends ConfigurationPlugin implements SignalHandler +{ + protected static final Logger _logger = Logger.getLogger(ServerConfiguration.class); + + // Default Configuration values + public static final int DEFAULT_BUFFER_READ_LIMIT_SIZE = 262144; + public static final int DEFAULT_BUFFER_WRITE_LIMIT_SIZE = 262144; + public static final boolean DEFAULT_BROKER_CONNECTOR_PROTECTIO_ENABLED = false; + public static final String DEFAULT_STATUS_UPDATES = "on"; + public static final String SECURITY_CONFIG_RELOADED = "SECURITY CONFIGURATION RELOADED"; + + public static final int DEFAULT_FRAME_SIZE = 65536; + public static final int DEFAULT_PORT = 5672; + public static final int DEFAULT_SSL_PORT = 8672; + public static final long DEFAULT_HOUSEKEEPING_PERIOD = 30000L; + public static final int DEFAULT_JMXPORT = 8999; + + public static final String QPID_HOME = "QPID_HOME"; + public static final String QPID_WORK = "QPID_WORK"; + public static final String LIB_DIR = "lib"; + public static final String PLUGIN_DIR = "plugins"; + public static final String CACHE_DIR = "cache"; + + private Map<String, VirtualHostConfiguration> _virtualHosts = new HashMap<String, VirtualHostConfiguration>(); + + private File _configFile; + private File _vhostsFile; + + private Logger _log = Logger.getLogger(this.getClass()); + + private ConfigurationManagementMBean _mbean; + + // Map of environment variables to config items + private static final Map<String, String> envVarMap = new HashMap<String, String>(); + + // Configuration values to be read from the configuration file + //todo Move all properties to static values to ensure system testing can be performed. + public static final String CONNECTOR_PROTECTIO_ENABLED = "connector.protectio.enabled"; + public static final String CONNECTOR_PROTECTIO_READ_BUFFER_LIMIT_SIZE = "connector.protectio.readBufferLimitSize"; + public static final String CONNECTOR_PROTECTIO_WRITE_BUFFER_LIMIT_SIZE = "connector.protectio.writeBufferLimitSize"; + public static final String MGMT_CUSTOM_REGISTRY_SOCKET = "management.custom-registry-socket"; + public static final String STATUS_UPDATES = "status-updates"; + public static final String ADVANCED_LOCALE = "advanced.locale"; + + { + envVarMap.put("QPID_PORT", "connector.port"); + envVarMap.put("QPID_ENABLEDIRECTBUFFERS", "advanced.enableDirectBuffers"); + envVarMap.put("QPID_SSLPORT", "connector.ssl.port"); + envVarMap.put("QPID_NIO", "connector.qpidnio"); + envVarMap.put("QPID_WRITEBIASED", "advanced.useWriteBiasedPool"); + envVarMap.put("QPID_JMXPORT", "management.jmxport"); + envVarMap.put("QPID_FRAMESIZE", "advanced.framesize"); + envVarMap.put("QPID_MSGAUTH", "security.msg-auth"); + envVarMap.put("QPID_AUTOREGISTER", "auto_register"); + envVarMap.put("QPID_MANAGEMENTENABLED", "management.enabled"); + envVarMap.put("QPID_HEARTBEATDELAY", "heartbeat.delay"); + envVarMap.put("QPID_HEARTBEATTIMEOUTFACTOR", "heartbeat.timeoutFactor"); + envVarMap.put("QPID_MAXIMUMMESSAGEAGE", "maximumMessageAge"); + envVarMap.put("QPID_MAXIMUMMESSAGECOUNT", "maximumMessageCount"); + envVarMap.put("QPID_MAXIMUMQUEUEDEPTH", "maximumQueueDepth"); + envVarMap.put("QPID_MAXIMUMMESSAGESIZE", "maximumMessageSize"); + envVarMap.put("QPID_MAXIMUMCHANNELCOUNT", "maximumChannelCount"); + envVarMap.put("QPID_MINIMUMALERTREPEATGAP", "minimumAlertRepeatGap"); + envVarMap.put("QPID_QUEUECAPACITY", "capacity"); + envVarMap.put("QPID_FLOWRESUMECAPACITY", "flowResumeCapacity"); + envVarMap.put("QPID_SOCKETRECEIVEBUFFER", "connector.socketReceiveBuffer"); + envVarMap.put("QPID_SOCKETWRITEBUFFER", "connector.socketWriteBuffer"); + envVarMap.put("QPID_TCPNODELAY", "connector.tcpNoDelay"); + envVarMap.put("QPID_ENABLEPOOLEDALLOCATOR", "advanced.enablePooledAllocator"); + envVarMap.put("QPID_STATUS-UPDATES", "status-updates"); + } + + /** + * Loads the given file and sets up the HUP signal handler. + * + * This will load the file and present the root level properties but will + * not perform any virtualhost configuration. + * <p> + * To perform this {@link #initialise()} must be called. + * <p> + * This has been made a two step process to allow the Plugin Manager and + * Configuration Manager to be initialised in the Application Registry. + * <p> + * If using this ServerConfiguration via an ApplicationRegistry there is no + * need to explictly call {@link #initialise()} as this is done via the + * {@link ApplicationRegistry#initialise()} method. + * + * @param configurationURL + * @throws org.apache.commons.configuration.ConfigurationException + */ + public ServerConfiguration(File configurationURL) throws ConfigurationException + { + this(parseConfig(configurationURL)); + _configFile = configurationURL; + try + { + Signal sig = new sun.misc.Signal("HUP"); + sun.misc.Signal.handle(sig, this); + } + catch (Exception e) + { + _logger.info("Signal HUP not supported for OS: " + System.getProperty("os.name")); + // We're on something that doesn't handle SIGHUP, how sad, Windows. + } + } + + /** + * Wraps the given Commons Configuration as a ServerConfiguration. + * + * Mainly used during testing and in locations where configuration is not + * desired but the interface requires configuration. + * <p> + * If the given configuration has VirtualHost configuration then + * {@link #initialise()} must be called to perform the required setup. + * <p> + * This has been made a two step process to allow the Plugin Manager and + * Configuration Manager to be initialised in the Application Registry. + * <p> + * If using this ServerConfiguration via an ApplicationRegistry there is no + * need to explictly call {@link #initialise()} as this is done via the + * {@link ApplicationRegistry#initialise()} method. + * + * @param conf + */ + public ServerConfiguration(Configuration conf) + { + _configuration = conf; + } + + /** + * Processes this configuration and setups any VirtualHosts defined in the + * configuration. + * + * This has been separated from the constructor to allow the PluginManager + * time to be created and provide plugins to the ConfigurationManager for + * processing here. + * <p> + * Called by {@link ApplicationRegistry#initialise()}. + * <p> + * NOTE: A DEFAULT ApplicationRegistry must exist when using this method + * or a new ApplicationRegistry will be created. + * + * @throws ConfigurationException + */ + public void initialise() throws ConfigurationException + { + setConfiguration("", _configuration); + setupVirtualHosts(_configuration); + } + + public String[] getElementsProcessed() + { + return new String[] { "" }; + } + + @Override + public void validateConfiguration() throws ConfigurationException + { + // Support for security.jmx.access was removed when JMX access rights were incorporated into the main ACL. + // This ensure that users remove the element from their configuration file. + + if (getListValue("security.jmx.access").size() > 0) + { + String message = "Validation error : security/jmx/access is no longer a supported element within the configuration xml." + + (_configFile == null ? "" : " Configuration file : " + _configFile); + throw new ConfigurationException(message); + } + } + + /* + * Modified to enforce virtualhosts configuration in external file or main file, but not + * both, as a fix for QPID-2360 and QPID-2361. + */ + @SuppressWarnings("unchecked") + protected void setupVirtualHosts(Configuration conf) throws ConfigurationException + { + List<String> vhostFiles = conf.getList("virtualhosts"); + Configuration vhostConfig = conf.subset("virtualhosts"); + + // Only one configuration mechanism allowed + if (!vhostFiles.isEmpty() && !vhostConfig.subset("virtualhost").isEmpty()) + { + throw new ConfigurationException("Only one of external or embedded virtualhosts configuration allowed."); + } + + // We can only have one vhosts XML file included + if (vhostFiles.size() > 1) + { + throw new ConfigurationException("Only one external virtualhosts configuration file allowed, multiple filenames found."); + } + + // Virtualhost configuration object + Configuration vhostConfiguration = new HierarchicalConfiguration(); + + // Load from embedded configuration if possible + if (!vhostConfig.subset("virtualhost").isEmpty()) + { + vhostConfiguration = vhostConfig; + } + else + { + // Load from the external configuration if possible + for (String fileName : vhostFiles) + { + // Open the vhosts XML file and copy values from it to our config + _vhostsFile = new File(fileName); + if (!_vhostsFile.exists()) + { + throw new ConfigurationException("Virtualhosts file does not exist"); + } + vhostConfiguration = parseConfig(new File(fileName)); + + // save the default virtualhost name + String defaultVirtualHost = vhostConfiguration.getString("default"); + _configuration.setProperty("virtualhosts.default", defaultVirtualHost); + } + } + + // Now extract the virtual host names from the configuration object + List hosts = vhostConfiguration.getList("virtualhost.name"); + for (int j = 0; j < hosts.size(); j++) + { + String name = (String) hosts.get(j); + + // Add the virtual hosts to the server configuration + VirtualHostConfiguration virtualhost = new VirtualHostConfiguration(name, vhostConfiguration.subset("virtualhost." + name)); + _virtualHosts.put(virtualhost.getName(), virtualhost); + } + } + + private static void substituteEnvironmentVariables(Configuration conf) + { + for (Entry<String, String> var : envVarMap.entrySet()) + { + String val = System.getenv(var.getKey()); + if (val != null) + { + conf.setProperty(var.getValue(), val); + } + } + } + + private static Configuration parseConfig(File file) throws ConfigurationException + { + ConfigurationFactory factory = new ConfigurationFactory(); + factory.setConfigurationFileName(file.getAbsolutePath()); + Configuration conf = factory.getConfiguration(); + + Iterator<?> keys = conf.getKeys(); + if (!keys.hasNext()) + { + keys = null; + conf = flatConfig(file); + } + + substituteEnvironmentVariables(conf); + + return conf; + } + + /** + * Check the configuration file to see if status updates are enabled. + * + * @return true if status updates are enabled + */ + public boolean getStatusUpdatesEnabled() + { + // Retrieve the setting from configuration but default to on. + String value = getStringValue(STATUS_UPDATES, DEFAULT_STATUS_UPDATES); + + return value.equalsIgnoreCase("on"); + } + + /** + * The currently defined {@see Locale} for this broker + * + * @return the configuration defined locale + */ + public Locale getLocale() + { + String localeString = getStringValue(ADVANCED_LOCALE); + // Expecting locale of format langauge_country_variant + + // If the configuration does not have a defined locale use the JVM default + if (localeString == null) + { + return Locale.getDefault(); + } + + String[] parts = localeString.split("_"); + + Locale locale; + switch (parts.length) + { + case 1: + locale = new Locale(localeString); + break; + case 2: + locale = new Locale(parts[0], parts[1]); + break; + default: + StringBuilder variant = new StringBuilder(parts[2]); + // If we have a variant such as the Java doc suggests for Spanish + // Traditional_WIN we may end up with more than 3 parts on a + // split with '_'. So we should recombine the variant. + if (parts.length > 3) + { + for (int index = 3; index < parts.length; index++) + { + variant.append('_').append(parts[index]); + } + } + + locale = new Locale(parts[0], parts[1], variant.toString()); + } + + return locale; + } + + // Our configuration class needs to make the interpolate method + // public so it can be called below from the config method. + public static class MyConfiguration extends CompositeConfiguration + { + public String interpolate(String obj) + { + return super.interpolate(obj); + } + } + + public final static Configuration flatConfig(File file) throws ConfigurationException + { + // We have to override the interpolate methods so that + // interpolation takes place accross the entirety of the + // composite configuration. Without doing this each + // configuration object only interpolates variables defined + // inside itself. + final MyConfiguration conf = new MyConfiguration(); + conf.addConfiguration(new SystemConfiguration() + { + protected String interpolate(String o) + { + return conf.interpolate(o); + } + }); + conf.addConfiguration(new XMLConfiguration(file) + { + protected String interpolate(String o) + { + return conf.interpolate(o); + } + }); + return conf; + } + + public String getConfigurationURL() + { + return _configFile == null ? "" : _configFile.getAbsolutePath(); + } + + public void handle(Signal arg0) + { + try + { + reparseConfigFileSecuritySections(); + } + catch (ConfigurationException e) + { + _logger.error("Could not reload configuration file security sections", e); + } + } + + public void reparseConfigFileSecuritySections() throws ConfigurationException + { + if (_configFile != null) + { + Configuration newConfig = parseConfig(_configFile); + setConfiguration("", newConfig); + ApplicationRegistry.getInstance().getSecurityManager().configureHostPlugins(this); + + // Reload virtualhosts from correct location + Configuration newVhosts; + if (_vhostsFile == null) + { + newVhosts = newConfig.subset("virtualhosts"); + } + else + { + newVhosts = parseConfig(_vhostsFile); + } + + VirtualHostRegistry vhostRegistry = ApplicationRegistry.getInstance().getVirtualHostRegistry(); + for (String hostName : _virtualHosts.keySet()) + { + VirtualHost vhost = vhostRegistry.getVirtualHost(hostName); + Configuration vhostConfig = newVhosts.subset("virtualhost." + hostName); + vhost.getConfiguration().setConfiguration("virtualhosts.virtualhost", vhostConfig); // XXX + vhost.getSecurityManager().configureGlobalPlugins(this); + vhost.getSecurityManager().configureHostPlugins(vhost.getConfiguration()); + } + + _logger.warn(SECURITY_CONFIG_RELOADED); + } + } + + public String getQpidWork() + { + return System.getProperty(QPID_WORK, System.getProperty("java.io.tmpdir")); + } + + public String getQpidHome() + { + return System.getProperty(QPID_HOME); + } + + public void setJMXManagementPort(int mport) + { + getConfig().setProperty("management.jmxport", mport); + } + + public int getJMXManagementPort() + { + return getIntValue("management.jmxport", DEFAULT_JMXPORT); + } + + public boolean getUseCustomRMISocketFactory() + { + return getBooleanValue(MGMT_CUSTOM_REGISTRY_SOCKET, true); + } + + public void setUseCustomRMISocketFactory(boolean bool) + { + getConfig().setProperty(MGMT_CUSTOM_REGISTRY_SOCKET, bool); + } + + public boolean getPlatformMbeanserver() + { + return getBooleanValue("management.platform-mbeanserver", true); + } + + public String[] getVirtualHosts() + { + return _virtualHosts.keySet().toArray(new String[_virtualHosts.size()]); + } + + public String getPluginDirectory() + { + return getStringValue("plugin-directory"); + } + + public String getCacheDirectory() + { + return getStringValue("cache-directory"); + } + + public VirtualHostConfiguration getVirtualHostConfig(String name) + { + return _virtualHosts.get(name); + } + + public void setVirtualHostConfig(VirtualHostConfiguration config) + { + _virtualHosts.put(config.getName(), config); + } + + public List<String> getPrincipalDatabaseNames() + { + return getListValue("security.principal-databases.principal-database.name"); + } + + public List<String> getPrincipalDatabaseClass() + { + return getListValue("security.principal-databases.principal-database.class"); + } + + public List<String> getPrincipalDatabaseAttributeNames(int index) + { + String name = "security.principal-databases.principal-database(" + index + ")." + "attributes.attribute.name"; + return getListValue(name); + } + + public List<String> getPrincipalDatabaseAttributeValues(int index) + { + String name = "security.principal-databases.principal-database(" + index + ")." + "attributes.attribute.value"; + return getListValue(name); + } + + public List<String> getManagementPrincipalDBs() + { + return getListValue("security.jmx.principal-database"); + } + + public int getFrameSize() + { + return getIntValue("advanced.framesize", DEFAULT_FRAME_SIZE); + } + + public boolean getProtectIOEnabled() + { + return getBooleanValue(CONNECTOR_PROTECTIO_ENABLED, DEFAULT_BROKER_CONNECTOR_PROTECTIO_ENABLED); + } + + public int getBufferReadLimit() + { + return getIntValue(CONNECTOR_PROTECTIO_READ_BUFFER_LIMIT_SIZE, DEFAULT_BUFFER_READ_LIMIT_SIZE); + } + + public int getBufferWriteLimit() + { + return getIntValue(CONNECTOR_PROTECTIO_WRITE_BUFFER_LIMIT_SIZE, DEFAULT_BUFFER_WRITE_LIMIT_SIZE); + } + + public boolean getSynchedClocks() + { + return getBooleanValue("advanced.synced-clocks"); + } + + public boolean getMsgAuth() + { + return getBooleanValue("security.msg-auth"); + } + + public String getJMXPrincipalDatabase() + { + return getStringValue("security.jmx.principal-database"); + } + + public String getManagementKeyStorePath() + { + return getStringValue("management.ssl.keyStorePath"); + } + + public boolean getManagementSSLEnabled() + { + return getBooleanValue("management.ssl.enabled", true); + } + + public String getManagementKeyStorePassword() + { + return getStringValue("management.ssl.keyStorePassword"); + } + + public boolean getQueueAutoRegister() + { + return getBooleanValue("queue.auto_register", true); + } + + public boolean getManagementEnabled() + { + return getBooleanValue("management.enabled", true); + } + + public void setManagementEnabled(boolean enabled) + { + getConfig().setProperty("management.enabled", enabled); + } + + public int getHeartBeatDelay() + { + return getIntValue("heartbeat.delay", 5); + } + + public double getHeartBeatTimeout() + { + return getDoubleValue("heartbeat.timeoutFactor", 2.0); + } + + public int getDeliveryPoolSize() + { + return getIntValue("delivery.poolsize"); + } + + public long getMaximumMessageAge() + { + return getLongValue("maximumMessageAge"); + } + + public long getMaximumMessageCount() + { + return getLongValue("maximumMessageCount"); + } + + public long getMaximumQueueDepth() + { + return getLongValue("maximumQueueDepth"); + } + + public long getMaximumMessageSize() + { + return getLongValue("maximumMessageSize"); + } + + public long getMinimumAlertRepeatGap() + { + return getLongValue("minimumAlertRepeatGap"); + } + + public long getCapacity() + { + return getLongValue("capacity"); + } + + public long getFlowResumeCapacity() + { + return getLongValue("flowResumeCapacity", getCapacity()); + } + + public int getProcessors() + { + return getIntValue("connector.processors", 4); + } + + public List getPorts() + { + return getListValue("connector.port", Collections.singletonList(DEFAULT_PORT)); + } + + public List getPortExclude010() + { + return getListValue("connector.non010port"); + } + + public List getPortExclude091() + { + return getListValue("connector.non091port"); + } + + public List getPortExclude09() + { + return getListValue("connector.non09port"); + } + + public List getPortExclude08() + { + return getListValue("connector.non08port"); + } + + public String getBind() + { + return getStringValue("connector.bind", "wildcard"); + } + + public int getReceiveBufferSize() + { + return getIntValue("connector.socketReceiveBuffer", 32767); + } + + public int getWriteBufferSize() + { + return getIntValue("connector.socketWriteBuffer", 32767); + } + + public boolean getTcpNoDelay() + { + return getBooleanValue("connector.tcpNoDelay", true); + } + + public boolean getEnableExecutorPool() + { + return getBooleanValue("advanced.filterchain[@enableExecutorPool]"); + } + + public boolean getEnableSSL() + { + return getBooleanValue("connector.ssl.enabled"); + } + + public boolean getSSLOnly() + { + return getBooleanValue("connector.ssl.sslOnly"); + } + + public int getSSLPort() + { + return getIntValue("connector.ssl.port", DEFAULT_SSL_PORT); + } + + public String getKeystorePath() + { + return getStringValue("connector.ssl.keystorePath", "none"); + } + + public String getKeystorePassword() + { + return getStringValue("connector.ssl.keystorePassword", "none"); + } + + public String getCertType() + { + return getStringValue("connector.ssl.certType", "SunX509"); + } + + public boolean getQpidNIO() + { + return getBooleanValue("connector.qpidnio"); + } + + public boolean getUseBiasedWrites() + { + return getBooleanValue("advanced.useWriteBiasedPool"); + } + + public String getDefaultVirtualHost() + { + return getStringValue("virtualhosts.default"); + } + + public void setDefaultVirtualHost(String vhost) + { + getConfig().setProperty("virtualhosts.default", vhost); + } + + public void setHousekeepingExpiredMessageCheckPeriod(long value) + { + getConfig().setProperty("housekeeping.expiredMessageCheckPeriod", value); + } + + public long getHousekeepingCheckPeriod() + { + return getLongValue("housekeeping.checkPeriod", + getLongValue("housekeeping.expiredMessageCheckPeriod", + DEFAULT_HOUSEKEEPING_PERIOD)); + } + + public long getStatisticsSamplePeriod() + { + return getConfig().getLong("statistics.sample.period", 5000L); + } + + public boolean isStatisticsGenerationBrokerEnabled() + { + return getConfig().getBoolean("statistics.generation.broker", false); + } + + public boolean isStatisticsGenerationVirtualhostsEnabled() + { + return getConfig().getBoolean("statistics.generation.virtualhosts", false); + } + + public boolean isStatisticsGenerationConnectionsEnabled() + { + return getConfig().getBoolean("statistics.generation.connections", false); + } + + public long getStatisticsReportingPeriod() + { + return getConfig().getLong("statistics.reporting.period", 0L); + } + + public boolean isStatisticsReportResetEnabled() + { + return getConfig().getBoolean("statistics.reporting.reset", false); + } + + public NetworkDriverConfiguration getNetworkConfiguration() + { + return new NetworkDriverConfiguration() + { + + public Integer getTrafficClass() + { + return null; + } + + public Boolean getTcpNoDelay() + { + // Can't call parent getTcpNoDelay since it just calls this one + return getBooleanValue("connector.tcpNoDelay", true); + } + + public Integer getSoTimeout() + { + return null; + } + + public Integer getSoLinger() + { + return null; + } + + public Integer getSendBufferSize() + { + return getBufferWriteLimit(); + } + + public Boolean getReuseAddress() + { + return null; + } + + public Integer getReceiveBufferSize() + { + return getBufferReadLimit(); + } + + public Boolean getOOBInline() + { + return null; + } + + public Boolean getKeepAlive() + { + return null; + } + }; + } + + public int getMaxChannelCount() + { + return getIntValue("maximumChannelCount", 256); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SessionConfig.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SessionConfig.java new file mode 100644 index 0000000000..8fef642eff --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SessionConfig.java @@ -0,0 +1,55 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import org.apache.qpid.AMQException; + +public interface SessionConfig extends ConfiguredObject<SessionConfigType, SessionConfig> +{ + VirtualHostConfig getVirtualHost(); + + String getSessionName(); + + int getChannel(); + + ConnectionConfig getConnectionConfig(); + + boolean isAttached(); + + long getDetachedLifespan(); + + Long getExpiryTime(); + + Long getMaxClientRate(); + + Long getTxnStarts(); + + Long getTxnCommits(); + + Long getTxnRejects(); + + Long getTxnCount(); + + boolean isTransactional(); + + void mgmtClose() throws AMQException; +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SessionConfigType.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SessionConfigType.java new file mode 100644 index 0000000000..97cf275575 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SessionConfigType.java @@ -0,0 +1,136 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import org.apache.qpid.server.exchange.ExchangeType; + +import java.util.*; + +public final class SessionConfigType extends ConfigObjectType<SessionConfigType, SessionConfig> +{ + private static final List<SessionProperty<?>> SESSION_PROPERTIES = new ArrayList<SessionProperty<?>>(); + + public static interface SessionProperty<S> extends ConfigProperty<SessionConfigType, SessionConfig, S> + { + } + + private abstract static class SessionReadWriteProperty<S> extends ConfigProperty.ReadWriteConfigProperty<SessionConfigType, SessionConfig, S> implements SessionProperty<S> + { + public SessionReadWriteProperty(String name) + { + super(name); + SESSION_PROPERTIES.add(this); + } + } + + private abstract static class SessionReadOnlyProperty<S> extends ConfigProperty.ReadOnlyConfigProperty<SessionConfigType, SessionConfig, S> implements SessionProperty<S> + { + public SessionReadOnlyProperty(String name) + { + super(name); + SESSION_PROPERTIES.add(this); + } + } + + public static final SessionReadOnlyProperty<VirtualHostConfig> VIRTUAL_HOST_PROPERTY = new SessionReadOnlyProperty<VirtualHostConfig>("virtualHost") + { + public VirtualHostConfig getValue(SessionConfig object) + { + return object.getVirtualHost(); + } + }; + + public static final SessionReadOnlyProperty<String> NAME_PROPERTY = new SessionReadOnlyProperty<String>("name") + { + public String getValue(SessionConfig object) + { + return object.getSessionName(); + } + }; + + public static final SessionReadOnlyProperty<Integer> CHANNEL_ID_PROPERTY = new SessionReadOnlyProperty<Integer>("channelId") + { + public Integer getValue(SessionConfig object) + { + return object.getChannel(); + } + }; + + public static final SessionReadOnlyProperty<ConnectionConfig> CONNECTION_PROPERTY = new SessionReadOnlyProperty<ConnectionConfig>("connection") + { + public ConnectionConfig getValue(SessionConfig object) + { + return object.getConnectionConfig(); + } + }; + + public static final SessionReadOnlyProperty<Boolean> ATTACHED_PROPERTY = new SessionReadOnlyProperty<Boolean>("attached") + { + public Boolean getValue(SessionConfig object) + { + return object.isAttached(); + } + }; + + public static final SessionReadOnlyProperty<Long> DETACHED_LIFESPAN_PROPERTY = new SessionReadOnlyProperty<Long>("detachedLifespan") + { + public Long getValue(SessionConfig object) + { + return object.getDetachedLifespan(); + } + }; + + public static final SessionReadOnlyProperty<Long> EXPIRE_TIME_PROPERTY = new SessionReadOnlyProperty<Long>("expireTime") + { + public Long getValue(SessionConfig object) + { + return object.getExpiryTime(); + } + }; + + public static final SessionReadOnlyProperty<Long> MAX_CLIENT_RATE_PROPERTY = new SessionReadOnlyProperty<Long>("maxClientRate") + { + public Long getValue(SessionConfig object) + { + return object.getMaxClientRate(); + } + }; + + private static final SessionConfigType INSTANCE = new SessionConfigType(); + + private SessionConfigType() + { + } + + public Collection<SessionProperty<?>> getProperties() + { + return Collections.unmodifiableList(SESSION_PROPERTIES); + } + + public static SessionConfigType getInstance() + { + return INSTANCE; + } + + + +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SubscriptionConfig.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SubscriptionConfig.java new file mode 100644 index 0000000000..b101d70553 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SubscriptionConfig.java @@ -0,0 +1,47 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import java.util.Map; + + +public interface SubscriptionConfig extends ConfiguredObject<SubscriptionConfigType, SubscriptionConfig> +{ + + SessionConfig getSessionConfig(); + + QueueConfig getQueue(); + + String getName(); + + Map<String, Object> getArguments(); + + String getCreditMode(); + + boolean isBrowsing(); + + boolean isExclusive(); + + boolean isExplicitAcknowledge(); + + Long getDelivered(); +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SubscriptionConfigType.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SubscriptionConfigType.java new file mode 100644 index 0000000000..99d3273b55 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SubscriptionConfigType.java @@ -0,0 +1,136 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + + +import java.util.*; + +public final class SubscriptionConfigType extends ConfigObjectType<SubscriptionConfigType, SubscriptionConfig> +{ + private static final List<SubscriptionProperty<?>> SUBSCRIPTION_PROPERTIES = new ArrayList<SubscriptionProperty<?>>(); + + public static interface SubscriptionProperty<S> extends ConfigProperty<SubscriptionConfigType, SubscriptionConfig, S> + { + } + + private abstract static class SubscriptionReadWriteProperty<S> extends ConfigProperty.ReadWriteConfigProperty<SubscriptionConfigType, SubscriptionConfig, S> implements SubscriptionProperty<S> + { + public SubscriptionReadWriteProperty(String name) + { + super(name); + SUBSCRIPTION_PROPERTIES.add(this); + } + } + + private abstract static class SubscriptionReadOnlyProperty<S> extends ConfigProperty.ReadOnlyConfigProperty<SubscriptionConfigType, SubscriptionConfig, S> implements SubscriptionProperty<S> + { + public SubscriptionReadOnlyProperty(String name) + { + super(name); + SUBSCRIPTION_PROPERTIES.add(this); + } + } + + public static final SubscriptionReadOnlyProperty<SessionConfig> SESSION_PROPERTY = new SubscriptionReadOnlyProperty<SessionConfig>("session") + { + public SessionConfig getValue(SubscriptionConfig object) + { + return object.getSessionConfig(); + } + }; + + public static final SubscriptionReadOnlyProperty<QueueConfig> QUEUE_PROPERTY = new SubscriptionReadOnlyProperty<QueueConfig>("queue") + { + public QueueConfig getValue(SubscriptionConfig object) + { + return object.getQueue(); + } + }; + + public static final SubscriptionReadOnlyProperty<String> NAME_PROPERTY = new SubscriptionReadOnlyProperty<String>("name") + { + public String getValue(SubscriptionConfig object) + { + return object.getName(); + } + }; + + public static final SubscriptionReadOnlyProperty<Map<String,Object>> ARGUMENTS = new SubscriptionReadOnlyProperty<Map<String,Object>>("arguments") + { + public Map<String,Object> getValue(SubscriptionConfig object) + { + return object.getArguments(); + } + }; + + public static final SubscriptionReadOnlyProperty<String> CREDIT_MODE_PROPERTY = new SubscriptionReadOnlyProperty<String>("creditMode") + { + public String getValue(SubscriptionConfig object) + { + return object.getCreditMode(); + } + }; + + public static final SubscriptionReadOnlyProperty<Boolean> BROWSING_PROPERTY = new SubscriptionReadOnlyProperty<Boolean>("browsing") + { + public Boolean getValue(SubscriptionConfig object) + { + return object.isBrowsing(); + } + }; + + public static final SubscriptionReadOnlyProperty<Boolean> EXCLUSIVE_PROPERTY = new SubscriptionReadOnlyProperty<Boolean>("exclusive") + { + public Boolean getValue(SubscriptionConfig object) + { + return object.isExclusive(); + } + }; + + public static final SubscriptionReadOnlyProperty<Boolean> EXPLICIT_ACK_PROPERTY = new SubscriptionReadOnlyProperty<Boolean>("explicitAck") + { + public Boolean getValue(SubscriptionConfig object) + { + return object.isExplicitAcknowledge(); + } + }; + + private static final SubscriptionConfigType INSTANCE = new SubscriptionConfigType(); + + private SubscriptionConfigType() + { + } + + public Collection<SubscriptionProperty<?>> getProperties() + { + return Collections.unmodifiableList(SUBSCRIPTION_PROPERTIES); + } + + + public static SubscriptionConfigType getInstance() + { + return INSTANCE; + } + + + +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SystemConfig.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SystemConfig.java new file mode 100644 index 0000000000..8a9029fbfd --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SystemConfig.java @@ -0,0 +1,42 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +public interface SystemConfig extends ConfiguredObject<SystemConfigType,SystemConfig> +{ + String getName(); + + String getOperatingSystemName(); + + String getNodeName(); + + + String getOSRelease(); + + String getOSVersion(); + + String getOSArchitecture(); + + void addBroker(BrokerConfig broker); + + void removeBroker(BrokerConfig broker); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SystemConfigImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SystemConfigImpl.java new file mode 100644 index 0000000000..09ebb07105 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SystemConfigImpl.java @@ -0,0 +1,136 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import java.util.UUID; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.net.InetAddress; +import java.net.UnknownHostException; + +public class SystemConfigImpl implements SystemConfig +{ + private static final String OS_NAME = System.getProperty("os.name"); + private static final String OS_ARCH = System.getProperty("os.arch"); + private static final String OS_VERSION = System.getProperty("os.version"); + + private final UUID _id; + private String _name; + + private final String _host; + + private final Map<UUID, BrokerConfig> _brokers = new ConcurrentHashMap<UUID, BrokerConfig>(); + + private final long _createTime = System.currentTimeMillis(); + private final ConfigStore _store; + + public SystemConfigImpl(ConfigStore store) + { + this(store.createId(), store); + } + + public SystemConfigImpl(UUID id, ConfigStore store) + { + _id = id; + _store = store; + String host; + try + { + InetAddress addr = InetAddress.getLocalHost(); + host = addr.getHostName(); + } + catch (UnknownHostException e) + { + host="localhost"; + } + _host = host; + } + + public String getName() + { + return _name; + } + + public String getOperatingSystemName() + { + return OS_NAME; + } + + public String getNodeName() + { + return _host; + } + + public String getOSRelease() + { + return OS_VERSION; + } + + public String getOSVersion() + { + return ""; + } + + public String getOSArchitecture() + { + return OS_ARCH; + } + + public UUID getId() + { + return _id; + } + + public SystemConfigType getConfigType() + { + return SystemConfigType.getInstance(); + } + + public ConfiguredObject getParent() + { + return null; + } + + public boolean isDurable() + { + return false; + } + + public void addBroker(final BrokerConfig broker) + { + broker.setSystem(this); + _store.addConfiguredObject(broker); + _brokers.put(broker.getId(), broker); + } + + public void removeBroker(final BrokerConfig broker) + { + _brokers.remove(broker.getId()); + _store.removeConfiguredObject(broker); + } + + public long getCreateTime() + { + return _createTime; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SystemConfigType.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SystemConfigType.java new file mode 100644 index 0000000000..f5aabd2345 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/SystemConfigType.java @@ -0,0 +1,128 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import java.util.*; + +public final class SystemConfigType extends ConfigObjectType<SystemConfigType, SystemConfig> +{ + private static final List<SystemProperty<?>> SYSTEM_PROPERTIES = new ArrayList<SystemProperty<?>>(); + + public static interface SystemProperty<S> extends ConfigProperty<SystemConfigType, SystemConfig, S> + { + } + + private abstract static class SystemReadWriteProperty<S> extends ConfigProperty.ReadWriteConfigProperty<SystemConfigType, SystemConfig, S> implements SystemProperty<S> + { + public SystemReadWriteProperty(String name) + { + super(name); + SYSTEM_PROPERTIES.add(this); + } + } + + private abstract static class SystemReadOnlyProperty<S> extends ConfigProperty.ReadOnlyConfigProperty<SystemConfigType, SystemConfig, S> implements SystemProperty<S> + { + public SystemReadOnlyProperty(String name) + { + super(name); + SYSTEM_PROPERTIES.add(this); + } + } + + public static final SystemReadOnlyProperty<String> NAME_PROPERTY = new SystemReadOnlyProperty<String>("name") + { + public String getValue(SystemConfig object) + { + return object.getName(); + } + }; + + public static final SystemReadOnlyProperty<UUID> ID_PROPERTY = new SystemReadOnlyProperty<UUID>("id") + { + public UUID getValue(SystemConfig object) + { + return object.getId(); + } + }; + + public static final SystemReadOnlyProperty<String> OS_NAME_PROPERTY = new SystemReadOnlyProperty<String>("osName") + { + public String getValue(SystemConfig object) + { + return object.getOperatingSystemName(); + } + }; + + public static final SystemReadOnlyProperty<String> NODE_NAME_PROPERTY = new SystemReadOnlyProperty<String>("nodeName") + { + public String getValue(SystemConfig object) + { + return object.getNodeName(); + } + }; + + public static final SystemReadOnlyProperty<String> RELEASE_PROPERTY = new SystemReadOnlyProperty<String>("release") + { + public String getValue(SystemConfig object) + { + return object.getOSRelease(); + } + }; + + public static final SystemReadOnlyProperty<String> VERSION_PROPERTY = new SystemReadOnlyProperty<String>("version") + { + public String getValue(SystemConfig object) + { + return object.getOSVersion(); + } + }; + + public static final SystemReadOnlyProperty<String> MACHINE_PROPERTY = new SystemReadOnlyProperty<String>("machine") + { + public String getValue(SystemConfig object) + { + return object.getOSArchitecture(); + } + }; + + private static final SystemConfigType INSTANCE = new SystemConfigType(); + + private SystemConfigType() + { + } + + public Collection<SystemProperty<?>> getProperties() + { + return Collections.unmodifiableList(SYSTEM_PROPERTIES); + } + + + + public static SystemConfigType getInstance() + { + return INSTANCE; + } + + + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/TopicConfig.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/TopicConfig.java new file mode 100644 index 0000000000..d5420d9718 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/TopicConfig.java @@ -0,0 +1,78 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; + +public class TopicConfig extends ConfigurationPlugin +{ + public TopicConfig() + { + _configuration = new PropertiesConfiguration(); + } + + @Override + public String[] getElementsProcessed() + { + return new String[]{"name", "subscriptionName"}; + } + + public String getName() + { + // If we don't have a specific topic then this config is for all topics. + return getStringValue("name", "#"); + } + + public String getSubscriptionName() + { + return getStringValue("subscriptionName"); + } + + public void validateConfiguration() throws ConfigurationException + { + if (_configuration.isEmpty()) + { + throw new ConfigurationException("Topic section cannot be empty."); + } + + if (getStringValue("name") == null && getSubscriptionName() == null) + { + throw new ConfigurationException("Topic section must have a 'name' or 'subscriptionName' element."); + } + + System.err.println("********* Created TC:"+this); + } + + + @Override + public String formatToString() + { + String response = "Topic:"+getName(); + if (getSubscriptionName() != null) + { + response += ", SubscriptionName:"+getSubscriptionName(); + } + + return response; + } +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/TopicConfiguration.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/TopicConfiguration.java new file mode 100644 index 0000000000..8716fed8c1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/TopicConfiguration.java @@ -0,0 +1,269 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory; +import org.apache.qpid.server.exchange.TopicExchange; +import org.apache.qpid.server.queue.AMQQueue; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +public class TopicConfiguration extends ConfigurationPlugin implements ExchangeConfigurationPlugin +{ + public static final ConfigurationPluginFactory FACTORY = new TopicConfigurationFactory(); + + private static final String VIRTUALHOSTS_VIRTUALHOST_TOPICS = "virtualhosts.virtualhost.topics"; + + public static class TopicConfigurationFactory implements ConfigurationPluginFactory + { + + public ConfigurationPlugin newInstance(String path, Configuration config) throws ConfigurationException + { + TopicConfiguration topicsConfig = new TopicConfiguration(); + topicsConfig.setConfiguration(path, config); + return topicsConfig; + } + + public List<String> getParentPaths() + { + return Arrays.asList(VIRTUALHOSTS_VIRTUALHOST_TOPICS); + } + } + + Map<String, TopicConfig> _topics = new HashMap<String, TopicConfig>(); + Map<String, Map<String, TopicConfig>> _subscriptions = new HashMap<String, Map<String, TopicConfig>>(); + + public String[] getElementsProcessed() + { + return new String[]{"topic"}; + } + + @Override + public void validateConfiguration() throws ConfigurationException + { + if (_configuration.isEmpty()) + { + throw new ConfigurationException("Topics section cannot be empty."); + } + + int topics = _configuration.getList("topic.name").size() + + _configuration.getList("topic.subscriptionName").size(); + + for (int index = 0; index < topics; index++) + { + Configuration topicSubset = _configuration.subset("topic(" + index + ")"); + + // This will occur when we have a subscriptionName that is bound to a + // topic. + if (topicSubset.isEmpty()) + { + break; + } + + TopicConfig topic = new TopicConfig(); + + topic.setConfiguration(VIRTUALHOSTS_VIRTUALHOST_TOPICS + ".topic", topicSubset ); + + String name = _configuration.getString("topic(" + index + ").name"); + String subscriptionName = _configuration.getString("topic(" + index + ").subscriptionName"); + + // Record config if subscriptionName is there + if (subscriptionName != null) + { + processSubscription(subscriptionName, topic); + } + else + { + // Otherwise record config as topic if we have the name + if (name != null) + { + processTopic(name, topic); + } + } + } + } + + /** + * @param name + * @param topic + * + * @throws org.apache.commons.configuration.ConfigurationException + * + */ + private void processTopic(String name, TopicConfig topic) throws ConfigurationException + { + if (_topics.containsKey(name)) + { + throw new ConfigurationException("Topics section cannot contain two entries for the same topic."); + } + else + { + _topics.put(name, topic); + } + } + + + private void processSubscription(String name, TopicConfig topic) throws ConfigurationException + { + Map<String,TopicConfig> topics; + if (_subscriptions.containsKey(name)) + { + topics = _subscriptions.get(name); + + if (topics.containsKey(topic.getName())) + { + throw new ConfigurationException("Subcription cannot contain two entries for the same topic."); + } + } + else + { + topics = new HashMap<String,TopicConfig>(); + } + + topics.put(topic.getName(),topic); + _subscriptions.put(name, topics); + + } + + @Override + public String formatToString() + { + return "Topics:" + _topics + ", Subscriptions:" + _subscriptions; + } + + /** + * This processes the given queue and apply configuration in the following + * order: + * + * Global Topic Values -> Topic Values -> Subscription Values + * + * @param queue + * + * @return + */ + public ConfigurationPlugin getConfiguration(AMQQueue queue) + { + //Create config with global topic configuration + TopicConfig config = new TopicConfig(); + + // Add global topic configuration + config.addConfiguration(this); + + // Process Topic Bindings as these are more generic than subscriptions + List<TopicConfig> boundToTopics = new LinkedList<TopicConfig>(); + + //Merge the configuration in the order that they are bound + for (Binding binding : queue.getBindings()) + { + if (binding.getExchange().getType().equals(TopicExchange.TYPE)) + { + // Identify topic for the binding key + TopicConfig topicConfig = getTopicConfigForRoutingKey(binding.getBindingKey()); + if (topicConfig != null) + { + boundToTopics.add(topicConfig); + } + } + } + + // If the Queue is bound to a number of topics then only use the global + // topic configuration. + // todo - What does it mean in terms of configuration to be bound to a + // number of topics? Do we try and merge? + // YES - right thing to do would be to merge from generic to specific. + // Means we need to be able to get an ordered list of topics for this + // binding. + if (boundToTopics.size() == 1) + { + config.addConfiguration(boundToTopics.get(0)); + } + + // If we have a subscription then attempt to look it up. + String subscriptionName = queue.getName(); + + // Apply subscription configurations + if (_subscriptions.containsKey(subscriptionName)) + { + + //Get all the Configuration that this subscription is bound to. + Map<String, TopicConfig> topics = _subscriptions.get(subscriptionName); + + TopicConfig subscriptionSpecificConfig = null; + + // See if we have a TopicConfig in topics for a topic we are bound to. + for (Binding binding : queue.getBindings()) + { + if (binding.getExchange().getType().equals(TopicExchange.TYPE)) + { + //todo - What does it mean to have multiple matches? + // Take the first match we get + if (subscriptionSpecificConfig == null) + { + // lookup the binding to see if we have a match in the subscription configs + subscriptionSpecificConfig = topics.get(binding.getBindingKey()); + } + } + } + + //todo we don't account for wild cards here. only explicit matching and all subscriptions + if (subscriptionSpecificConfig == null) + { + // lookup the binding to see if we have a match in the subscription configs + subscriptionSpecificConfig = topics.get("#"); + } + + // Apply subscription specific config. + if (subscriptionSpecificConfig != null) + { + config.addConfiguration(subscriptionSpecificConfig); + } + } + return config; + } + + /** + * This method should perform the same heuristics as the TopicExchange + * to attempt to identify a piece of configuration for the give routingKey. + * + * i.e. If we have 'stocks.*' defined in the config + * and we bind 'stocks.appl' then we should return the 'stocks.*' + * configuration. + * + * @param routingkey the key to lookup + * + * @return the TopicConfig if found. + */ + private TopicConfig getTopicConfigForRoutingKey(String routingkey) + { + //todo actually perform TopicExchange style lookup not just straight + // lookup as we are just now. + return _topics.get(routingkey); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfig.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfig.java new file mode 100644 index 0000000000..9256724c56 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfig.java @@ -0,0 +1,34 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +public interface VirtualHostConfig extends ConfiguredObject<VirtualHostConfigType, VirtualHostConfig> +{ + String getName(); + + BrokerConfig getBroker(); + + String getFederationTag(); + + void setBroker(BrokerConfig brokerConfig); + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfigType.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfigType.java new file mode 100644 index 0000000000..96682335bf --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfigType.java @@ -0,0 +1,96 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import java.util.*; + +public class VirtualHostConfigType extends ConfigObjectType<VirtualHostConfigType, VirtualHostConfig> +{ + private static final List<VirtualHostProperty<?>> VIRTUAL_HOST_PROPERTIES = new ArrayList<VirtualHostProperty<?>>(); + private static final VirtualHostConfigType INSTANCE = new VirtualHostConfigType(); +public static interface VirtualHostProperty<S> extends ConfigProperty<VirtualHostConfigType, VirtualHostConfig, S> + { + } + + private abstract static class VirtualHostReadWriteProperty<S> extends ConfigProperty.ReadWriteConfigProperty<VirtualHostConfigType, VirtualHostConfig, S> implements VirtualHostProperty<S> + { + public VirtualHostReadWriteProperty(String name) + { + super(name); + VIRTUAL_HOST_PROPERTIES.add(this); + } + } + + private abstract static class VirtualHostReadOnlyProperty<S> extends ConfigProperty.ReadOnlyConfigProperty<VirtualHostConfigType, VirtualHostConfig, S> implements VirtualHostProperty<S> + { + public VirtualHostReadOnlyProperty(String name) + { + super(name); + VIRTUAL_HOST_PROPERTIES.add(this); + } + } + + + public static final VirtualHostReadOnlyProperty<String> NAME_PROPERTY = new VirtualHostReadOnlyProperty<String>("name") + { + public String getValue(VirtualHostConfig object) + { + return object.getName(); + } + }; + + + public static final VirtualHostReadOnlyProperty<BrokerConfig> BROKER_PROPERTY = new VirtualHostReadOnlyProperty<BrokerConfig>("broker") + { + public BrokerConfig getValue(VirtualHostConfig object) + { + return object.getBroker(); + } + }; + + public static final VirtualHostReadOnlyProperty<String> FEDERATION_TAG_PROPERTY = new VirtualHostReadOnlyProperty<String>("federationTag") + { + public String getValue(VirtualHostConfig object) + { + return object.getFederationTag(); + } + }; + + + + public Collection<? extends ConfigProperty<VirtualHostConfigType, VirtualHostConfig, ?>> getProperties() + { + return Collections.unmodifiableList(VIRTUAL_HOST_PROPERTIES); + } + + + private VirtualHostConfigType() + { + } + + public static VirtualHostConfigType getInstance() + { + return INSTANCE; + } + + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java new file mode 100644 index 0000000000..a710230616 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/VirtualHostConfiguration.java @@ -0,0 +1,342 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.commons.configuration.CompositeConfiguration; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.store.MemoryMessageStore; + +public class VirtualHostConfiguration extends ConfigurationPlugin +{ + private String _name; + private Map<String, QueueConfiguration> _queues = new HashMap<String, QueueConfiguration>(); + private Map<String, ExchangeConfiguration> _exchanges = new HashMap<String, ExchangeConfiguration>(); + + public VirtualHostConfiguration(String name, Configuration config) throws ConfigurationException + { + _name = name; + setConfiguration(config); + } + + /** + * Apply the given configuration to this VirtualHostConfiguration + * + * @param config the config to apply + * @throws ConfigurationException if a problem occurs with configuration + */ + public void setConfiguration(Configuration config) throws ConfigurationException + { + setConfiguration("virtualhosts.virtualhost", config); + + Iterator i = getListValue("queues.queue.name").iterator(); + + while (i.hasNext()) + { + String queueName = (String) i.next(); + _queues.put(queueName, new QueueConfiguration(queueName, this)); + } + + i = getListValue("exchanges.exchange.name").iterator(); + int count = 0; + while (i.hasNext()) + { + CompositeConfiguration mungedConf = new CompositeConfiguration(); + mungedConf.addConfiguration(config.subset("exchanges.exchange(" + count++ + ")")); + mungedConf.addConfiguration(_configuration.subset("exchanges")); + String exchName = (String) i.next(); + _exchanges.put(exchName, new ExchangeConfiguration(exchName, mungedConf)); + } + } + + public String getName() + { + return _name; + } + + public long getHousekeepingExpiredMessageCheckPeriod() + { + return getLongValue("housekeeping.expiredMessageCheckPeriod", ApplicationRegistry.getInstance().getConfiguration().getHousekeepingCheckPeriod()); + } + + public String getAuthenticationDatabase() + { + return getStringValue("security.authentication.name"); + } + + public List getCustomExchanges() + { + return getListValue("custom-exchanges.class-name"); + } + + public Configuration getStoreConfiguration() + { + return _configuration.subset("store"); + } + + public String getMessageStoreClass() + { + return getStringValue("store.class", MemoryMessageStore.class.getName()); + } + + public void setMessageStoreClass(String storeClass) + { + _configuration.setProperty("store.class", storeClass); + } + + public List getExchanges() + { + return getListValue("exchanges.exchange.name"); + } + + public String[] getQueueNames() + { + return _queues.keySet().toArray(new String[_queues.size()]); + } + + public ExchangeConfiguration getExchangeConfiguration(String exchangeName) + { + return _exchanges.get(exchangeName); + } + + public QueueConfiguration getQueueConfiguration(String queueName) + { + // We might be asked for the config for a queue we don't know about, + // such as one that's been dynamically created. Those get the defaults by default. + if (_queues.containsKey(queueName)) + { + return _queues.get(queueName); + } + else + { + try + { + return new QueueConfiguration(queueName, this); + } + catch (ConfigurationException e) + { + // The configuration is empty so there can't be an error. + return null; + } + } + } + + public ConfigurationPlugin getQueueConfiguration(AMQQueue queue) + { + VirtualHostConfiguration hostConfig = queue.getVirtualHost().getConfiguration(); + + // First check if we have a named queue configuration (the easy case) + if (Arrays.asList(hostConfig.getQueueNames()).contains(queue.getName())) + { + return null; + } + + // We don't have an explicit queue config we must find out what we need. + ArrayList<Binding> bindings = new ArrayList<Binding>(queue.getBindings()); + + List<AMQShortString> exchangeClasses = new ArrayList<AMQShortString>(bindings.size()); + + //Remove default exchange + for (int index = 0; index < bindings.size(); index++) + { + // Ignore the DEFAULT Exchange binding + if (bindings.get(index).getExchange().getNameShortString().equals(ExchangeDefaults.DEFAULT_EXCHANGE_NAME)) + { + bindings.remove(index); + } + else + { + exchangeClasses.add(bindings.get(index).getExchange().getType().getName()); + + if (exchangeClasses.size() > 1) + { + // If we have more than 1 class of exchange then we can only use the global queue configuration. + // and this will be returned from the default getQueueConfiguration + return null; + } + } + } + + // If we are just bound the the default exchange then use the default. + if (bindings.isEmpty()) + { + return null; + } + + // If we are bound to only one type of exchange then we are going + // to have to resolve the configuration for that exchange. + + String exchangeName = bindings.get(0).getExchange().getType().getName().toString(); + + // Lookup a Configuration handler for this Exchange. + + // Build the expected class name. <Exchangename>sConfiguration + // i.e. TopicConfiguration or HeadersConfiguration + String exchangeClass = "org.apache.qpid.server.configuration." + + exchangeName.substring(0, 1).toUpperCase() + + exchangeName.substring(1) + "Configuration"; + + ExchangeConfigurationPlugin exchangeConfiguration + = (ExchangeConfigurationPlugin) queue.getVirtualHost().getConfiguration().getConfiguration(exchangeClass); + + // now need to perform the queue-topic-topics-queues magic. + // So make a new ConfigurationObject that will hold all the configuration for this queue. + ConfigurationPlugin queueConfig = new QueueConfiguration.QueueConfig(); + + // Initialise the queue with any Global values we may have + PropertiesConfiguration newQueueConfig = new PropertiesConfiguration(); + newQueueConfig.setProperty("name", queue.getName()); + + try + { + //Set the queue name + CompositeConfiguration mungedConf = new CompositeConfiguration(); + //Set the queue name + mungedConf.addConfiguration(newQueueConfig); + //Set the global queue configuration + mungedConf.addConfiguration(getConfig().subset("queues")); + + // Set configuration + queueConfig.setConfiguration("virtualhosts.virtualhost.queues", mungedConf); + } + catch (ConfigurationException e) + { + // This will not occur as queues only require a name. + _logger.error("QueueConfiguration requirements have changed."); + } + + // Merge any configuration the Exchange wishes to apply + if (exchangeConfiguration != null) + { + queueConfig.addConfiguration(exchangeConfiguration.getConfiguration(queue)); + } + + //Finally merge in any specific queue configuration we have. + if (_queues.containsKey(queue.getName())) + { + queueConfig.addConfiguration(_queues.get(queue.getName())); + } + + return queueConfig; + } + + public long getMemoryUsageMaximum() + { + return getLongValue("queues.maximumMemoryUsage"); + } + + public long getMemoryUsageMinimum() + { + return getLongValue("queues.minimumMemoryUsage"); + } + + public int getMaximumMessageAge() + { + return getIntValue("queues.maximumMessageAge"); + } + + public Long getMaximumQueueDepth() + { + return getLongValue("queues.maximumQueueDepth"); + } + + public Long getMaximumMessageSize() + { + return getLongValue("queues.maximumMessageSize"); + } + + public Long getMaximumMessageCount() + { + return getLongValue("queues.maximumMessageCount"); + } + + public Long getMinimumAlertRepeatGap() + { + return getLongValue("queues.minimumAlertRepeatGap"); + } + + public long getCapacity() + { + return getLongValue("queues.capacity"); + } + + public long getFlowResumeCapacity() + { + return getLongValue("queues.flowResumeCapacity", getCapacity()); + } + + public String[] getElementsProcessed() + { + return new String[]{"queues", "exchanges", "custom-exchanges", "store", "housekeeping"}; + + } + + @Override + public void validateConfiguration() throws ConfigurationException + { + // QPID-3249. Support for specifying authentication name at vhost level is no longer supported. + if (getListValue("security.authentication.name").size() > 0) + { + String message = "Validation error : security/authentication/name is no longer a supported element within the configuration xml." + + " It appears in virtual host definition : " + _name; + throw new ConfigurationException(message); + } + } + + public int getHouseKeepingThreadCount() + { + return getIntValue("housekeeping.poolSize", Runtime.getRuntime().availableProcessors()); + } + + public long getTransactionTimeoutOpenWarn() + { + return getLongValue("transactionTimeout.openWarn", 0L); + } + + public long getTransactionTimeoutOpenClose() + { + return getLongValue("transactionTimeout.openClose", 0L); + } + + public long getTransactionTimeoutIdleWarn() + { + return getLongValue("transactionTimeout.idleWarn", 0L); + } + + public long getTransactionTimeoutIdleClose() + { + return getLongValue("transactionTimeout.idleClose", 0L); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/management/ConfigurationManagementMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/management/ConfigurationManagementMBean.java new file mode 100644 index 0000000000..cc402d5b4a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/management/ConfigurationManagementMBean.java @@ -0,0 +1,48 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration.management; + +import javax.management.NotCompliantMBeanException; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.management.common.mbeans.ConfigurationManagement; +import org.apache.qpid.server.management.AMQManagedObject; +import org.apache.qpid.server.registry.ApplicationRegistry; + +public class ConfigurationManagementMBean extends AMQManagedObject implements ConfigurationManagement +{ + + public ConfigurationManagementMBean() throws NotCompliantMBeanException + { + super(ConfigurationManagement.class, ConfigurationManagement.TYPE); + } + + public String getObjectInstanceName() + { + return ConfigurationManagement.TYPE; + } + + public void reloadSecurityConfiguration() throws Exception + { + ApplicationRegistry.getInstance().getConfiguration().reparseConfigFileSecuritySections(); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/plugins/ConfigurationPlugin.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/plugins/ConfigurationPlugin.java new file mode 100644 index 0000000000..82b576ea51 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/plugins/ConfigurationPlugin.java @@ -0,0 +1,454 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration.plugins; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.ConversionException; +import org.apache.log4j.Logger; +import org.apache.qpid.server.configuration.ConfigurationManager; +import org.apache.qpid.server.registry.ApplicationRegistry; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +public abstract class ConfigurationPlugin +{ + protected static final Logger _logger = Logger.getLogger(ConfigurationPlugin.class); + + private Map<String, ConfigurationPlugin> + _pluginConfiguration = new HashMap<String, ConfigurationPlugin>(); + + protected Configuration _configuration; + + /** + * The Elements that this Plugin can process. + * + * For a Queues plugin that would be a list containing: + * <ul> + * <li>queue - the queue entries + * <li>the alerting values for defaults + * <li>exchange - the default exchange + * <li>durable - set the default durablity + * </ul> + */ + abstract public String[] getElementsProcessed(); + + /** Performs configuration validation. */ + public void validateConfiguration() throws ConfigurationException + { + // Override in sub-classes + } + + public Configuration getConfig() + { + return _configuration; + } + + public <C extends ConfigurationPlugin> C getConfiguration(String plugin) + { + return (C) _pluginConfiguration.get(plugin); + } + + /** + * Sets the configuration for this plugin + * + * @param path + * @param configuration the configuration for this plugin. + */ + public void setConfiguration(String path, Configuration configuration) throws ConfigurationException + { + _configuration = configuration; + + // Extract a list of elements for processing + Iterator<?> keys = configuration.getKeys(); + + Set<String> elements = new HashSet<String>(); + while (keys.hasNext()) + { + String key = (String) keys.next(); + + int elementNameIndex = key.indexOf("."); + + String element = key.trim(); + if (elementNameIndex != -1) + { + element = key.substring(0, elementNameIndex).trim(); + } + + // Trim any element properties + elementNameIndex = element.indexOf("["); + if (elementNameIndex > 0) + { + element = element.substring(0, elementNameIndex).trim(); + } + + elements.add(element); + } + + //Remove the items we already expect in the configuration + for (String tag : getElementsProcessed()) + { + + // Work round the issue with Commons configuration. + // With an XMLConfiguration the key will be [@property] + // but with a CompositeConfiguration it will be @property]. + // Hide this issue from our users so when/if we change the + // configuration they don't have to. + int bracketIndex = tag.indexOf("["); + if (bracketIndex != -1) + { + tag = tag.substring(bracketIndex + 1, tag.length()); + } + + elements.remove(tag); + } + + if (_logger.isInfoEnabled()) + { + if (!elements.isEmpty()) + { + _logger.info("Elements to lookup:" + path); + for (String tag : elements) + { + _logger.info("Tag:'" + tag + "'"); + } + } + } + + // Process the elements in the configuration + for (String element : elements) + { + ConfigurationManager configurationManager = ApplicationRegistry.getInstance().getConfigurationManager(); + Configuration handled = element.length() == 0 ? configuration : configuration.subset(element); + + String configurationElement = element; + if (path.length() > 0) + { + configurationElement = path + "." + configurationElement; + } + + List<ConfigurationPlugin> handlers = configurationManager.getConfigurationPlugins(configurationElement, handled); + + if(_logger.isDebugEnabled()) + { + _logger.debug("For '" + element + "' found handlers (" + handlers.size() + "):" + handlers); + } + + for (ConfigurationPlugin plugin : handlers) + { + _pluginConfiguration.put(plugin.getClass().getName(), plugin); + } + } + + validateConfiguration(); + } + + /** Helper method to print out list of keys in a {@link Configuration}. */ + public static final void showKeys(Configuration config) + { + if (config.isEmpty()) + { + _logger.info("Configuration is empty"); + } + else + { + Iterator<?> keys = config.getKeys(); + while (keys.hasNext()) + { + String key = (String) keys.next(); + _logger.info("Configuration key: " + key); + } + } + } + + protected boolean hasConfiguration() + { + return _configuration != null; + } + + /// Getters + + protected double getDoubleValue(String property) + { + return getDoubleValue(property, 0.0); + } + + protected double getDoubleValue(String property, double defaultValue) + { + return _configuration.getDouble(property, defaultValue); + } + + protected long getLongValue(String property) + { + return getLongValue(property, 0); + } + + protected long getLongValue(String property, long defaultValue) + { + return _configuration.getLong(property, defaultValue); + } + + protected int getIntValue(String property) + { + return getIntValue(property, 0); + } + + protected int getIntValue(String property, int defaultValue) + { + return _configuration.getInt(property, defaultValue); + } + + protected String getStringValue(String property) + { + return getStringValue(property, null); + } + + protected String getStringValue(String property, String defaultValue) + { + return _configuration.getString(property, defaultValue); + } + + protected boolean getBooleanValue(String property) + { + return getBooleanValue(property, false); + } + + protected boolean getBooleanValue(String property, boolean defaultValue) + { + return _configuration.getBoolean(property, defaultValue); + } + + protected List getListValue(String property) + { + return getListValue(property, Collections.EMPTY_LIST); + } + + protected List getListValue(String property, List defaultValue) + { + return _configuration.getList(property, defaultValue); + } + + /// Validation Helpers + + protected boolean contains(String property) + { + return _configuration.getProperty(property) != null; + } + + /** + * Provide mechanism to validate Configuration contains a Postiive Long Value + * + * @param property + * + * @throws ConfigurationException + */ + protected void validatePositiveLong(String property) throws ConfigurationException + { + try + { + if (!containsPositiveLong(property)) + { + throw new ConfigurationException(this.getClass().getSimpleName() + + ": '" + property + + "' must be a Positive Long value."); + } + } + catch (Exception e) + { + Throwable last = e; + + // Find the first cause + if (e instanceof ConversionException) + { + Throwable t = e.getCause(); + while (t != null) + { + last = t; + t = last.getCause(); + } + } + + throw new ConfigurationException(this.getClass().getSimpleName() + + ": unable to configure invalid " + + property + ":" + + _configuration.getString(property), + last); + } + } + + protected boolean containsLong(String property) + { + try + { + _configuration.getLong(property); + return true; + } + catch (NoSuchElementException e) + { + return false; + } + } + + protected boolean containsPositiveLong(String property) + { + try + { + long value = _configuration.getLong(property); + return value > 0; + } + catch (NoSuchElementException e) + { + return false; + } + + } + + protected boolean containsInt(String property) + { + try + { + _configuration.getInt(property); + return true; + } + catch (NoSuchElementException e) + { + return false; + } + } + + protected boolean containsBoolean(String property) + { + try + { + _configuration.getBoolean(property); + return true; + } + catch (NoSuchElementException e) + { + return false; + } + } + + /** + * Given another configuration merge the configuration into our own config + * + * The new values being merged in will take precedence over existing values. + * + * In the simplistic case this means something like: + * + * So if we have configuration set + * name = 'fooo' + * + * And the new configuration contains a name then that will be reset. + * name = 'new' + * + * However this plugin will simply contain other plugins so the merge will + * be called until we end up at a base plugin that understand how to merge + * items. i.e Alerting values. Where the provided configuration will take + * precedence. + * + * @param configuration the config to merge in to our own. + */ + public void addConfiguration(ConfigurationPlugin configuration) + { + // If given configuration is null then there is nothing to process. + if (configuration == null) + { + return; + } + + // Merge all the sub configuration items + for (Map.Entry<String, ConfigurationPlugin> newPlugins : configuration._pluginConfiguration.entrySet()) + { + String key = newPlugins.getKey(); + ConfigurationPlugin config = newPlugins.getValue(); + + if (_pluginConfiguration.containsKey(key)) + { + //Merge the configuration if we already have this type of config + _pluginConfiguration.get(key).mergeConfiguration(config); + } + else + { + //otherwise just add it to our config. + _pluginConfiguration.put(key, config); + } + } + + //Merge the configuration itself + String key = configuration.getClass().getName(); + if (_pluginConfiguration.containsKey(key)) + { + //Merge the configuration if we already have this type of config + _pluginConfiguration.get(key).mergeConfiguration(configuration); + } + else + { + //If we are adding a configuration of our own type then merge + if (configuration.getClass() == this.getClass()) + { + mergeConfiguration(configuration); + } + else + { + // just store this in case someone else needs it. + _pluginConfiguration.put(key, configuration); + } + + } + + } + + protected void mergeConfiguration(ConfigurationPlugin configuration) + { + _configuration = configuration.getConfig(); + } + + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append("\n").append(getClass().getSimpleName()); + sb.append("=[ (").append(formatToString()).append(")"); + + for(Map.Entry<String,ConfigurationPlugin> item : _pluginConfiguration.entrySet()) + { + sb.append("\n").append(item.getValue()); + } + + sb.append("]\n"); + + return sb.toString(); + } + + public String formatToString() + { + return super.toString(); + } + +} + + diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/plugins/ConfigurationPluginFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/plugins/ConfigurationPluginFactory.java new file mode 100644 index 0000000000..02560b296e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/plugins/ConfigurationPluginFactory.java @@ -0,0 +1,38 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration.plugins; + +import java.util.List; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; + +public interface ConfigurationPluginFactory +{ + /** + * The Parent paths of the configuration that this plugin supports. + * + * For example, {@code queue} elements have a parent path of {@code virtualhosts.virtualhost}. + */ + public List<String> getParentPaths(); + + public ConfigurationPlugin newInstance(String path, Configuration config) throws ConfigurationException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/plugins/SlowConsumerDetectionConfiguration.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/plugins/SlowConsumerDetectionConfiguration.java new file mode 100644 index 0000000000..7a2632d923 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/plugins/SlowConsumerDetectionConfiguration.java @@ -0,0 +1,89 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration.plugins; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.ConversionException; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class SlowConsumerDetectionConfiguration extends ConfigurationPlugin +{ + public static class SlowConsumerDetectionConfigurationFactory implements ConfigurationPluginFactory + { + public ConfigurationPlugin newInstance(String path, Configuration config) throws ConfigurationException + { + SlowConsumerDetectionConfiguration slowConsumerConfig = new SlowConsumerDetectionConfiguration(); + slowConsumerConfig.setConfiguration(path, config); + return slowConsumerConfig; + } + + public List<String> getParentPaths() + { + return Arrays.asList("virtualhosts.virtualhost.slow-consumer-detection"); + } + } + + //Set Default time unit to seconds + TimeUnit _timeUnit = TimeUnit.SECONDS; + + public String[] getElementsProcessed() + { + return new String[]{"delay", + "timeunit"}; + } + + public long getDelay() + { + return getLongValue("delay", 10); + } + + public TimeUnit getTimeUnit() + { + return _timeUnit; + } + + @Override + public void validateConfiguration() throws ConfigurationException + { + validatePositiveLong("delay"); + + String timeUnit = getStringValue("timeunit"); + + if (timeUnit != null) + { + try + { + _timeUnit = TimeUnit.valueOf(timeUnit.toUpperCase()); + } + catch (IllegalArgumentException iae) + { + throw new ConfigurationException("Unable to configure Slow Consumer Detection invalid TimeUnit:" + timeUnit); + } + } + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/plugins/SlowConsumerDetectionPolicyConfiguration.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/plugins/SlowConsumerDetectionPolicyConfiguration.java new file mode 100644 index 0000000000..ca8dec851a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/plugins/SlowConsumerDetectionPolicyConfiguration.java @@ -0,0 +1,76 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration.plugins; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory; + +import java.util.Arrays; +import java.util.List; + +public class SlowConsumerDetectionPolicyConfiguration extends ConfigurationPlugin +{ + public static class SlowConsumerDetectionPolicyConfigurationFactory implements ConfigurationPluginFactory + { + public ConfigurationPlugin newInstance(String path, Configuration config) throws ConfigurationException + { + SlowConsumerDetectionPolicyConfiguration slowConsumerConfig = new SlowConsumerDetectionPolicyConfiguration(); + slowConsumerConfig.setConfiguration(path, config); + return slowConsumerConfig; + } + + public List<String> getParentPaths() + { + return Arrays.asList( + "virtualhosts.virtualhost.queues.slow-consumer-detection.policy", + "virtualhosts.virtualhost.queues.queue.slow-consumer-detection.policy", + "virtualhosts.virtualhost.topics.slow-consumer-detection.policy", + "virtualhosts.virtualhost.topics.topic.slow-consumer-detection.policy"); + } + } + + public String[] getElementsProcessed() + { + return new String[]{"name"}; + } + + public String getPolicyName() + { + return getStringValue("name"); + } + + @Override + public void validateConfiguration() throws ConfigurationException + { + if (getPolicyName() == null) + { + throw new ConfigurationException("No Slow consumer policy defined."); + } + } + + @Override + public String formatToString() + { + return "Policy:"+getPolicyName(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/plugins/SlowConsumerDetectionQueueConfiguration.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/plugins/SlowConsumerDetectionQueueConfiguration.java new file mode 100644 index 0000000000..0638ea362f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/configuration/plugins/SlowConsumerDetectionQueueConfiguration.java @@ -0,0 +1,153 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.configuration.plugins; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory; +import org.apache.qpid.server.plugins.PluginManager; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.slowconsumerdetection.policies.SlowConsumerPolicyPlugin; +import org.apache.qpid.slowconsumerdetection.policies.SlowConsumerPolicyPluginFactory; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class SlowConsumerDetectionQueueConfiguration extends ConfigurationPlugin +{ + private SlowConsumerPolicyPlugin _policyPlugin; + + public static class SlowConsumerDetectionQueueConfigurationFactory implements ConfigurationPluginFactory + { + public ConfigurationPlugin newInstance(String path, Configuration config) throws ConfigurationException + { + SlowConsumerDetectionQueueConfiguration slowConsumerConfig = new SlowConsumerDetectionQueueConfiguration(); + slowConsumerConfig.setConfiguration(path, config); + return slowConsumerConfig; + } + + public List<String> getParentPaths() + { + return Arrays.asList( + "virtualhosts.virtualhost.queues.slow-consumer-detection", + "virtualhosts.virtualhost.queues.queue.slow-consumer-detection", + "virtualhosts.virtualhost.topics.slow-consumer-detection", + "virtualhosts.virtualhost.topics.topic.slow-consumer-detection"); + } + } + + public String[] getElementsProcessed() + { + return new String[]{"messageAge", + "depth", + "messageCount"}; + } + + public long getMessageAge() + { + return getLongValue("messageAge"); + } + + public long getDepth() + { + return getLongValue("depth"); + } + + public long getMessageCount() + { + return getLongValue("messageCount"); + } + + public SlowConsumerPolicyPlugin getPolicy() + { + return _policyPlugin; + } + + @Override + public void validateConfiguration() throws ConfigurationException + { + if (!containsPositiveLong("messageAge") && + !containsPositiveLong("depth") && + !containsPositiveLong("messageCount")) + { + throw new ConfigurationException("At least one configuration property" + + "('messageAge','depth' or 'messageCount') must be specified."); + } + + SlowConsumerDetectionPolicyConfiguration policyConfig = getConfiguration(SlowConsumerDetectionPolicyConfiguration.class.getName()); + + PluginManager pluginManager = ApplicationRegistry.getInstance().getPluginManager(); + Map<String, SlowConsumerPolicyPluginFactory> factories = pluginManager.getSlowConsumerPlugins(); + + if (policyConfig == null) + { + throw new ConfigurationException("No Slow Consumer Policy specified. Known Policies:" + factories.keySet()); + } + + if (_logger.isDebugEnabled()) + { + Iterator<?> keys = policyConfig.getConfig().getKeys(); + + while (keys.hasNext()) + { + String key = (String) keys.next(); + + _logger.debug("Policy Keys:" + key); + } + + } + + SlowConsumerPolicyPluginFactory<SlowConsumerPolicyPlugin> pluginFactory = factories.get(policyConfig.getPolicyName().toLowerCase()); + + if (pluginFactory == null) + { + throw new ConfigurationException("Unknown Slow Consumer Policy specified:" + policyConfig.getPolicyName() + " Known Policies:" + factories.keySet()); + } + + _policyPlugin = pluginFactory.newInstance(policyConfig); + + // Debug the creation of this Config + _logger.debug(this); + } + + public String formatToString() + { + StringBuilder sb = new StringBuilder(); + if (getMessageAge() > 0) + { + sb.append("Age:").append(getMessageAge()).append(":"); + } + if (getDepth() > 0) + { + sb.append("Depth:").append(getDepth()).append(":"); + } + if (getMessageCount() > 0) + { + sb.append("Count:").append(getMessageCount()).append(":"); + } + + sb.append("Policy[").append(getPolicy()).append("]"); + return sb.toString(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/connection/ConnectionRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/connection/ConnectionRegistry.java new file mode 100644 index 0000000000..c06305ee4e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/connection/ConnectionRegistry.java @@ -0,0 +1,81 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.connection; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.common.Closeable; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.AMQConnectionModel; + +public class ConnectionRegistry implements IConnectionRegistry, Closeable +{ + private List<AMQConnectionModel> _registry = new CopyOnWriteArrayList<AMQConnectionModel>(); + + private Logger _logger = Logger.getLogger(ConnectionRegistry.class); + + public void initialise() + { + // None required + } + + /** Close all of the currently open connections. */ + public void close() + { + while (!_registry.isEmpty()) + { + AMQConnectionModel connection = _registry.get(0); + closeConnection(connection, AMQConstant.INTERNAL_ERROR, "Broker is shutting down"); + } + } + + public void closeConnection(AMQConnectionModel connection, AMQConstant cause, String message) + { + try + { + connection.close(cause, message); + } + catch (AMQException e) + { + _logger.warn("Error closing connection:" + e.getMessage()); + } + } + + public void registerConnection(AMQConnectionModel connnection) + { + _registry.add(connnection); + } + + public void deregisterConnection(AMQConnectionModel connnection) + { + _registry.remove(connnection); + } + + @Override + public List<AMQConnectionModel> getConnections() + { + return new ArrayList<AMQConnectionModel>(_registry); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/connection/IConnectionRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/connection/IConnectionRegistry.java new file mode 100644 index 0000000000..b4f5bffa57 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/connection/IConnectionRegistry.java @@ -0,0 +1,42 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.connection; + +import java.util.List; + +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.AMQConnectionModel; + +public interface IConnectionRegistry +{ + public void initialise(); + + public void close() throws AMQException; + + public void closeConnection(AMQConnectionModel connection, AMQConstant cause, String message); + + public List<AMQConnectionModel> getConnections(); + + public void registerConnection(AMQConnectionModel connnection); + + public void deregisterConnection(AMQConnectionModel connnection); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java new file mode 100644 index 0000000000..d0231e4d80 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchange.java @@ -0,0 +1,403 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.configuration.ConfigStore; +import org.apache.qpid.server.configuration.ConfiguredObject; +import org.apache.qpid.server.configuration.ExchangeConfigType; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.ExchangeMessages; +import org.apache.qpid.server.logging.subjects.ExchangeLogSubject; +import org.apache.qpid.server.management.Managable; +import org.apache.qpid.server.management.ManagedObject; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import javax.management.JMException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +public abstract class AbstractExchange implements Exchange, Managable +{ + + + private AMQShortString _name; + private final AtomicBoolean _closed = new AtomicBoolean(); + + private Exchange _alternateExchange; + + protected boolean _durable; + protected int _ticket; + + private VirtualHost _virtualHost; + + private final List<Exchange.Task> _closeTaskList = new CopyOnWriteArrayList<Exchange.Task>(); + + + protected AbstractExchangeMBean _exchangeMbean; + + /** + * Whether the exchange is automatically deleted once all queues have detached from it + */ + protected boolean _autoDelete; + + //The logSubject for ths exchange + private LogSubject _logSubject; + private Map<ExchangeReferrer,Object> _referrers = new ConcurrentHashMap<ExchangeReferrer,Object>(); + + private final CopyOnWriteArrayList<Binding> _bindings = new CopyOnWriteArrayList<Binding>(); + private final ExchangeType<? extends Exchange> _type; + private UUID _id; + private final AtomicInteger _bindingCountHigh = new AtomicInteger(); + private final AtomicLong _receivedMessageCount = new AtomicLong(); + private final AtomicLong _receivedMessageSize = new AtomicLong(); + private final AtomicLong _routedMessageCount = new AtomicLong(); + private final AtomicLong _routedMessageSize = new AtomicLong(); + + private final CopyOnWriteArrayList<Exchange.BindingListener> _listeners = new CopyOnWriteArrayList<Exchange.BindingListener>(); + + //TODO : persist creation time + private long _createTime = System.currentTimeMillis(); + + public AbstractExchange(final ExchangeType<? extends Exchange> type) + { + _type = type; + } + + public AMQShortString getNameShortString() + { + return _name; + } + + public final AMQShortString getTypeShortString() + { + return _type.getName(); + } + + /** + * Concrete exchanges must implement this method in order to create the managed representation. This is + * called during initialisation (template method pattern). + * @return the MBean + */ + protected abstract AbstractExchangeMBean createMBean() throws JMException; + + public void initialise(VirtualHost host, AMQShortString name, boolean durable, int ticket, boolean autoDelete) + throws AMQException + { + _virtualHost = host; + _name = name; + _durable = durable; + _autoDelete = autoDelete; + _ticket = ticket; + + // TODO - fix + _id = getConfigStore().createId(); + + getConfigStore().addConfiguredObject(this); + try + { + _exchangeMbean = createMBean(); + _exchangeMbean.register(); + } + catch (JMException e) + { + getLogger().error(e); + } + _logSubject = new ExchangeLogSubject(this, this.getVirtualHost()); + + // Log Exchange creation + CurrentActor.get().message(ExchangeMessages.CREATED(String.valueOf(getTypeShortString()), String.valueOf(name), durable)); + } + + public ConfigStore getConfigStore() + { + return getVirtualHost().getConfigStore(); + } + + public abstract Logger getLogger(); + + public boolean isDurable() + { + return _durable; + } + + public boolean isAutoDelete() + { + return _autoDelete; + } + + public int getTicket() + { + return _ticket; + } + + public void close() throws AMQException + { + + if(_closed.compareAndSet(false,true)) + { + if (_exchangeMbean != null) + { + _exchangeMbean.unregister(); + } + getConfigStore().removeConfiguredObject(this); + if(_alternateExchange != null) + { + _alternateExchange.removeReference(this); + } + + CurrentActor.get().message(_logSubject, ExchangeMessages.DELETED()); + + for(Task task : _closeTaskList) + { + task.onClose(this); + } + _closeTaskList.clear(); + } + } + + public String toString() + { + return getClass().getSimpleName() + "[" + getNameShortString() +"]"; + } + + public ManagedObject getManagedObject() + { + return _exchangeMbean; + } + + public VirtualHost getVirtualHost() + { + return _virtualHost; + } + + public QueueRegistry getQueueRegistry() + { + return getVirtualHost().getQueueRegistry(); + } + + public boolean isBound(String bindingKey, Map<String,Object> arguments, AMQQueue queue) + { + return isBound(new AMQShortString(bindingKey), queue); + } + + + public boolean isBound(String bindingKey, AMQQueue queue) + { + return isBound(new AMQShortString(bindingKey), queue); + } + + public boolean isBound(String bindingKey) + { + return isBound(new AMQShortString(bindingKey)); + } + + public Exchange getAlternateExchange() + { + return _alternateExchange; + } + + public void setAlternateExchange(Exchange exchange) + { + if(_alternateExchange != null) + { + _alternateExchange.removeReference(this); + } + if(exchange != null) + { + exchange.addReference(this); + } + _alternateExchange = exchange; + + } + + public void removeReference(ExchangeReferrer exchange) + { + _referrers.remove(exchange); + } + + public void addReference(ExchangeReferrer exchange) + { + _referrers.put(exchange, Boolean.TRUE); + } + + public boolean hasReferrers() + { + return !_referrers.isEmpty(); + } + + public void addCloseTask(final Task task) + { + _closeTaskList.add(task); + } + + public void removeCloseTask(final Task task) + { + _closeTaskList.remove(task); + } + + public final void addBinding(final Binding binding) + { + _bindings.add(binding); + int bindingCountSize = _bindings.size(); + int maxBindingsSize; + while((maxBindingsSize = _bindingCountHigh.get()) < bindingCountSize) + { + _bindingCountHigh.compareAndSet(maxBindingsSize, bindingCountSize); + } + for(BindingListener listener : _listeners) + { + listener.bindingAdded(this, binding); + } + onBind(binding); + } + + public long getBindingCountHigh() + { + return _bindingCountHigh.get(); + } + + public final void removeBinding(final Binding binding) + { + onUnbind(binding); + for(BindingListener listener : _listeners) + { + listener.bindingRemoved(this, binding); + } + _bindings.remove(binding); + } + + public final Collection<Binding> getBindings() + { + return Collections.unmodifiableList(_bindings); + } + + protected abstract void onBind(final Binding binding); + + protected abstract void onUnbind(final Binding binding); + + + public String getName() + { + return _name.toString(); + } + + public ExchangeType getType() + { + return _type; + } + + public Map<String, Object> getArguments() + { + // TODO - Fix + return Collections.EMPTY_MAP; + } + + public UUID getId() + { + return _id; + } + + public ExchangeConfigType getConfigType() + { + return ExchangeConfigType.getInstance(); + } + + public ConfiguredObject getParent() + { + return _virtualHost; + } + + public long getBindingCount() + { + return getBindings().size(); + } + + + + public final ArrayList<? extends BaseQueue> route(final InboundMessage message) + { + _receivedMessageCount.incrementAndGet(); + _receivedMessageSize.addAndGet(message.getSize()); + final ArrayList<? extends BaseQueue> queues = doRoute(message); + if(queues != null && !queues.isEmpty()) + { + _routedMessageCount.incrementAndGet(); + _routedMessageSize.addAndGet(message.getSize()); + } + return queues; + } + + protected abstract ArrayList<? extends BaseQueue> doRoute(final InboundMessage message); + + public long getMsgReceives() + { + return _receivedMessageCount.get(); + } + + public long getMsgRoutes() + { + return _routedMessageCount.get(); + } + + public long getByteReceives() + { + return _receivedMessageSize.get(); + } + + public long getByteRoutes() + { + return _routedMessageSize.get(); + } + + public long getCreateTime() + { + return _createTime; + } + + public void addBindingListener(final BindingListener listener) + { + _listeners.add(listener); + } + + public void removeBindingListener(final BindingListener listener) + { + _listeners.remove(listener); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchangeMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchangeMBean.java new file mode 100644 index 0000000000..0f1b709475 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/AbstractExchangeMBean.java @@ -0,0 +1,182 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange; + +import java.util.Collections; +import java.util.Map; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.server.management.AMQManagedObject; +import org.apache.qpid.server.management.ManagedObject; +import org.apache.qpid.server.management.ManagedObjectRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.binding.BindingFactory; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.ManagementActor; +import org.apache.qpid.management.common.mbeans.ManagedExchange; +import org.apache.qpid.framing.AMQShortString; + +import javax.management.openmbean.*; +import javax.management.MBeanException; +import javax.management.NotCompliantMBeanException; +import javax.management.ObjectName; +import javax.management.MalformedObjectNameException; +import javax.management.JMException; + +/** + * Abstract MBean class. This has some of the methods implemented from + * management intrerface for exchanges. Any implementaion of an + * Exchange MBean should extend this class. + */ +public abstract class AbstractExchangeMBean<T extends AbstractExchange> extends AMQManagedObject implements ManagedExchange +{ + // open mbean data types for representing exchange bindings + protected OpenType[] _bindingItemTypes; + protected CompositeType _bindingDataType; + protected TabularType _bindinglistDataType; + + + private T _exchange; + + public AbstractExchangeMBean(final T abstractExchange) throws NotCompliantMBeanException + { + super(ManagedExchange.class, ManagedExchange.TYPE); + _exchange = abstractExchange; + } + + protected void init() throws OpenDataException + { + _bindingItemTypes = new OpenType[2]; + _bindingItemTypes[0] = SimpleType.STRING; + _bindingItemTypes[1] = new ArrayType(1, SimpleType.STRING); + _bindingDataType = new CompositeType("Exchange Binding", "Binding key and Queue names", + COMPOSITE_ITEM_NAMES.toArray(new String[COMPOSITE_ITEM_NAMES.size()]), + COMPOSITE_ITEM_DESCRIPTIONS.toArray(new String[COMPOSITE_ITEM_DESCRIPTIONS.size()]), _bindingItemTypes); + _bindinglistDataType = new TabularType("Exchange Bindings", "Exchange Bindings for " + getName(), + _bindingDataType, TABULAR_UNIQUE_INDEX.toArray(new String[TABULAR_UNIQUE_INDEX.size()])); + } + + public ManagedObject getParentObject() + { + return _exchange.getVirtualHost().getManagedObject(); + } + + public T getExchange() + { + return _exchange; + } + + + public String getObjectInstanceName() + { + return ObjectName.quote(_exchange.getName()); + } + + public String getName() + { + return _exchange.getName(); + } + + public String getExchangeType() + { + return _exchange.getTypeShortString().toString(); + } + + public Integer getTicketNo() + { + return _exchange._ticket; + } + + public boolean isDurable() + { + return _exchange._durable; + } + + public boolean isAutoDelete() + { + return _exchange._autoDelete; + } + + // Added exchangetype in the object name lets maangement apps to do any customization required + public ObjectName getObjectName() throws MalformedObjectNameException + { + String objNameString = super.getObjectName().toString(); + objNameString = objNameString + ",ExchangeType=" + getExchangeType(); + return new ObjectName(objNameString); + } + + protected ManagedObjectRegistry getManagedObjectRegistry() + { + return ApplicationRegistry.getInstance().getManagedObjectRegistry(); + } + + public void createNewBinding(String queueName, String binding) throws JMException + { + VirtualHost vhost = getExchange().getVirtualHost(); + AMQQueue queue = vhost.getQueueRegistry().getQueue(new AMQShortString(queueName)); + if (queue == null) + { + throw new JMException("Queue \"" + queueName + "\" is not registered with the virtualhost."); + } + + CurrentActor.set(new ManagementActor(_logActor.getRootMessageLogger())); + try + { + vhost.getBindingFactory().addBinding(binding,queue,getExchange(),null); + } + catch (AMQException ex) + { + JMException jme = new JMException(ex.toString()); + throw new MBeanException(jme, "Error creating new binding " + binding); + } + CurrentActor.remove(); + } + + /** + * Removes a queue binding from the exchange. + * + * @see BindingFactory#removeBinding(String, AMQQueue, Exchange, Map) + */ + public void removeBinding(String queueName, String binding) throws JMException + { + VirtualHost vhost = getExchange().getVirtualHost(); + AMQQueue queue = vhost.getQueueRegistry().getQueue(new AMQShortString(queueName)); + if (queue == null) + { + throw new JMException("Queue \"" + queueName + "\" is not registered with the virtualhost."); + } + + CurrentActor.set(new ManagementActor(_logActor.getRootMessageLogger())); + try + { + vhost.getBindingFactory().removeBinding(binding, queue, _exchange, Collections.<String, Object>emptyMap()); + } + catch (AMQException ex) + { + JMException jme = new JMException(ex.toString()); + throw new MBeanException(jme, "Error removing binding " + binding); + } + CurrentActor.remove(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java new file mode 100644 index 0000000000..7837a9bc38 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeFactory.java @@ -0,0 +1,146 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.AMQUnknownExchangeType; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.qmf.ManagementExchange; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class DefaultExchangeFactory implements ExchangeFactory +{ + private static final Logger _logger = Logger.getLogger(DefaultExchangeFactory.class); + + private Map<AMQShortString, ExchangeType<? extends Exchange>> _exchangeClassMap = new HashMap<AMQShortString, ExchangeType<? extends Exchange>>(); + private final VirtualHost _host; + + public DefaultExchangeFactory(VirtualHost host) + { + _host = host; + registerExchangeType(DirectExchange.TYPE); + registerExchangeType(TopicExchange.TYPE); + registerExchangeType(HeadersExchange.TYPE); + registerExchangeType(FanoutExchange.TYPE); + registerExchangeType(ManagementExchange.TYPE); + } + + public void registerExchangeType(ExchangeType<? extends Exchange> type) + { + _exchangeClassMap.put(type.getName(), type); + } + + public Collection<ExchangeType<? extends Exchange>> getRegisteredTypes() + { + return _exchangeClassMap.values(); + } + + public Collection<ExchangeType<? extends Exchange>> getPublicCreatableTypes() + { + Collection<ExchangeType<? extends Exchange>> publicTypes = + new ArrayList<ExchangeType<? extends Exchange>>(); + publicTypes.addAll(_exchangeClassMap.values()); + + //Remove the ManagementExchange type if present, as these + //are private and cannot be created by external means + publicTypes.remove(ManagementExchange.TYPE); + + return publicTypes; + } + + + + public Exchange createExchange(String exchange, String type, boolean durable, boolean autoDelete) + throws AMQException + { + return createExchange(new AMQShortString(exchange), new AMQShortString(type), durable, autoDelete, 0); + } + + public Exchange createExchange(AMQShortString exchange, AMQShortString type, boolean durable, boolean autoDelete, + int ticket) + throws AMQException + { + // Check access + if (!_host.getSecurityManager().authoriseCreateExchange(autoDelete, durable, exchange, null, null, null, type)) + { + String description = "Permission denied: exchange-name '" + exchange.asString() + "'"; + throw new AMQSecurityException(description); + } + + ExchangeType<? extends Exchange> exchType = _exchangeClassMap.get(type); + if (exchType == null) + { + throw new AMQUnknownExchangeType("Unknown exchange type: " + type,null); + } + + Exchange e = exchType.newInstance(_host, exchange, durable, ticket, autoDelete); + return e; + } + + public void initialise(VirtualHostConfiguration hostConfig) + { + + if (hostConfig == null) + { + return; + } + + for(Object className : hostConfig.getCustomExchanges()) + { + try + { + ExchangeType<?> exchangeType = ApplicationRegistry.getInstance().getPluginManager().getExchanges().get(String.valueOf(className)); + if (exchangeType == null) + { + _logger.error("No such custom exchange class found: \""+String.valueOf(className)+"\""); + return; + } + Class<? extends ExchangeType> exchangeTypeClass = exchangeType.getClass(); + ExchangeType<? extends ExchangeType> type = exchangeTypeClass.newInstance(); + registerExchangeType(type); + } + catch (ClassCastException classCastEx) + { + _logger.error("No custom exchange class: \""+String.valueOf(className)+"\" cannot be registered as it does not extend class \""+ExchangeType.class+"\""); + } + catch (IllegalAccessException e) + { + _logger.error("Cannot create custom exchange class: \""+String.valueOf(className)+"\"",e); + } + catch (InstantiationException e) + { + _logger.error("Cannot create custom exchange class: \""+String.valueOf(className)+"\"",e); + } + } + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java new file mode 100644 index 0000000000..0e7459498a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DefaultExchangeRegistry.java @@ -0,0 +1,164 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange; + +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.queue.IncomingMessage; +import org.apache.qpid.server.store.DurableConfigurationStore; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class DefaultExchangeRegistry implements ExchangeRegistry +{ + private static final Logger _log = Logger.getLogger(DefaultExchangeRegistry.class); + + /** + * Maps from exchange name to exchange instance + */ + private ConcurrentMap<AMQShortString, Exchange> _exchangeMap = new ConcurrentHashMap<AMQShortString, Exchange>(); + private ConcurrentMap<String, Exchange> _exchangeMapStr = new ConcurrentHashMap<String, Exchange>(); + + private Exchange _defaultExchange; + private VirtualHost _host; + + public DefaultExchangeRegistry(VirtualHost host) + { + //create 'standard' exchanges: + _host = host; + + } + + public void initialise() throws AMQException + { + new ExchangeInitialiser().initialise(_host.getExchangeFactory(), this, getDurableConfigurationStore()); + } + + + + public DurableConfigurationStore getDurableConfigurationStore() + { + return _host.getDurableConfigurationStore(); + } + + public void registerExchange(Exchange exchange) throws AMQException + { + _exchangeMap.put(exchange.getNameShortString(), exchange); + _exchangeMapStr.put(exchange.getNameShortString().toString(), exchange); + } + + public void setDefaultExchange(Exchange exchange) + { + _defaultExchange = exchange; + } + + public Exchange getDefaultExchange() + { + return _defaultExchange; + } + + public Collection<AMQShortString> getExchangeNames() + { + return _exchangeMap.keySet(); + } + + public void unregisterExchange(AMQShortString name, boolean inUse) throws AMQException + { + // Check access + if (!_host.getSecurityManager().authoriseDelete(_exchangeMap.get(name))) + { + throw new AMQSecurityException(); + } + + // TODO: check inUse argument + + Exchange e = _exchangeMap.remove(name); + _exchangeMapStr.remove(name.toString()); + if (e != null) + { + if (e.isDurable()) + { + getDurableConfigurationStore().removeExchange(e); + } + e.close(); + } + else + { + throw new AMQException("Unknown exchange " + name); + } + } + + public void unregisterExchange(String name, boolean inUse) throws AMQException + { + unregisterExchange(new AMQShortString(name), inUse); + } + + public Exchange getExchange(AMQShortString name) + { + if ((name == null) || name.length() == 0) + { + return getDefaultExchange(); + } + else + { + return _exchangeMap.get(name); + } + + } + + public Exchange getExchange(String name) + { + if ((name == null) || name.length() == 0) + { + return getDefaultExchange(); + } + else + { + return _exchangeMapStr.get(name); + } + } + + + /** + * Routes content through exchanges, delivering it to 1 or more queues. + * @param payload + * @throws AMQException if something goes wrong delivering data + */ + public void routeContent(IncomingMessage payload) throws AMQException + { + final AMQShortString exchange = payload.getExchange(); + final Exchange exch = getExchange(exchange); + // there is a small window of opportunity for the exchange to be deleted in between + // the BasicPublish being received (where the exchange is validated) and the final + // content body being received (which triggers this method) + // TODO: check where the exchange is validated + if (exch == null) + { + throw new AMQException("Exchange '" + exchange + "' does not exist"); + } + payload.enqueue(exch.route(payload)); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DirectExchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DirectExchange.java new file mode 100644 index 0000000000..cb0d8ecf8f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DirectExchange.java @@ -0,0 +1,216 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import javax.management.JMException; +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; + +public class DirectExchange extends AbstractExchange +{ + private static final Logger _logger = Logger.getLogger(DirectExchange.class); + + private final ConcurrentHashMap<String, CopyOnWriteArraySet<Binding>> _bindingsByKey = + new ConcurrentHashMap<String, CopyOnWriteArraySet<Binding>>(); + + public static final ExchangeType<DirectExchange> TYPE = new ExchangeType<DirectExchange>() + { + + public AMQShortString getName() + { + return ExchangeDefaults.DIRECT_EXCHANGE_CLASS; + } + + public Class<DirectExchange> getExchangeClass() + { + return DirectExchange.class; + } + + public DirectExchange newInstance(VirtualHost host, + AMQShortString name, + boolean durable, + int ticket, + boolean autoDelete) throws AMQException + { + DirectExchange exch = new DirectExchange(); + exch.initialise(host,name,durable,ticket,autoDelete); + return exch; + } + + public AMQShortString getDefaultExchangeName() + { + return ExchangeDefaults.DIRECT_EXCHANGE_NAME; + } + }; + + + public DirectExchange() + { + super(TYPE); + } + + protected AbstractExchangeMBean createMBean() throws JMException + { + return new DirectExchangeMBean(this); + } + + public Logger getLogger() + { + return _logger; + } + + + public ArrayList<? extends BaseQueue> doRoute(InboundMessage payload) + { + + final String routingKey = payload.getRoutingKey(); + + CopyOnWriteArraySet<Binding> bindings = _bindingsByKey.get(routingKey == null ? "" : routingKey); + + if(bindings != null) + { + final ArrayList<BaseQueue> queues = new ArrayList<BaseQueue>(bindings.size()); + + for(Binding binding : bindings) + { + queues.add(binding.getQueue()); + binding.incrementMatches(); + } + + if (_logger.isDebugEnabled()) + { + _logger.debug("Publishing message to queue " + queues); + } + + return queues; + } + else + { + return new ArrayList<BaseQueue>(0); + } + + + + } + + public boolean isBound(AMQShortString routingKey, FieldTable arguments, AMQQueue queue) + { + return isBound(routingKey,queue); + } + + public boolean isBound(AMQShortString routingKey, AMQQueue queue) + { + String bindingKey = (routingKey == null) ? "" : routingKey.toString(); + CopyOnWriteArraySet<Binding> bindings = _bindingsByKey.get(bindingKey); + if(bindings != null) + { + for(Binding binding : bindings) + { + if(binding.getQueue().equals(queue)) + { + return true; + } + } + } + return false; + + } + + public boolean isBound(AMQShortString routingKey) + { + String bindingKey = (routingKey == null) ? "" : routingKey.toString(); + CopyOnWriteArraySet<Binding> bindings = _bindingsByKey.get(bindingKey); + return bindings != null && !bindings.isEmpty(); + } + + public boolean isBound(AMQQueue queue) + { + + for (CopyOnWriteArraySet<Binding> bindings : _bindingsByKey.values()) + { + for(Binding binding : bindings) + { + if(binding.getQueue().equals(queue)) + { + return true; + } + } + } + return false; + } + + public boolean hasBindings() + { + return !getBindings().isEmpty(); + } + + protected void onBind(final Binding binding) + { + String bindingKey = binding.getBindingKey(); + AMQQueue queue = binding.getQueue(); + AMQShortString routingKey = AMQShortString.valueOf(bindingKey); + + assert queue != null; + assert routingKey != null; + + CopyOnWriteArraySet<Binding> bindings = _bindingsByKey.get(bindingKey); + + if(bindings == null) + { + bindings = new CopyOnWriteArraySet<Binding>(); + CopyOnWriteArraySet<Binding> newBindings; + if((newBindings = _bindingsByKey.putIfAbsent(bindingKey, bindings)) != null) + { + bindings = newBindings; + } + } + + bindings.add(binding); + + } + + protected void onUnbind(final Binding binding) + { + assert binding != null; + + CopyOnWriteArraySet<Binding> bindings = _bindingsByKey.get(binding.getBindingKey()); + if(bindings != null) + { + bindings.remove(binding); + } + + } + + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DirectExchangeMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DirectExchangeMBean.java new file mode 100644 index 0000000000..94fc44d9c7 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/DirectExchangeMBean.java @@ -0,0 +1,81 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange; + +import org.apache.qpid.management.common.mbeans.annotations.MBeanDescription; +import org.apache.qpid.management.common.mbeans.annotations.MBeanConstructor; +import org.apache.qpid.server.binding.Binding; + +import javax.management.JMException; +import javax.management.openmbean.*; +import java.util.List; +import java.util.Map; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * MBean class implementing the management interfaces. + */ +@MBeanDescription("Management Bean for Direct Exchange") +final class DirectExchangeMBean extends AbstractExchangeMBean<DirectExchange> +{ + @MBeanConstructor("Creates an MBean for AMQ direct exchange") + public DirectExchangeMBean(final DirectExchange exchange) throws JMException + { + super(exchange); + + init(); + } + + public TabularData bindings() throws OpenDataException + { + TabularDataSupport bindingList = new TabularDataSupport(_bindinglistDataType); + + Map<String, List<String>> bindingMap = new HashMap<String, List<String>>(); + + for (Binding binding : getExchange().getBindings()) + { + String key = binding.getBindingKey(); + List<String> queueList = bindingMap.get(key); + if(queueList == null) + { + queueList = new ArrayList<String>(); + bindingMap.put(key, queueList); + } + queueList.add(binding.getQueue().getNameShortString().toString()); + + } + + for(Map.Entry<String, List<String>> entry : bindingMap.entrySet()) + { + Object[] bindingItemValues = {entry.getKey(), entry.getValue().toArray(new String[0])}; + CompositeData bindingData = new CompositeDataSupport(_bindingDataType, + COMPOSITE_ITEM_NAMES.toArray(new String[COMPOSITE_ITEM_NAMES.size()]), + bindingItemValues); + bindingList.put(bindingData); + } + + return bindingList; + } + + + +}// End of MBean class diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/Exchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/Exchange.java new file mode 100644 index 0000000000..356a7f89b9 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/Exchange.java @@ -0,0 +1,145 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQInternalException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; + +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.binding.BindingFactory; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.configuration.ExchangeConfig; + +import javax.management.JMException; +import java.util.ArrayList; +import java.util.List; +import java.util.Collection; +import java.util.concurrent.CopyOnWriteArrayList; + +public interface Exchange extends ExchangeReferrer, ExchangeConfig +{ + + public interface BindingListener + { + void bindingAdded(Exchange exchange, Binding binding); + void bindingRemoved(Exchange exchange, Binding binding); + } + + AMQShortString getNameShortString(); + + AMQShortString getTypeShortString(); + + void initialise(VirtualHost host, AMQShortString name, boolean durable, int ticket, boolean autoDelete) + throws AMQException, JMException; + + boolean isDurable(); + + /** + * @return true if the exchange will be deleted after all queues have been detached + */ + boolean isAutoDelete(); + + int getTicket(); + + void close() throws AMQException; + + + ArrayList<? extends BaseQueue> route(InboundMessage message); + + + /** + * Determines whether a message would be isBound to a particular queue using a specific routing key and arguments + * @param routingKey + * @param arguments + * @param queue + * @return + * @throws AMQException + */ + boolean isBound(AMQShortString routingKey, FieldTable arguments, AMQQueue queue); + + /** + * Determines whether a message would be isBound to a particular queue using a specific routing key + * @param routingKey + * @param queue + * @return + * @throws AMQException + */ + boolean isBound(AMQShortString routingKey, AMQQueue queue); + + /** + * Determines whether a message is routing to any queue using a specific _routing key + * @param routingKey + * @return + * @throws AMQException + */ + boolean isBound(AMQShortString routingKey); + + boolean isBound(AMQQueue queue); + + /** + * Returns true if this exchange has at least one binding associated with it. + * @return + * @throws AMQException + */ + boolean hasBindings(); + + + boolean isBound(String bindingKey, AMQQueue queue); + + boolean isBound(String bindingKey); + + void addCloseTask(Task task); + + void removeCloseTask(Task task); + + + Exchange getAlternateExchange(); + + void setAlternateExchange(Exchange exchange); + + void removeReference(ExchangeReferrer exchange); + + void addReference(ExchangeReferrer exchange); + + boolean hasReferrers(); + + void addBinding(Binding binding); + + void removeBinding(Binding binding); + + Collection<Binding> getBindings(); + + public void addBindingListener(BindingListener listener); + + public void removeBindingListener(BindingListener listener); + + + public static interface Task + { + public void onClose(Exchange exchange) throws AMQSecurityException, AMQInternalException; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java new file mode 100644 index 0000000000..92795487e4 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeFactory.java @@ -0,0 +1,43 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange; + +import java.util.Collection; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; + + +public interface ExchangeFactory +{ + Exchange createExchange(AMQShortString exchange, AMQShortString type, boolean durable, boolean autoDelete, + int ticket) + throws AMQException; + + void initialise(VirtualHostConfiguration hostConfig); + + Collection<ExchangeType<? extends Exchange>> getRegisteredTypes(); + + Collection<ExchangeType<? extends Exchange>> getPublicCreatableTypes(); + + Exchange createExchange(String exchange, String type, boolean durable, boolean autoDelete) throws AMQException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.java new file mode 100644 index 0000000000..c77f114428 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInUseException.java @@ -0,0 +1,45 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange; + +import org.apache.qpid.AMQException; + +/** + * ExchangeInUseRegistry indicates that an exchange cannot be unregistered because it is currently being used. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represents failure to unregister exchange that is in use. + * </table> + * + * @todo Not an AMQP exception as no status code. + * + * @todo This exception is not used. However, it is part of the ExchangeRegistry interface, and looks like code is + * going to need to be added to throw/deal with this. Alternatively ExchangeResitries may be able to handle the + * issue internally. + */ +public class ExchangeInUseException extends AMQException +{ + public ExchangeInUseException(String exchangeName) + { + super("Exchange " + exchangeName + " is currently in use"); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInitialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInitialiser.java new file mode 100644 index 0000000000..4dfcce7bbe --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeInitialiser.java @@ -0,0 +1,54 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange; + +import org.apache.qpid.AMQException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.store.DurableConfigurationStore; + +public class ExchangeInitialiser +{ + public void initialise(ExchangeFactory factory, ExchangeRegistry registry, DurableConfigurationStore store) throws AMQException{ + for (ExchangeType<? extends Exchange> type : factory.getRegisteredTypes()) + { + define (registry, factory, type.getDefaultExchangeName(), type.getName(), store); + } + + define(registry, factory, ExchangeDefaults.DEFAULT_EXCHANGE_NAME, ExchangeDefaults.DIRECT_EXCHANGE_CLASS, store); + registry.setDefaultExchange(registry.getExchange(ExchangeDefaults.DEFAULT_EXCHANGE_NAME)); + } + + private void define(ExchangeRegistry r, ExchangeFactory f, + AMQShortString name, AMQShortString type, DurableConfigurationStore store) throws AMQException + { + if(r.getExchange(name)== null) + { + Exchange exchange = f.createExchange(name, type, true, false, 0); + r.registerExchange(exchange); + + if(exchange.isDurable()) + { + store.createExchange(exchange); + } + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeReferrer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeReferrer.java new file mode 100755 index 0000000000..e41d63d97d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeReferrer.java @@ -0,0 +1,26 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange; + +public interface ExchangeReferrer +{ +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java new file mode 100644 index 0000000000..e34ef29d9b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeRegistry.java @@ -0,0 +1,55 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; + +import java.util.Collection; + + +public interface ExchangeRegistry extends MessageRouter +{ + void registerExchange(Exchange exchange) throws AMQException; + + /** + * Unregister an exchange + * @param name name of the exchange to delete + * @param inUse if true, do NOT delete the exchange if it is in use (has queues bound to it) + * @throws ExchangeInUseException when the exchange cannot be deleted because it is in use + * @throws AMQException + */ + void unregisterExchange(AMQShortString name, boolean inUse) throws ExchangeInUseException, AMQException; + + Exchange getExchange(AMQShortString name); + + void setDefaultExchange(Exchange exchange); + + Exchange getDefaultExchange(); + + Collection<AMQShortString> getExchangeNames(); + + void initialise() throws AMQException; + + Exchange getExchange(String exchangeName); + + void unregisterExchange(String exchange, boolean ifUnused) throws ExchangeInUseException, AMQException;; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeType.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeType.java new file mode 100644 index 0000000000..0b55caa2f1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/ExchangeType.java @@ -0,0 +1,35 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.virtualhost.VirtualHost; + + +public interface ExchangeType<T extends Exchange> +{ + public AMQShortString getName(); + public Class<T> getExchangeClass(); + public T newInstance(VirtualHost host, AMQShortString name, + boolean durable, int ticket, boolean autoDelete) throws AMQException; + public AMQShortString getDefaultExchangeName(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/FanoutExchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/FanoutExchange.java new file mode 100644 index 0000000000..bd75f7bc51 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/FanoutExchange.java @@ -0,0 +1,206 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import javax.management.JMException; +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; + +public class FanoutExchange extends AbstractExchange +{ + private static final Logger _logger = Logger.getLogger(FanoutExchange.class); + + private static final Integer ONE = Integer.valueOf(1); + + /** + * Maps from queue name to queue instances + */ + private final ConcurrentHashMap<AMQQueue,Integer> _queues = new ConcurrentHashMap<AMQQueue,Integer>(); + + protected AbstractExchangeMBean createMBean() throws JMException + { + return new FanoutExchangeMBean(this); + } + + public Logger getLogger() + { + return _logger; + } + + public static final ExchangeType<FanoutExchange> TYPE = new ExchangeType<FanoutExchange>() + { + + public AMQShortString getName() + { + return ExchangeDefaults.FANOUT_EXCHANGE_CLASS; + } + + public Class<FanoutExchange> getExchangeClass() + { + return FanoutExchange.class; + } + + public FanoutExchange newInstance(VirtualHost host, + AMQShortString name, + boolean durable, + int ticket, + boolean autoDelete) throws AMQException + { + FanoutExchange exch = new FanoutExchange(); + exch.initialise(host, name, durable, ticket, autoDelete); + return exch; + } + + public AMQShortString getDefaultExchangeName() + { + return ExchangeDefaults.FANOUT_EXCHANGE_NAME; + } + }; + + public FanoutExchange() + { + super(TYPE); + } + + public ArrayList<BaseQueue> doRoute(InboundMessage payload) + { + + + if (_logger.isDebugEnabled()) + { + _logger.debug("Publishing message to queue " + _queues); + } + + for(Binding b : getBindings()) + { + b.incrementMatches(); + } + + return new ArrayList<BaseQueue>(_queues.keySet()); + + } + + public boolean isBound(AMQShortString routingKey, FieldTable arguments, AMQQueue queue) + { + return isBound(routingKey, queue); + } + + public boolean isBound(AMQShortString routingKey, AMQQueue queue) + { + return _queues.contains(queue); + } + + public boolean isBound(AMQShortString routingKey) + { + + return (_queues != null) && !_queues.isEmpty(); + } + + public boolean isBound(AMQQueue queue) + { + + return _queues.contains(queue); + } + + public boolean hasBindings() + { + return !_queues.isEmpty(); + } + + protected void onBind(final Binding binding) + { + AMQQueue queue = binding.getQueue(); + assert queue != null; + + Integer oldVal; + + if((oldVal = _queues.putIfAbsent(queue, ONE)) != null) + { + Integer newVal = oldVal+1; + while(!_queues.replace(queue, oldVal, newVal)) + { + oldVal = _queues.get(queue); + if(oldVal == null) + { + oldVal = _queues.putIfAbsent(queue, ONE); + if(oldVal == null) + { + break; + } + } + newVal = oldVal + 1; + } + } + + if (_logger.isDebugEnabled()) + { + _logger.debug("Binding queue " + queue + + " with routing key " + new AMQShortString(binding.getBindingKey()) + " to exchange " + this); + } + } + + protected void onUnbind(final Binding binding) + { + AMQQueue queue = binding.getQueue(); + Integer oldValue = _queues.get(queue); + + boolean done = false; + + while(!(done || oldValue == null)) + { + while(!(done || oldValue == null) && oldValue.intValue() == 1) + { + if(!_queues.remove(queue, oldValue)) + { + oldValue = _queues.get(queue); + } + else + { + done = true; + } + } + while(!(done || oldValue == null) && oldValue.intValue() != 1) + { + Integer newValue = oldValue - 1; + if(!_queues.replace(queue, oldValue, newValue)) + { + oldValue = _queues.get(queue); + } + else + { + done = true; + } + } + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/FanoutExchangeMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/FanoutExchangeMBean.java new file mode 100644 index 0000000000..2c85b7f787 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/FanoutExchangeMBean.java @@ -0,0 +1,70 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange; + +import org.apache.qpid.management.common.mbeans.annotations.MBeanDescription; +import org.apache.qpid.management.common.mbeans.annotations.MBeanConstructor; +import org.apache.qpid.server.binding.Binding; + +import javax.management.JMException; +import javax.management.openmbean.*; +import java.util.ArrayList; + +/** + * MBean class implementing the management interfaces. + */ +@MBeanDescription("Management Bean for Fanout Exchange") +final class FanoutExchangeMBean extends AbstractExchangeMBean<FanoutExchange> +{ + private static final String BINDING_KEY_SUBSTITUTE = "*"; + + @MBeanConstructor("Creates an MBean for AMQ fanout exchange") + public FanoutExchangeMBean(final FanoutExchange exchange) throws JMException + { + super(exchange); + init(); + } + + public TabularData bindings() throws OpenDataException + { + + TabularDataSupport bindingList = new TabularDataSupport(_bindinglistDataType); + + + ArrayList<String> queueNames = new ArrayList<String>(); + + for (Binding binding : getExchange().getBindings()) + { + String queueName = binding.getQueue().getNameShortString().toString(); + queueNames.add(queueName); + } + + Object[] bindingItemValues = {BINDING_KEY_SUBSTITUTE, queueNames.toArray(new String[0])}; + CompositeData bindingData = new CompositeDataSupport(_bindingDataType, + COMPOSITE_ITEM_NAMES.toArray(new String[COMPOSITE_ITEM_NAMES.size()]), + bindingItemValues); + bindingList.put(bindingData); + + return bindingList; + } + + +} // End of MBean class diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java new file mode 100644 index 0000000000..f58a6513a9 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersBinding.java @@ -0,0 +1,260 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.log4j.Logger; +import org.apache.qpid.framing.AMQTypedValue; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.message.AMQMessageHeader; + +/** + * Defines binding and matching based on a set of headers. + */ +class HeadersBinding +{ + private static final Logger _logger = Logger.getLogger(HeadersBinding.class); + + private final FieldTable _mappings; + private final Binding _binding; + private final Set<String> required = new HashSet<String>(); + private final Map<String,Object> matches = new HashMap<String,Object>(); + private boolean matchAny; + + /** + * Creates a header binding for a set of mappings. Those mappings whose value is + * null or the empty string are assumed only to be required headers, with + * no constraint on the value. Those with a non-null value are assumed to + * define a required match of value. + * + * @param binding the binding to create a header binding using + */ + public HeadersBinding(Binding binding) + { + _binding = binding; + if(_binding !=null) + { + _mappings = FieldTable.convertToFieldTable(_binding.getArguments()); + initMappings(); + } + else + { + _mappings = null; + } + } + + private void initMappings() + { + _mappings.processOverElements(new FieldTable.FieldTableElementProcessor() + { + + public boolean processElement(String propertyName, AMQTypedValue value) + { + if (isSpecial(propertyName)) + { + processSpecial(propertyName, value.getValue()); + } + else if (value.getValue() == null || value.getValue().equals("")) + { + required.add(propertyName); + } + else + { + matches.put(propertyName,value.getValue()); + } + + return true; + } + + public Object getResult() + { + return null; + } + }); + } + + protected FieldTable getMappings() + { + return _mappings; + } + + public Binding getBinding() + { + return _binding; + } + + /** + * Checks whether the supplied headers match the requirements of this binding + * @param headers the headers to check + * @return true if the headers define any required keys and match any required + * values + */ + public boolean matches(AMQMessageHeader headers) + { + if(headers == null) + { + return required.isEmpty() && matches.isEmpty(); + } + else + { + return matchAny ? or(headers) : and(headers); + } + } + + private boolean and(AMQMessageHeader headers) + { + if(headers.containsHeaders(required)) + { + for(Map.Entry<String, Object> e : matches.entrySet()) + { + if(!e.getValue().equals(headers.getHeader(e.getKey()))) + { + return false; + } + } + return true; + } + else + { + return false; + } + } + + + private boolean or(final AMQMessageHeader headers) + { + if(required.isEmpty()) + { + return matches.isEmpty() || passesMatchesOr(headers); + } + else + { + if(!passesRequiredOr(headers)) + { + return !matches.isEmpty() && passesMatchesOr(headers); + } + else + { + return true; + } + + } + } + + private boolean passesMatchesOr(AMQMessageHeader headers) + { + for(Map.Entry<String,Object> entry : matches.entrySet()) + { + if(headers.containsHeader(entry.getKey()) + && ((entry.getValue() == null && headers.getHeader(entry.getKey()) == null) + || (entry.getValue().equals(headers.getHeader(entry.getKey()))))) + { + return true; + } + } + return false; + } + + private boolean passesRequiredOr(AMQMessageHeader headers) + { + for(String name : required) + { + if(headers.containsHeader(name)) + { + return true; + } + } + return false; + } + + private void processSpecial(String key, Object value) + { + if("X-match".equalsIgnoreCase(key)) + { + matchAny = isAny(value); + } + else + { + _logger.warn("Ignoring special header: " + key); + } + } + + private boolean isAny(Object value) + { + if(value instanceof String) + { + if("any".equalsIgnoreCase((String) value)) return true; + if("all".equalsIgnoreCase((String) value)) return false; + } + _logger.warn("Ignoring unrecognised match type: " + value); + return false;//default to all + } + + static boolean isSpecial(Object key) + { + return key instanceof String && isSpecial((String) key); + } + + static boolean isSpecial(String key) + { + return key.startsWith("X-") || key.startsWith("x-"); + } + + @Override + public boolean equals(final Object o) + { + if (this == o) + { + return true; + } + + if (o == null) + { + return false; + } + + if (!(o instanceof HeadersBinding)) + { + return false; + } + + final HeadersBinding hb = (HeadersBinding) o; + + if(_binding == null) + { + if(hb.getBinding() != null) + { + return false; + } + } + else if (!_binding.equals(hb.getBinding())) + { + return false; + } + + return true; + } +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java new file mode 100644 index 0000000000..f9cbfeb78b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchange.java @@ -0,0 +1,260 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.binding.Binding; + +import javax.management.JMException; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * An exchange that binds queues based on a set of required headers and header values + * and routes messages to these queues by matching the headers of the message against + * those with which the queues were bound. + * <p/> + * <pre> + * The Headers Exchange + * + * Routes messages according to the value/presence of fields in the message header table. + * (Basic and JMS content has a content header field called "headers" that is a table of + * message header fields). + * + * class = "headers" + * routing key is not used + * + * Has the following binding arguments: + * + * the X-match field - if "all", does an AND match (used for GRM), if "any", does an OR match. + * other fields prefixed with "X-" are ignored (and generate a console warning message). + * a field with no value or empty value indicates a match on presence only. + * a field with a value indicates match on field presence and specific value. + * + * Standard instances: + * + * amq.match - pub/sub on field content/value + * </pre> + */ +public class HeadersExchange extends AbstractExchange +{ + + private static final Logger _logger = Logger.getLogger(HeadersExchange.class); + + private final ConcurrentHashMap<String, CopyOnWriteArraySet<Binding>> _bindingsByKey = + new ConcurrentHashMap<String, CopyOnWriteArraySet<Binding>>(); + + private final CopyOnWriteArrayList<HeadersBinding> _bindingHeaderMatchers = + new CopyOnWriteArrayList<HeadersBinding>(); + + + public static final ExchangeType<HeadersExchange> TYPE = new ExchangeType<HeadersExchange>() + { + + public AMQShortString getName() + { + return ExchangeDefaults.HEADERS_EXCHANGE_CLASS; + } + + public Class<HeadersExchange> getExchangeClass() + { + return HeadersExchange.class; + } + + public HeadersExchange newInstance(VirtualHost host, AMQShortString name, boolean durable, int ticket, + boolean autoDelete) throws AMQException + { + HeadersExchange exch = new HeadersExchange(); + + exch.initialise(host, name, durable, ticket, autoDelete); + return exch; + } + + public AMQShortString getDefaultExchangeName() + { + + return ExchangeDefaults.HEADERS_EXCHANGE_NAME; + } + }; + + public HeadersExchange() + { + super(TYPE); + } + + + + public ArrayList<BaseQueue> doRoute(InboundMessage payload) + { + AMQMessageHeader header = payload.getMessageHeader(); + if (_logger.isDebugEnabled()) + { + _logger.debug("Exchange " + getNameShortString() + ": routing message with headers " + header); + } + + LinkedHashSet<BaseQueue> queues = new LinkedHashSet<BaseQueue>(); + + for (HeadersBinding hb : _bindingHeaderMatchers) + { + if (hb.matches(header)) + { + Binding b = hb.getBinding(); + + b.incrementMatches(); + + if (_logger.isDebugEnabled()) + { + _logger.debug("Exchange " + getNameShortString() + ": delivering message with headers " + + header + " to " + b.getQueue().getNameShortString()); + } + queues.add(b.getQueue()); + } + } + + return new ArrayList<BaseQueue>(queues); + } + + public boolean isBound(AMQShortString routingKey, FieldTable arguments, AMQQueue queue) + { + //fixme isBound here should take the arguements in to consideration. + return isBound(routingKey, queue); + } + + public boolean isBound(AMQShortString routingKey, AMQQueue queue) + { + String bindingKey = (routingKey == null) ? "" : routingKey.toString(); + CopyOnWriteArraySet<Binding> bindings = _bindingsByKey.get(bindingKey); + + if(bindings != null) + { + for(Binding binding : bindings) + { + if(binding.getQueue().equals(queue)) + { + return true; + } + } + } + + return false; + } + + public boolean isBound(AMQShortString routingKey) + { + String bindingKey = (routingKey == null) ? "" : routingKey.toString(); + CopyOnWriteArraySet<Binding> bindings = _bindingsByKey.get(bindingKey); + return bindings != null && !bindings.isEmpty(); + } + + public boolean isBound(AMQQueue queue) + { + for (CopyOnWriteArraySet<Binding> bindings : _bindingsByKey.values()) + { + for(Binding binding : bindings) + { + if(binding.getQueue().equals(queue)) + { + return true; + } + } + } + + return false; + } + + public boolean hasBindings() + { + return !getBindings().isEmpty(); + } + + protected AbstractExchangeMBean createMBean() throws JMException + { + return new HeadersExchangeMBean(this); + } + + public Logger getLogger() + { + return _logger; + } + + protected void onBind(final Binding binding) + { + String bindingKey = binding.getBindingKey(); + AMQQueue queue = binding.getQueue(); + AMQShortString routingKey = AMQShortString.valueOf(bindingKey); + Map<String,Object> args = binding.getArguments(); + + assert queue != null; + assert routingKey != null; + + CopyOnWriteArraySet<Binding> bindings = _bindingsByKey.get(bindingKey); + + if(bindings == null) + { + bindings = new CopyOnWriteArraySet<Binding>(); + CopyOnWriteArraySet<Binding> newBindings; + if((newBindings = _bindingsByKey.putIfAbsent(bindingKey, bindings)) != null) + { + bindings = newBindings; + } + } + + if(_logger.isDebugEnabled()) + { + _logger.debug("Exchange " + getNameShortString() + ": Binding " + queue.getNameShortString() + + " with binding key '" +bindingKey + "' and args: " + args); + } + + _bindingHeaderMatchers.add(new HeadersBinding(binding)); + bindings.add(binding); + + } + + protected void onUnbind(final Binding binding) + { + assert binding != null; + + CopyOnWriteArraySet<Binding> bindings = _bindingsByKey.get(binding.getBindingKey()); + if(bindings != null) + { + bindings.remove(binding); + } + + if(_logger.isDebugEnabled()) + { + _logger.debug("Removing Binding: " + _bindingHeaderMatchers.remove(new HeadersBinding(binding))); + } + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchangeMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchangeMBean.java new file mode 100644 index 0000000000..66c9b5b552 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/HeadersExchangeMBean.java @@ -0,0 +1,98 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange; + +import org.apache.qpid.management.common.mbeans.annotations.MBeanDescription; +import org.apache.qpid.management.common.mbeans.annotations.MBeanConstructor; +import org.apache.qpid.server.binding.Binding; + +import javax.management.JMException; +import javax.management.openmbean.*; +import java.util.List; +import java.util.ArrayList; +import java.util.Map; + +/** + * HeadersExchangeMBean class implements the management interface for the + * Header Exchanges. + */ +@MBeanDescription("Management Bean for Headers Exchange") +final class HeadersExchangeMBean extends AbstractExchangeMBean<HeadersExchange> +{ + + @MBeanConstructor("Creates an MBean for AMQ Headers exchange") + public HeadersExchangeMBean(final HeadersExchange headersExchange) throws JMException + { + super(headersExchange); + init(); + } + + /** + * initialises the OpenType objects. + */ + protected void init() throws OpenDataException + { + + _bindingItemTypes = new OpenType[3]; + _bindingItemTypes[0] = SimpleType.INTEGER; + _bindingItemTypes[1] = SimpleType.STRING; + _bindingItemTypes[2] = new ArrayType(1, SimpleType.STRING); + _bindingDataType = new CompositeType("Exchange Binding", "Queue name and header bindings", + HEADERS_COMPOSITE_ITEM_NAMES.toArray(new String[HEADERS_COMPOSITE_ITEM_NAMES.size()]), + HEADERS_COMPOSITE_ITEM_DESC.toArray(new String[HEADERS_COMPOSITE_ITEM_DESC.size()]), _bindingItemTypes); + _bindinglistDataType = new TabularType("Exchange Bindings", "List of exchange bindings for " + getName(), + _bindingDataType, HEADERS_TABULAR_UNIQUE_INDEX.toArray(new String[HEADERS_TABULAR_UNIQUE_INDEX.size()])); + } + + public TabularData bindings() throws OpenDataException + { + TabularDataSupport bindingList = new TabularDataSupport(_bindinglistDataType); + int count = 1; + for (Binding binding : getExchange().getBindings()) + { + + String queueName = binding.getQueue().getNameShortString().toString(); + + + Map<String,Object> headerMappings = binding.getArguments(); + final List<String> mappingList = new ArrayList<String>(); + + if(headerMappings != null) + { + for(Map.Entry<String,Object> entry : headerMappings.entrySet()) + { + + mappingList.add(entry.getKey() + "=" + entry.getValue()); + } + } + + + Object[] bindingItemValues = {count++, queueName, mappingList.toArray(new String[0])}; + CompositeData bindingData = new CompositeDataSupport(_bindingDataType, + HEADERS_COMPOSITE_ITEM_NAMES.toArray(new String[HEADERS_COMPOSITE_ITEM_NAMES.size()]), bindingItemValues); + bindingList.put(bindingData); + } + + return bindingList; + } + + +} // End of MBean class diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/MessageRouter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/MessageRouter.java new file mode 100644 index 0000000000..025a8014aa --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/MessageRouter.java @@ -0,0 +1,40 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.IncomingMessage; + +/** + * Separated out from the ExchangeRegistry interface to allow components + * that use only this part to have a dependency with a reduced footprint. + * + */ +public interface MessageRouter +{ + /** + * Routes content through exchanges, delivering it to 1 or more queues. + * @param message the message to be routed + * + * @throws org.apache.qpid.AMQException if something goes wrong delivering data + */ + void routeContent(IncomingMessage message) throws AMQException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/TopicExchange.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/TopicExchange.java new file mode 100644 index 0000000000..e523eb24fb --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/TopicExchange.java @@ -0,0 +1,369 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQInvalidArgumentException; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.exchange.topic.*; +import org.apache.qpid.server.filter.JMSSelectorFilter; +import org.apache.qpid.server.message.InboundMessage; + +import javax.management.JMException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.lang.ref.WeakReference; + +public class TopicExchange extends AbstractExchange +{ + + public static final ExchangeType<TopicExchange> TYPE = new ExchangeType<TopicExchange>() + { + + public AMQShortString getName() + { + return ExchangeDefaults.TOPIC_EXCHANGE_CLASS; + } + + public Class<TopicExchange> getExchangeClass() + { + return TopicExchange.class; + } + + public TopicExchange newInstance(VirtualHost host, + AMQShortString name, + boolean durable, + int ticket, + boolean autoDelete) throws AMQException + { + TopicExchange exch = new TopicExchange(); + exch.initialise(host, name, durable, ticket, autoDelete); + return exch; + } + + public AMQShortString getDefaultExchangeName() + { + return ExchangeDefaults.TOPIC_EXCHANGE_NAME; + } + }; + + + private static final Logger _logger = Logger.getLogger(TopicExchange.class); + + + + private final TopicParser _parser = new TopicParser(); + + private final Map<AMQShortString, TopicExchangeResult> _topicExchangeResults = + new ConcurrentHashMap<AMQShortString, TopicExchangeResult>(); + + private final Map<Binding, FieldTable> _bindings = new HashMap<Binding, FieldTable>(); + + private final Map<String, WeakReference<JMSSelectorFilter>> _selectorCache = new WeakHashMap<String, WeakReference<JMSSelectorFilter>>(); + + public TopicExchange() + { + super(TYPE); + } + + protected synchronized void registerQueue(final Binding binding) throws AMQInvalidArgumentException + { + AMQShortString rKey = new AMQShortString(binding.getBindingKey()) ; + AMQQueue queue = binding.getQueue(); + FieldTable args = FieldTable.convertToFieldTable(binding.getArguments()); + + assert queue != null; + assert rKey != null; + + _logger.debug("Registering queue " + queue.getNameShortString() + " with routing key " + rKey); + + + AMQShortString routingKey = TopicNormalizer.normalize(rKey); + + if(_bindings.containsKey(binding)) + { + FieldTable oldArgs = _bindings.get(binding); + TopicExchangeResult result = _topicExchangeResults.get(routingKey); + + if(argumentsContainSelector(args)) + { + if(argumentsContainSelector(oldArgs)) + { + result.replaceQueueFilter(queue,createSelectorFilter(oldArgs), createSelectorFilter(args)); + } + else + { + result.addFilteredQueue(queue,createSelectorFilter(args)); + result.removeUnfilteredQueue(queue); + } + } + else + { + if(argumentsContainSelector(oldArgs)) + { + result.addUnfilteredQueue(queue); + result.removeFilteredQueue(queue, createSelectorFilter(oldArgs)); + } + else + { + // TODO - fix control flow + return; + } + } + + result.addBinding(binding); + + } + else + { + + TopicExchangeResult result = _topicExchangeResults.get(routingKey); + if(result == null) + { + result = new TopicExchangeResult(); + if(argumentsContainSelector(args)) + { + result.addFilteredQueue(queue, createSelectorFilter(args)); + } + else + { + result.addUnfilteredQueue(queue); + } + _parser.addBinding(routingKey, result); + _topicExchangeResults.put(routingKey,result); + } + else + { + if(argumentsContainSelector(args)) + { + result.addFilteredQueue(queue, createSelectorFilter(args)); + } + else + { + result.addUnfilteredQueue(queue); + } + } + + result.addBinding(binding); + _bindings.put(binding, args); + } + + + } + + private JMSSelectorFilter createSelectorFilter(final FieldTable args) throws AMQInvalidArgumentException + { + + final String selectorString = args.getString(AMQPFilterTypes.JMS_SELECTOR.getValue()); + WeakReference<JMSSelectorFilter> selectorRef = _selectorCache.get(selectorString); + JMSSelectorFilter selector = null; + + if(selectorRef == null || (selector = selectorRef.get())==null) + { + selector = new JMSSelectorFilter(selectorString); + _selectorCache.put(selectorString, new WeakReference<JMSSelectorFilter>(selector)); + } + return selector; + } + + private static boolean argumentsContainSelector(final FieldTable args) + { + return args != null && args.containsKey(AMQPFilterTypes.JMS_SELECTOR.getValue()) && args.getString(AMQPFilterTypes.JMS_SELECTOR.getValue()).trim().length() != 0; + } + + public ArrayList<BaseQueue> doRoute(InboundMessage payload) + { + + final AMQShortString routingKey = payload.getRoutingKey() == null + ? AMQShortString.EMPTY_STRING + : new AMQShortString(payload.getRoutingKey()); + + // The copy here is unfortunate, but not too bad relevant to the amount of + // things created and copied in getMatchedQueues + ArrayList<BaseQueue> queues = new ArrayList<BaseQueue>(); + queues.addAll(getMatchedQueues(payload, routingKey)); + + if(queues == null || queues.isEmpty()) + { + _logger.info("Message routing key: " + payload.getRoutingKey() + " No routes."); + } + + return queues; + + } + + public boolean isBound(AMQShortString routingKey, FieldTable arguments, AMQQueue queue) + { + Binding binding = new Binding(null, routingKey.toString(), queue, this, FieldTable.convertToMap(arguments)); + + if (arguments == null) + { + return _bindings.containsKey(binding); + } + else + { + FieldTable o = _bindings.get(binding); + if (o != null) + { + return o.equals(arguments); + } + else + { + return false; + } + + } + } + + public boolean isBound(AMQShortString routingKey, AMQQueue queue) + { + return isBound(routingKey, null, queue); + } + + public boolean isBound(AMQShortString routingKey) + { + for(Binding b : _bindings.keySet()) + { + if(b.getBindingKey().equals(routingKey.toString())) + { + return true; + } + } + + return false; + } + + public boolean isBound(AMQQueue queue) + { + for(Binding b : _bindings.keySet()) + { + if(b.getQueue().equals(queue)) + { + return true; + } + } + + return false; + } + + public boolean hasBindings() + { + return !_bindings.isEmpty(); + } + + private boolean deregisterQueue(final Binding binding) + { + if(_bindings.containsKey(binding)) + { + FieldTable bindingArgs = _bindings.remove(binding); + AMQShortString bindingKey = TopicNormalizer.normalize(new AMQShortString(binding.getBindingKey())); + TopicExchangeResult result = _topicExchangeResults.get(bindingKey); + + result.removeBinding(binding); + + if(argumentsContainSelector(bindingArgs)) + { + try + { + result.removeFilteredQueue(binding.getQueue(), createSelectorFilter(bindingArgs)); + } + catch (AMQInvalidArgumentException e) + { + return false; + } + } + else + { + result.removeUnfilteredQueue(binding.getQueue()); + } + return true; + } + else + { + return false; + } + } + + protected AbstractExchangeMBean createMBean() throws JMException + { + return new TopicExchangeMBean(this); + } + + public Logger getLogger() + { + return _logger; + } + + private Collection<AMQQueue> getMatchedQueues(InboundMessage message, AMQShortString routingKey) + { + + Collection<TopicMatcherResult> results = _parser.parse(routingKey); + if(results.isEmpty()) + { + return Collections.EMPTY_SET; + } + else + { + Collection<AMQQueue> queues = results.size() == 1 ? null : new HashSet<AMQQueue>(); + for(TopicMatcherResult result : results) + { + TopicExchangeResult res = (TopicExchangeResult)result; + + for(Binding b : res.getBindings()) + { + b.incrementMatches(); + } + + queues = res.processMessage(message, queues); + } + return queues; + } + + + } + + protected void onBind(final Binding binding) + { + try + { + registerQueue(binding); + } + catch (AMQInvalidArgumentException e) + { + throw new RuntimeException(e); + } + } + + protected void onUnbind(final Binding binding) + { + deregisterQueue(binding); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/TopicExchangeMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/TopicExchangeMBean.java new file mode 100644 index 0000000000..620c3ce140 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/TopicExchangeMBean.java @@ -0,0 +1,77 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange; + +import org.apache.qpid.management.common.mbeans.annotations.MBeanDescription; +import org.apache.qpid.management.common.mbeans.annotations.MBeanConstructor; +import org.apache.qpid.server.binding.Binding; + +import javax.management.JMException; +import javax.management.openmbean.*; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.ArrayList; + +/** TopicExchangeMBean class implements the management interface for the Topic exchanges. */ +@MBeanDescription("Management Bean for Topic Exchange") +final class TopicExchangeMBean extends AbstractExchangeMBean<TopicExchange> +{ + private TopicExchange _topicExchange; + + @MBeanConstructor("Creates an MBean for AMQ topic exchange") + public TopicExchangeMBean(final TopicExchange topicExchange) throws JMException + { + super(topicExchange); + init(); + } + + /** returns exchange bindings in tabular form */ + public TabularData bindings() throws OpenDataException + { + TabularDataSupport bindingList = new TabularDataSupport(_bindinglistDataType); + Map<String, List<String>> bindingData = new HashMap<String, List<String>>(); + for (Binding binding : getExchange().getBindings()) + { + String key = binding.getBindingKey(); + List<String> queueNames = bindingData.get(key); + if(queueNames == null) + { + queueNames = new ArrayList<String>(); + bindingData.put(key, queueNames); + } + queueNames.add(binding.getQueue().getNameShortString().toString()); + + } + for(Map.Entry<String, List<String>> entry : bindingData.entrySet()) + { + Object[] bindingItemValues = {entry.getKey(), entry.getValue().toArray(new String[entry.getValue().size()]) }; + CompositeData bindingCompositeData = + new CompositeDataSupport(_bindingDataType, + COMPOSITE_ITEM_NAMES.toArray(new String[COMPOSITE_ITEM_NAMES.size()]), + bindingItemValues); + bindingList.put(bindingCompositeData); + } + + return bindingList; + } + +} // End of MBean class diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderKey.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderKey.java new file mode 100644 index 0000000000..8fdb91cbef --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderKey.java @@ -0,0 +1,40 @@ +package org.apache.qpid.server.exchange.headers; + +import org.apache.qpid.framing.AMQShortString; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ +public class HeaderKey +{ + public static final HeaderKey UNKNOWN = new HeaderKey(new AMQShortString("<< UNKNOWN >>")); + private AMQShortString _key; + + public HeaderKey(final AMQShortString key) + { + _key = key; + } + + public String toString() + { + return _key.toString(); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderKeyDictionary.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderKeyDictionary.java new file mode 100644 index 0000000000..7be99a88c9 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderKeyDictionary.java @@ -0,0 +1,50 @@ +package org.apache.qpid.server.exchange.headers; + +import org.apache.qpid.framing.AMQShortString; + +import java.util.Map; +import java.util.HashMap; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ +public class HeaderKeyDictionary +{ + + private final Map<AMQShortString, HeaderKey> _dictionary = new HashMap<AMQShortString, HeaderKey>(); + + + public HeaderKey get(final AMQShortString key) + { + HeaderKey headerKey = _dictionary.get(key); + return headerKey == null ? HeaderKey.UNKNOWN : headerKey; + } + + public HeaderKey getOrCreate(final AMQShortString key) + { + HeaderKey headerKey = _dictionary.get(key); + if(headerKey == null) + { + headerKey = new HeaderKey(key); + _dictionary.put(key, headerKey); + } + return headerKey; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderMatcherResult.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderMatcherResult.java new file mode 100644 index 0000000000..518064bb29 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeaderMatcherResult.java @@ -0,0 +1,25 @@ +package org.apache.qpid.server.exchange.headers; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ +public class HeaderMatcherResult +{ +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeadersMatcherDFAState.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeadersMatcherDFAState.java new file mode 100644 index 0000000000..9da93d483a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeadersMatcherDFAState.java @@ -0,0 +1,339 @@ +package org.apache.qpid.server.exchange.headers; + +import org.apache.qpid.framing.AMQTypedValue; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.exchange.topic.TopicMatcherDFAState; +import org.apache.qpid.server.exchange.topic.TopicWord; +import org.apache.qpid.server.exchange.topic.TopicMatcherResult; + +import java.util.*; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ +public class HeadersMatcherDFAState +{ + + + private final Collection<HeaderMatcherResult> _results; + private final Map<HeaderKey, Map<AMQTypedValue,HeadersMatcherDFAState>> _nextStateMap; + private final HeaderKeyDictionary _dictionary; + + public HeadersMatcherDFAState(Map<HeaderKey, Map<AMQTypedValue,HeadersMatcherDFAState>> nextStateMap, + Collection<HeaderMatcherResult> results, + HeaderKeyDictionary dictionary) + { + _nextStateMap = nextStateMap; + _results = results; + _dictionary = dictionary; + } + + + public Collection<HeaderMatcherResult> match(final FieldTable table) + { + return match(table.iterator()); + } + + + + public Collection<HeaderMatcherResult> match(Iterator<Map.Entry<AMQShortString,AMQTypedValue>> fieldTableIterator) + { + + if(_nextStateMap.isEmpty()) + { + return _results; + } + + while(fieldTableIterator.hasNext()) + { + + Map.Entry<AMQShortString, AMQTypedValue> fieldTableEntry = fieldTableIterator.next(); + HeaderKey key = _dictionary.get(fieldTableEntry.getKey()); + if(key != HeaderKey.UNKNOWN) + { + Map<AMQTypedValue, HeadersMatcherDFAState> valueToStateMap = _nextStateMap.get(key); + + if(valueToStateMap != null) + { + HeadersMatcherDFAState nextState = valueToStateMap.get(fieldTableEntry.getValue()); + + if(nextState == null) + { + nextState = valueToStateMap.get(null); + } + if(nextState != null && nextState != this) + { + return nextState.match(fieldTableIterator); + } + } + + } + } + + return _results; + + } + + + HeadersMatcherDFAState mergeStateMachines(HeadersMatcherDFAState otherStateMachine) + { + + assert(otherStateMachine._dictionary == _dictionary); + + Map<Set<HeadersMatcherDFAState>, HeadersMatcherDFAState> newStateMap= new HashMap<Set<HeadersMatcherDFAState>, HeadersMatcherDFAState>(); + + Collection<HeaderMatcherResult> results; + + if(_results.isEmpty()) + { + results = otherStateMachine._results; + } + else if(otherStateMachine._results.isEmpty()) + { + results = _results; + } + else + { + results = new HashSet<HeaderMatcherResult>(_results); + results.addAll(otherStateMachine._results); + } + + + final Map<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>> newNextStateMap = new HashMap<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>>(); + + HeadersMatcherDFAState newState = new HeadersMatcherDFAState(newNextStateMap, results, _dictionary); + + + Set<HeadersMatcherDFAState> oldStates = new HashSet<HeadersMatcherDFAState>(); + oldStates.add(this); + oldStates.add(otherStateMachine); + + newStateMap.put(oldStates, newState); + + mergeStateMachines(oldStates, newNextStateMap, newStateMap); + + return newState; + + + } + + private void mergeStateMachines(final Set<HeadersMatcherDFAState> oldStates, + final Map<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>> newNextStateMap, + final Map<Set<HeadersMatcherDFAState>, HeadersMatcherDFAState> newStateMap) + { + Map<HeaderKey, Map<AMQTypedValue, Set<HeadersMatcherDFAState>>> nfaMap = new HashMap<HeaderKey, Map<AMQTypedValue, Set<HeadersMatcherDFAState>>>(); + + Set<HeaderKey> distinctKeys = new HashSet<HeaderKey>(); + + for(HeadersMatcherDFAState state : oldStates) + { + Map<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>> map = state._nextStateMap; + + for(Map.Entry<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>> entry : map.entrySet()) + { + Map<AMQTypedValue, Set<HeadersMatcherDFAState>> valueToStatesMap = nfaMap.get(entry.getKey()); + + if(valueToStatesMap == null) + { + valueToStatesMap = new HashMap<AMQTypedValue, Set<HeadersMatcherDFAState>>(); + nfaMap.put(entry.getKey(), valueToStatesMap); + } + + for(Map.Entry<AMQTypedValue, HeadersMatcherDFAState> valueToStateEntry : entry.getValue().entrySet()) + { + Set<HeadersMatcherDFAState> states = valueToStatesMap.get(valueToStateEntry.getKey()); + if(states == null) + { + states = new HashSet<HeadersMatcherDFAState>(); + valueToStatesMap.put(valueToStateEntry.getKey(),states); + } + states.add(valueToStateEntry.getValue()); + } + + distinctKeys.add(entry.getKey()); + } + } + + Map<HeaderKey, Set<HeadersMatcherDFAState>> anyValueStates = new HashMap<HeaderKey, Set<HeadersMatcherDFAState>>(); + + for(HeaderKey distinctKey : distinctKeys) + { + Map<AMQTypedValue, Set<HeadersMatcherDFAState>> valueToStateMap = nfaMap.get(distinctKey); + if(valueToStateMap != null) + { + Set<HeadersMatcherDFAState> statesForKeyDefault = valueToStateMap.get(null); + if(statesForKeyDefault != null) + { + anyValueStates.put(distinctKey, statesForKeyDefault); + } + } + } + + // add the defaults for "null" to all other specified values of a given header key + + for( Map.Entry<HeaderKey,Map<AMQTypedValue,Set<HeadersMatcherDFAState>>> entry : nfaMap.entrySet()) + { + Map<AMQTypedValue, Set<HeadersMatcherDFAState>> valueToStatesMap = entry.getValue(); + for(Map.Entry<AMQTypedValue, Set<HeadersMatcherDFAState>> valueToStates : valueToStatesMap.entrySet()) + { + if(valueToStates.getKey() != null) + { + + + Set<HeadersMatcherDFAState> defaults = anyValueStates.get(entry.getKey()); + if(defaults != null) + { + valueToStates.getValue().addAll(defaults); + } + } + } + } + + // if a given header key is not mentioned in the map of a machine; then that machine would stay at the same state + // for that key. + for(HeaderKey distinctKey : distinctKeys) + { + Map<AMQTypedValue, Set<HeadersMatcherDFAState>> valueToStatesMap = nfaMap.get(distinctKey); + for(HeadersMatcherDFAState oldState : oldStates) + { + if(!oldState._nextStateMap.containsKey(distinctKey)) + { + for(Set<HeadersMatcherDFAState> endStates : valueToStatesMap.values()) + { + endStates.add(oldState); + } + } + } + } + + + + + for(Map.Entry<HeaderKey,Map<AMQTypedValue,Set<HeadersMatcherDFAState>>> transitionClass : nfaMap.entrySet()) + { + Map<AMQTypedValue, HeadersMatcherDFAState> valueToDFAState = newNextStateMap.get(transitionClass.getKey()); + if(valueToDFAState == null) + { + valueToDFAState = new HashMap<AMQTypedValue, HeadersMatcherDFAState>(); + newNextStateMap.put(transitionClass.getKey(), valueToDFAState); + } + + for(Map.Entry<AMQTypedValue,Set<HeadersMatcherDFAState>> transition : transitionClass.getValue().entrySet()) + { + Set<HeadersMatcherDFAState> destinations = transition.getValue(); + + + HeadersMatcherDFAState nextState = newStateMap.get(destinations); + + if(nextState == null) + { + + if(destinations.size() == 1) + { + nextState = destinations.iterator().next(); + newStateMap.put(destinations, nextState); + } + else + { + Collection<HeaderMatcherResult> results; + + Set<Collection<HeaderMatcherResult>> resultSets = new HashSet<Collection<HeaderMatcherResult>>(); + for(HeadersMatcherDFAState destination : destinations) + { + resultSets.add(destination._results); + } + resultSets.remove(Collections.EMPTY_SET); + if(resultSets.size() == 0) + { + results = Collections.EMPTY_SET; + } + else if(resultSets.size() == 1) + { + results = resultSets.iterator().next(); + } + else + { + results = new HashSet<HeaderMatcherResult>(); + for(Collection<HeaderMatcherResult> oldResult : resultSets) + { + results.addAll(oldResult); + } + } + + final Map<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>> nextStateMap = new HashMap<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>>(); + + nextState = new HeadersMatcherDFAState(nextStateMap, results, _dictionary); + newStateMap.put(destinations, nextState); + + mergeStateMachines( + destinations, + nextStateMap, + newStateMap); + + + } + + + } + valueToDFAState.put(transition.getKey(),nextState); + } + } + + + + final ArrayList<HeaderKey> removeKeyList = new ArrayList<HeaderKey>(); + + for(Map.Entry<HeaderKey,Map<AMQTypedValue,HeadersMatcherDFAState>> entry : _nextStateMap.entrySet()) + { + final ArrayList<AMQTypedValue> removeValueList = new ArrayList<AMQTypedValue>(); + + for(Map.Entry<AMQTypedValue,HeadersMatcherDFAState> valueToDFAState : entry.getValue().entrySet()) + { + if(valueToDFAState.getValue() == this) + { + HeadersMatcherDFAState defaultState = entry.getValue().get(null); + if(defaultState == null || defaultState == this) + { + removeValueList.add(valueToDFAState.getKey()); + } + } + } + + for(AMQTypedValue removeValue : removeValueList) + { + entry.getValue().remove(removeValue); + } + + if(entry.getValue().isEmpty()) + { + removeKeyList.add(entry.getKey()); + } + + } + + for(HeaderKey removeKey : removeKeyList) + { + _nextStateMap.remove(removeKey); + } + + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeadersParser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeadersParser.java new file mode 100644 index 0000000000..0e3a3894fe --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/headers/HeadersParser.java @@ -0,0 +1,441 @@ +package org.apache.qpid.server.exchange.headers; + +import org.apache.qpid.framing.*; + +import java.util.*; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ +public class HeadersParser +{ + + private final HeaderKeyDictionary _dictionary = new HeaderKeyDictionary(); + private static final AMQShortString MATCHING_TYPE_KEY = new AMQShortString("x-match"); + private static final String ANY_MATCHING = "any"; + private static final AMQShortString RESERVED_KEY_PREFIX = new AMQShortString("x-"); + + + HeadersMatcherDFAState createStateMachine(FieldTable bindingArguments, HeaderMatcherResult result) + { + String matchingType = bindingArguments.getString(MATCHING_TYPE_KEY); + boolean matchAny = matchingType.equalsIgnoreCase(ANY_MATCHING); + if(matchAny) + { + return createStateMachineForAnyMatch(bindingArguments, result); + } + else + { + return createStateMachineForAllMatch(bindingArguments, result); + } + + + } + + + private HeadersMatcherDFAState createStateMachineForAnyMatch(final FieldTable bindingArguments, + final HeaderMatcherResult result) + { + + // DFAs for "any" matches have only two states, "not-matched" and "matched"... they start in the former + // and upon meeting any of the criteria they move to the latter + + //noinspection unchecked + final HeadersMatcherDFAState successState = + new HeadersMatcherDFAState(Collections.EMPTY_MAP,Collections.singleton(result),_dictionary); + + Map<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>> nextStateMap = + new HashMap<HeaderKey, Map<AMQTypedValue, HeadersMatcherDFAState>>(); + + Set<AMQShortString> seenKeys = new HashSet<AMQShortString>(); + + Iterator<Map.Entry<AMQShortString, AMQTypedValue>> tableIterator = bindingArguments.iterator(); + + while(tableIterator.hasNext()) + { + final Map.Entry<AMQShortString, AMQTypedValue> entry = tableIterator.next(); + final AMQShortString key = entry.getKey(); + final AMQTypedValue value = entry.getValue(); + + + if(seenKeys.add(key) && !key.startsWith(RESERVED_KEY_PREFIX)) + { + final AMQType type = value.getType(); + + final HeaderKey headerKey = _dictionary.getOrCreate(key); + final Map<AMQTypedValue, HeadersMatcherDFAState> valueMap; + + if(type == AMQType.VOID || + ((type == AMQType.ASCII_STRING || type == AMQType.WIDE_STRING) && ((CharSequence)value.getValue()).length() == 0)) + { + valueMap = Collections.singletonMap(null,successState); + + } + else + { + valueMap = Collections.singletonMap(value,successState); + } + nextStateMap.put(headerKey,valueMap); + + } + + } + + if(seenKeys.size() == 0) + { + return successState; + } + else + { + return new HeadersMatcherDFAState(nextStateMap,Collections.EMPTY_SET,_dictionary); + } + + + } + + + private HeadersMatcherDFAState createStateMachineForAllMatch(final FieldTable bindingArguments, + final HeaderMatcherResult result) + { + // DFAs for "all" matches have a "success" state, a "fail" state, and states for every subset of + // matches which are possible, starting with the empty subset. For example if we have a binding + // { x-match="all" + // a=1 + // b=1 + // c=1 + // d=1 } + // Then we would have the following states + // (1) Seen none of a, b, c, or d + // (2) Seen a=1 ; none of b,c, or d + // (3) Seen b=1 ; none of a,c, or d + // (4) Seen c=1 ; none of a,b, or d + // (5) Seen d=1 ; none of a,b, or c + // (6) Seen a=1,b=1 ; none of c,d + // (7) Seen a=1,c=1 ; none of b,d + // (8) Seen a=1,d=1 ; none of b,c + // (9) Seen b=1,c=1 ; none of a,d + //(10) Seen b=1,d=1 ; none of c,d + //(11) Seen c=1,d=1 ; none of a,b + //(12) Seen a=1,b=1,c=1 ; not d + //(13) Seen a=1,b=1,d=1 ; not c + //(14) Seen a=1,c=1,d=1 ; not b + //(15) Seen b=1,c=1,d=1 ; not a + //(16) success + //(17) fail + // + // All states but (16) can transition to (17); additionally: + // (1) can transition to (2),(3),(4),(5) + // (2) can transition to (6),(7),(8) + // (3) can transition to (6),(9),(10) + // (4) can transition to (7),(9),(11) + // (5) can transition to (8),(10),(11) + // (6) can transition to (12),(13) + // (7) can transition to (12),(14) + // (8) can transition to (13),(14) + // (9) can transition to (12),(15) + //(10) can transition to (13),(15) + //(11) can transition to (14),(15) + //(12)-(15) can transition to (16) + + Set<AMQShortString> seenKeys = new HashSet<AMQShortString>(); + List<KeyValuePair> requiredTerms = new ArrayList<KeyValuePair>(bindingArguments.size()); + + Iterator<Map.Entry<AMQShortString, AMQTypedValue>> tableIterator = bindingArguments.iterator(); + + + + while(tableIterator.hasNext()) + { + final Map.Entry<AMQShortString, AMQTypedValue> entry = tableIterator.next(); + final AMQShortString key = entry.getKey(); + final AMQTypedValue value = entry.getValue(); + + + if(seenKeys.add(key) && !key.startsWith(RESERVED_KEY_PREFIX)) + { + final AMQType type = value.getType(); + + if(type == AMQType.VOID || + ((type == AMQType.ASCII_STRING || type == AMQType.WIDE_STRING) && ((CharSequence)value.getValue()).length() == 0)) + { + requiredTerms.add(new KeyValuePair(_dictionary.getOrCreate(key),null)); + } + else + { + requiredTerms.add(new KeyValuePair(_dictionary.getOrCreate(key),value)); + } + } + + } + + final HeadersMatcherDFAState successState = + new HeadersMatcherDFAState(Collections.EMPTY_MAP,Collections.singleton(result),_dictionary); + + final HeadersMatcherDFAState failState = + new HeadersMatcherDFAState(Collections.EMPTY_MAP,Collections.EMPTY_SET,_dictionary); + + Map<Set<KeyValuePair>, HeadersMatcherDFAState> notSeenTermsToStateMap = + new HashMap<Set<KeyValuePair>, HeadersMatcherDFAState>(); + + notSeenTermsToStateMap.put(Collections.EMPTY_SET, successState); + + + final int numberOfTerms = requiredTerms.size(); + + for(int numMissingTerms = 1; numMissingTerms <= numberOfTerms; numMissingTerms++) + { + int[] pos = new int[numMissingTerms]; + for(int i = 0; i < numMissingTerms; i++) + { + pos[i] = i; + } + + final int maxTermValue = (numberOfTerms - (numMissingTerms - 1)); + + while(pos[0] < maxTermValue) + { + + Set<KeyValuePair> stateSet = new HashSet<KeyValuePair>(); + for(int posIndex = 0; posIndex < pos.length; posIndex++) + { + stateSet.add(requiredTerms.get(pos[posIndex])); + } + + final Map<HeaderKey, Map<AMQTypedValue,HeadersMatcherDFAState>> nextStateMap = + new HashMap<HeaderKey, Map<AMQTypedValue,HeadersMatcherDFAState>>(); + + + for(int posIndex = 0; posIndex < pos.length; posIndex++) + { + KeyValuePair nextTerm = requiredTerms.get(pos[posIndex]); + HashSet<KeyValuePair> nextStateSet = + new HashSet<KeyValuePair>(stateSet); + nextStateSet.remove(nextTerm); + + Map<AMQTypedValue, HeadersMatcherDFAState> valueToStateMap = + new HashMap<AMQTypedValue, HeadersMatcherDFAState>(); + nextStateMap.put(nextTerm._key, valueToStateMap); + + valueToStateMap.put( nextTerm._value,notSeenTermsToStateMap.get(nextStateSet)); + if(nextTerm._value != null) + { + valueToStateMap.put(null, failState); + } + + + } + + + HeadersMatcherDFAState newState = new HeadersMatcherDFAState(nextStateMap, Collections.EMPTY_SET, _dictionary); + + notSeenTermsToStateMap.put(stateSet, newState); + + int i = numMissingTerms; + while(i-- != 0) + { + if(++pos[i] <= numberOfTerms -(numMissingTerms-i)) + { + int k = pos[i]; + for(int j = i+1; j < numMissingTerms; j++) + { + pos[j] = ++k; + } + break; + } + } + } + + + + + } + + + return notSeenTermsToStateMap.get(new HashSet<KeyValuePair>(requiredTerms)); + + + + } + + public static void main(String[] args) throws AMQFrameDecodingException + { + + FieldTable bindingTable = new FieldTable(); + + bindingTable.setString(new AMQShortString("x-match"),"all"); + bindingTable.setInteger("a",1); + bindingTable.setVoid(new AMQShortString("b")); + bindingTable.setString("c",""); + bindingTable.setInteger("d",4); + bindingTable.setInteger("e",1); + + + + FieldTable bindingTable2 = new FieldTable(); + bindingTable2.setString(new AMQShortString("x-match"),"all"); + bindingTable2.setInteger("a",1); + bindingTable2.setVoid(new AMQShortString("b")); + bindingTable2.setString("c",""); + bindingTable2.setInteger("d",4); + bindingTable2.setInteger("e",1); + bindingTable2.setInteger("f",1); + + + FieldTable table = new FieldTable(); + table.setInteger("a",1); + table.setInteger("b",2); + table.setString("c",""); + table.setInteger("d",4); + table.setInteger("e",1); + table.setInteger("f",1); + table.setInteger("h",1); + table.setInteger("i",1); + table.setInteger("j",1); + table.setInteger("k",1); + table.setInteger("l",1); + + org.apache.mina.common.ByteBuffer buffer = org.apache.mina.common.ByteBuffer.allocate( (int) table.getEncodedSize()); + EncodingUtils.writeFieldTableBytes(buffer, table); + buffer.flip(); + + FieldTable table2 = EncodingUtils.readFieldTable(buffer); + + + + FieldTable bindingTable3 = new FieldTable(); + bindingTable3.setString(new AMQShortString("x-match"),"any"); + bindingTable3.setInteger("a",1); + bindingTable3.setInteger("b",3); + + + FieldTable bindingTable4 = new FieldTable(); + bindingTable4.setString(new AMQShortString("x-match"),"any"); + bindingTable4.setVoid(new AMQShortString("a")); + + + FieldTable bindingTable5 = new FieldTable(); + bindingTable5.setString(new AMQShortString("x-match"),"all"); + bindingTable5.setString(new AMQShortString("h"),"hello"); + + for(int i = 0; i < 100; i++) + { + printMatches(new FieldTable[] {bindingTable5} , table2); + } + + + + } + + + + private static void printMatches(final FieldTable[] bindingKeys, final FieldTable routingKey) + { + HeadersMatcherDFAState sm = null; + Map<HeaderMatcherResult, String> resultMap = new HashMap<HeaderMatcherResult, String>(); + + HeadersParser parser = new HeadersParser(); + + for(int i = 0; i < bindingKeys.length; i++) + { + HeaderMatcherResult r = new HeaderMatcherResult(); + resultMap.put(r, bindingKeys[i].toString()); + + + if(i==0) + { + sm = parser.createStateMachine(bindingKeys[i], r); + } + else + { + sm = sm.mergeStateMachines(parser.createStateMachine(bindingKeys[i], r)); + } + } + + Collection<HeaderMatcherResult> results = null; + long beforeTime = System.currentTimeMillis(); + for(int i = 0; i < 1000000; i++) + { + routingKey.size(); + + assert sm != null; + results = sm.match(routingKey); + + } + long elapsed = System.currentTimeMillis() - beforeTime; + System.out.println("1000000 Iterations took: " + elapsed); + Collection<String> resultStrings = new ArrayList<String>(); + + assert results != null; + for(HeaderMatcherResult result : results) + { + resultStrings.add(resultMap.get(result)); + } + + final ArrayList<String> nonMatches = new ArrayList<String>(); + for(FieldTable key : bindingKeys) + { + nonMatches.add(key.toString()); + } + nonMatches.removeAll(resultStrings); + System.out.println("\""+routingKey+"\" matched with " + resultStrings + " DID NOT MATCH with " + nonMatches); + + + } + + + public final static class KeyValuePair + { + public final HeaderKey _key; + public final AMQTypedValue _value; + private final int _hashCode; + + public KeyValuePair(final HeaderKey key, final AMQTypedValue value) + { + _key = key; + _value = value; + int hash = (1 + 31 * _key.hashCode()); + if(_value != null) + { + hash+=_value.hashCode(); + } + _hashCode = hash; + } + + public int hashCode() + { + return _hashCode; + } + + public boolean equals(Object o) + { + assert o != null; + assert o instanceof KeyValuePair; + KeyValuePair other = (KeyValuePair)o; + return (_key == other._key) && (_value == null ? other._value == null : _value.equals(other._value)); + } + + + public String toString() + { + return "{" + _key + " -> " + _value + "}"; + } + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicExchangeResult.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicExchangeResult.java new file mode 100644 index 0000000000..41dc0d749a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicExchangeResult.java @@ -0,0 +1,201 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange.topic; + +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.filter.MessageFilter; +import org.apache.qpid.server.message.InboundMessage; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +public final class TopicExchangeResult implements TopicMatcherResult +{ + private final List<Binding> _bindings = new CopyOnWriteArrayList<Binding>(); + private final Map<AMQQueue, Integer> _unfilteredQueues = new ConcurrentHashMap<AMQQueue, Integer>(); + private final ConcurrentHashMap<AMQQueue, Map<MessageFilter,Integer>> _filteredQueues = new ConcurrentHashMap<AMQQueue, Map<MessageFilter, Integer>>(); + + public void addUnfilteredQueue(AMQQueue queue) + { + Integer instances = _unfilteredQueues.get(queue); + if(instances == null) + { + _unfilteredQueues.put(queue, 1); + } + else + { + _unfilteredQueues.put(queue, instances + 1); + } + } + + public void removeUnfilteredQueue(AMQQueue queue) + { + Integer instances = _unfilteredQueues.get(queue); + if(instances == 1) + { + _unfilteredQueues.remove(queue); + } + else + { + _unfilteredQueues.put(queue,instances - 1); + } + + } + + public Collection<AMQQueue> getUnfilteredQueues() + { + return _unfilteredQueues.keySet(); + } + + public void addBinding(Binding binding) + { + _bindings.add(binding); + } + + public void removeBinding(Binding binding) + { + _bindings.remove(binding); + } + + public List<Binding> getBindings() + { + return new ArrayList<Binding>(_bindings); + } + + public void addFilteredQueue(AMQQueue queue, MessageFilter filter) + { + Map<MessageFilter,Integer> filters = _filteredQueues.get(queue); + if(filters == null) + { + filters = new ConcurrentHashMap<MessageFilter,Integer>(); + _filteredQueues.put(queue, filters); + } + Integer instances = filters.get(filter); + if(instances == null) + { + filters.put(filter,1); + } + else + { + filters.put(filter, instances + 1); + } + + } + + public void removeFilteredQueue(AMQQueue queue, MessageFilter filter) + { + Map<MessageFilter,Integer> filters = _filteredQueues.get(queue); + if(filters != null) + { + Integer instances = filters.get(filter); + if(instances != null) + { + if(instances == 1) + { + filters.remove(filter); + if(filters.isEmpty()) + { + _filteredQueues.remove(queue); + } + } + else + { + filters.put(filter, instances - 1); + } + } + + } + + } + + public void replaceQueueFilter(AMQQueue queue, + MessageFilter oldFilter, + MessageFilter newFilter) + { + Map<MessageFilter,Integer> filters = _filteredQueues.get(queue); + Map<MessageFilter,Integer> newFilters = new ConcurrentHashMap<MessageFilter,Integer>(filters); + Integer oldFilterInstances = filters.get(oldFilter); + if(oldFilterInstances == 1) + { + newFilters.remove(oldFilter); + } + else + { + newFilters.put(oldFilter, oldFilterInstances-1); + } + Integer newFilterInstances = filters.get(newFilter); + if(newFilterInstances == null) + { + newFilters.put(newFilter, 1); + } + else + { + newFilters.put(newFilter, newFilterInstances+1); + } + _filteredQueues.put(queue,newFilters); + } + + public Collection<AMQQueue> processMessage(InboundMessage msg, Collection<AMQQueue> queues) + { + if(queues == null) + { + if(_filteredQueues.isEmpty()) + { + return new ArrayList<AMQQueue>(_unfilteredQueues.keySet()); + } + else + { + queues = new HashSet<AMQQueue>(); + } + } + else if(!(queues instanceof Set)) + { + queues = new HashSet<AMQQueue>(queues); + } + + queues.addAll(_unfilteredQueues.keySet()); + if(!_filteredQueues.isEmpty()) + { + for(Map.Entry<AMQQueue, Map<MessageFilter, Integer>> entry : _filteredQueues.entrySet()) + { + if(!queues.contains(entry.getKey())) + { + for(MessageFilter filter : entry.getValue().keySet()) + { + if(filter.matches(msg)) + { + queues.add(entry.getKey()); + } + } + } + } + } + return queues; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherDFAState.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherDFAState.java new file mode 100644 index 0000000000..36076cf75b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherDFAState.java @@ -0,0 +1,295 @@ +package org.apache.qpid.server.exchange.topic; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.AMQShortStringTokenizer; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ +public class TopicMatcherDFAState +{ + private static final AtomicInteger stateId = new AtomicInteger(); + + private final int _id = stateId.incrementAndGet(); + + private final Collection<TopicMatcherResult> _results; + private final Map<TopicWord, TopicMatcherDFAState> _nextStateMap; + private static final byte TOPIC_DELIMITTER = (byte)'.'; + + + public TopicMatcherDFAState(Map<TopicWord, TopicMatcherDFAState> nextStateMap, + Collection<TopicMatcherResult> results ) + { + _nextStateMap = nextStateMap; + _results = results; + } + + + public TopicMatcherDFAState nextState(TopicWord word) + { + final TopicMatcherDFAState nextState = _nextStateMap.get(word); + return nextState == null ? _nextStateMap.get(TopicWord.ANY_WORD) : nextState; + } + + public Collection<TopicMatcherResult> terminate() + { + return _results; + } + + + public Collection<TopicMatcherResult> parse(TopicWordDictionary dictionary, AMQShortString routingKey) + { + return parse(dictionary, routingKey.tokenize(TOPIC_DELIMITTER)); + } + + private Collection<TopicMatcherResult> parse(final TopicWordDictionary dictionary, + final AMQShortStringTokenizer tokens) + { + if(!tokens.hasMoreTokens()) + { + return _results; + } + TopicWord word = dictionary.getWord(tokens.nextToken()); + TopicMatcherDFAState nextState = _nextStateMap.get(word); + if(nextState == null && word != TopicWord.ANY_WORD) + { + nextState = _nextStateMap.get(TopicWord.ANY_WORD); + } + if(nextState == null) + { + return Collections.EMPTY_SET; + } + // Shortcut if we are at a looping terminal state + if((nextState == this) && (_nextStateMap.size() == 1) && _nextStateMap.containsKey(TopicWord.ANY_WORD)) + { + return _results; + } + + return nextState.parse(dictionary, tokens); + + } + + + public TopicMatcherDFAState mergeStateMachines(TopicMatcherDFAState otherStateMachine) + { + Map<Set<TopicMatcherDFAState>, TopicMatcherDFAState> newStateMap= new HashMap<Set<TopicMatcherDFAState>, TopicMatcherDFAState>(); + + Collection<TopicMatcherResult> results; + + if(_results.isEmpty()) + { + results = otherStateMachine._results; + } + else if(otherStateMachine._results.isEmpty()) + { + results = _results; + } + else + { + results = new HashSet<TopicMatcherResult>(_results); + results.addAll(otherStateMachine._results); + } + + + final Map<TopicWord, TopicMatcherDFAState> newNextStateMap = new HashMap<TopicWord, TopicMatcherDFAState>(); + + TopicMatcherDFAState newState = new TopicMatcherDFAState(newNextStateMap, results); + + + Set<TopicMatcherDFAState> oldStates = new HashSet<TopicMatcherDFAState>(); + oldStates.add(this); + oldStates.add(otherStateMachine); + + newStateMap.put(oldStates, newState); + + mergeStateMachines(oldStates, newNextStateMap, newStateMap); + + return newState; + + } + + private static void mergeStateMachines( + final Set<TopicMatcherDFAState> oldStates, + final Map<TopicWord, TopicMatcherDFAState> newNextStateMap, + final Map<Set<TopicMatcherDFAState>, TopicMatcherDFAState> newStateMap) + { + Map<TopicWord, Set<TopicMatcherDFAState>> nfaMap = new HashMap<TopicWord, Set<TopicMatcherDFAState>>(); + + for(TopicMatcherDFAState state : oldStates) + { + Map<TopicWord, TopicMatcherDFAState> map = state._nextStateMap; + for(Map.Entry<TopicWord, TopicMatcherDFAState> entry : map.entrySet()) + { + Set<TopicMatcherDFAState> states = nfaMap.get(entry.getKey()); + if(states == null) + { + states = new HashSet<TopicMatcherDFAState>(); + nfaMap.put(entry.getKey(), states); + } + states.add(entry.getValue()); + } + } + + Set<TopicMatcherDFAState> anyWordStates = nfaMap.get(TopicWord.ANY_WORD); + + for(Map.Entry<TopicWord, Set<TopicMatcherDFAState>> transition : nfaMap.entrySet()) + { + Set<TopicMatcherDFAState> destinations = transition.getValue(); + + if(anyWordStates != null) + { + destinations.addAll(anyWordStates); + } + + TopicMatcherDFAState nextState = newStateMap.get(destinations); + if(nextState == null) + { + + if(destinations.size() == 1) + { + nextState = destinations.iterator().next(); + newStateMap.put(destinations, nextState); + } + else + { + Collection<TopicMatcherResult> results; + + Set<Collection<TopicMatcherResult>> resultSets = new HashSet<Collection<TopicMatcherResult>>(); + for(TopicMatcherDFAState destination : destinations) + { + resultSets.add(destination._results); + } + resultSets.remove(Collections.EMPTY_SET); + if(resultSets.size() == 0) + { + results = Collections.EMPTY_SET; + } + else if(resultSets.size() == 1) + { + results = resultSets.iterator().next(); + } + else + { + results = new HashSet<TopicMatcherResult>(); + for(Collection<TopicMatcherResult> oldResult : resultSets) + { + results.addAll(oldResult); + } + } + + final Map<TopicWord, TopicMatcherDFAState> nextStateMap = new HashMap<TopicWord, TopicMatcherDFAState>(); + + nextState = new TopicMatcherDFAState(nextStateMap, results); + newStateMap.put(destinations, nextState); + + mergeStateMachines( + destinations, + nextStateMap, + newStateMap); + + + } + + + } + newNextStateMap.put(transition.getKey(),nextState); + } + + // Remove redundant transitions where defined tokenWord has same action as ANY_WORD + TopicMatcherDFAState anyWordState = newNextStateMap.get(TopicWord.ANY_WORD); + if(anyWordState != null) + { + List<TopicWord> removeList = new ArrayList<TopicWord>(); + for(Map.Entry<TopicWord,TopicMatcherDFAState> entry : newNextStateMap.entrySet()) + { + if(entry.getValue() == anyWordState && entry.getKey() != TopicWord.ANY_WORD) + { + removeList.add(entry.getKey()); + } + } + for(TopicWord removeKey : removeList) + { + newNextStateMap.remove(removeKey); + } + } + + + + } + + + public String toString() + { + StringBuilder transitions = new StringBuilder(); + for(Map.Entry<TopicWord, TopicMatcherDFAState> entry : _nextStateMap.entrySet()) + { + transitions.append("[ "); + transitions.append(entry.getKey()); + transitions.append("\t ->\t "); + transitions.append(entry.getValue()._id); + transitions.append(" ]\n"); + } + + + return "[ State " + _id + " ]\n" + transitions + "\n"; + + } + + public String reachableStates() + { + StringBuilder result = new StringBuilder("Start state: " + _id + "\n"); + + SortedSet<TopicMatcherDFAState> reachableStates = + new TreeSet<TopicMatcherDFAState>(new Comparator<TopicMatcherDFAState>() + { + public int compare(final TopicMatcherDFAState o1, final TopicMatcherDFAState o2) + { + return o1._id - o2._id; + } + }); + reachableStates.add(this); + + int count; + + do + { + count = reachableStates.size(); + Collection<TopicMatcherDFAState> originalStates = new ArrayList<TopicMatcherDFAState>(reachableStates); + for(TopicMatcherDFAState state : originalStates) + { + reachableStates.addAll(state._nextStateMap.values()); + } + } + while(reachableStates.size() != count); + + + + for(TopicMatcherDFAState state : reachableStates) + { + result.append(state.toString()); + } + + return result.toString(); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherResult.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherResult.java new file mode 100644 index 0000000000..71d30adfac --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicMatcherResult.java @@ -0,0 +1,25 @@ +package org.apache.qpid.server.exchange.topic; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ +public interface TopicMatcherResult +{ +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicNormalizer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicNormalizer.java new file mode 100644 index 0000000000..7e7cb6c0ae --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicNormalizer.java @@ -0,0 +1,96 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.exchange.topic; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.AMQShortStringTokenizer; +import org.apache.qpid.server.exchange.TopicExchange; + +import java.util.List; +import java.util.ArrayList; + +public class TopicNormalizer +{ + private static final byte TOPIC_SEPARATOR = (byte)'.'; + private static final byte HASH_BYTE = (byte)'#'; + private static final byte STAR_BYTE = (byte)'*'; + + private static final AMQShortString TOPIC_SEPARATOR_AS_SHORTSTRING = new AMQShortString("."); + private static final AMQShortString AMQP_STAR_TOKEN = new AMQShortString("*"); + private static final AMQShortString AMQP_HASH_TOKEN = new AMQShortString("#"); + + public static AMQShortString normalize(AMQShortString routingKey) + { + if(routingKey == null) + { + return AMQShortString.EMPTY_STRING; + } + else if(!(routingKey.contains(HASH_BYTE) || routingKey.contains(STAR_BYTE))) + { + return routingKey; + } + else + { + AMQShortStringTokenizer routingTokens = routingKey.tokenize(TOPIC_SEPARATOR); + + List<AMQShortString> subscriptionList = new ArrayList<AMQShortString>(); + + while (routingTokens.hasMoreTokens()) + { + subscriptionList.add(routingTokens.nextToken()); + } + + int size = subscriptionList.size(); + + for (int index = 0; index < size; index++) + { + // if there are more levels + if ((index + 1) < size) + { + if (subscriptionList.get(index).equals(AMQP_HASH_TOKEN)) + { + if (subscriptionList.get(index + 1).equals(AMQP_HASH_TOKEN)) + { + // we don't need #.# delete this one + subscriptionList.remove(index); + size--; + // redo this normalisation + index--; + } + + if (subscriptionList.get(index + 1).equals(AMQP_STAR_TOKEN)) + { + // we don't want #.* swap to *.# + // remove it and put it in at index + 1 + subscriptionList.add(index + 1, subscriptionList.remove(index)); + } + } + } // if we have more levels + } + + + + AMQShortString normalizedString = AMQShortString.join(subscriptionList, TOPIC_SEPARATOR_AS_SHORTSTRING); + + return normalizedString; + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicParser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicParser.java new file mode 100644 index 0000000000..3e9facf412 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicParser.java @@ -0,0 +1,613 @@ +package org.apache.qpid.server.exchange.topic; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.AMQShortStringTokenizer; + +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; +import java.io.IOException; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ +public class TopicParser +{ + private static final byte TOPIC_DELIMITER = (byte)'.'; + + private final TopicWordDictionary _dictionary = new TopicWordDictionary(); + private final AtomicReference<TopicMatcherDFAState> _stateMachine = new AtomicReference<TopicMatcherDFAState>(); + + private static class Position + { + private final TopicWord _word; + private final boolean _selfTransition; + private final int _position; + private final boolean _endState; + private boolean _followedByAnyLoop; + + + public Position(final int position, final TopicWord word, final boolean selfTransition, final boolean endState) + { + _position = position; + _word = word; + _selfTransition = selfTransition; + _endState = endState; + } + + + } + + private static final Position ERROR_POSITION = new Position(Integer.MAX_VALUE,null, true, false); + + private static class SimpleState + { + Set<Position> _positions; + Map<TopicWord, SimpleState> _nextState; + } + + + public void addBinding(AMQShortString bindingKey, TopicMatcherResult result) + { + + TopicMatcherDFAState startingStateMachine; + TopicMatcherDFAState newStateMachine; + + do + { + startingStateMachine = _stateMachine.get(); + if(startingStateMachine == null) + { + newStateMachine = createStateMachine(bindingKey, result); + } + else + { + newStateMachine = startingStateMachine.mergeStateMachines(createStateMachine(bindingKey, result)); + } + + } + while(!_stateMachine.compareAndSet(startingStateMachine,newStateMachine)); + + } + + public Collection<TopicMatcherResult> parse(AMQShortString routingKey) + { + TopicMatcherDFAState stateMachine = _stateMachine.get(); + if(stateMachine == null) + { + return Collections.EMPTY_SET; + } + else + { + return stateMachine.parse(_dictionary,routingKey); + } + } + + + TopicMatcherDFAState createStateMachine(AMQShortString bindingKey, TopicMatcherResult result) + { + List<TopicWord> wordList = createTopicWordList(bindingKey); + int wildCards = 0; + for(TopicWord word : wordList) + { + if(word == TopicWord.WILDCARD_WORD) + { + wildCards++; + } + } + if(wildCards == 0) + { + TopicMatcherDFAState[] states = new TopicMatcherDFAState[wordList.size()+1]; + states[states.length-1] = new TopicMatcherDFAState(Collections.EMPTY_MAP, Collections.singleton(result)); + for(int i = states.length-2; i >= 0; i--) + { + states[i] = new TopicMatcherDFAState(Collections.singletonMap(wordList.get(i),states[i+1]),Collections.EMPTY_SET); + + } + return states[0]; + } + else if(wildCards == wordList.size()) + { + Map<TopicWord,TopicMatcherDFAState> stateMap = new HashMap<TopicWord,TopicMatcherDFAState>(); + TopicMatcherDFAState state = new TopicMatcherDFAState(stateMap, Collections.singleton(result)); + stateMap.put(TopicWord.ANY_WORD, state); + return state; + } + + + int positionCount = wordList.size() - wildCards; + + Position[] positions = new Position[positionCount+1]; + + int lastWord; + + if(wordList.get(wordList.size()-1)== TopicWord.WILDCARD_WORD) + { + lastWord = wordList.size()-1; + positions[positionCount] = new Position(positionCount, TopicWord.ANY_WORD, true, true); + } + else + { + lastWord = wordList.size(); + positions[positionCount] = new Position(positionCount, TopicWord.ANY_WORD, false, true); + } + + + int pos = 0; + int wordPos = 0; + + + while(wordPos < lastWord) + { + TopicWord word = wordList.get(wordPos++); + + if(word == TopicWord.WILDCARD_WORD) + { + int nextWordPos = wordPos++; + word = wordList.get(nextWordPos); + + positions[pos] = new Position(pos++,word,true,false); + } + else + { + positions[pos] = new Position(pos++,word,false,false); + } + + } + + + for(int p = 0; p<positionCount; p++) + { + boolean followedByWildcards = true; + + int n = p; + while(followedByWildcards && n<(positionCount+1)) + { + + if(positions[n]._selfTransition) + { + break; + } + else if(positions[n]._word!=TopicWord.ANY_WORD) + { + followedByWildcards = false; + } + n++; + } + + + positions[p]._followedByAnyLoop = followedByWildcards && (n!= positionCount+1); + } + + + // from each position you transition to a set of other positions. + // we approach this by examining steps of increasing length - so we + // look how far we can go from the start position in 1 word, 2 words, etc... + + Map<Set<Position>,SimpleState> stateMap = new HashMap<Set<Position>,SimpleState>(); + + + SimpleState state = new SimpleState(); + state._positions = Collections.singleton( positions[0] ); + stateMap.put(state._positions, state); + + calculateNextStates(state, stateMap, positions); + + SimpleState[] simpleStates = stateMap.values().toArray(new SimpleState[stateMap.size()]); + HashMap<TopicWord, TopicMatcherDFAState>[] dfaStateMaps = new HashMap[simpleStates.length]; + Map<SimpleState, TopicMatcherDFAState> simple2DFAMap = new HashMap<SimpleState, TopicMatcherDFAState>(); + + for(int i = 0; i < simpleStates.length; i++) + { + + Collection<TopicMatcherResult> results; + boolean endState = false; + + for(Position p : simpleStates[i]._positions) + { + if(p._endState) + { + endState = true; + break; + } + } + + if(endState) + { + results = Collections.singleton(result); + } + else + { + results = Collections.EMPTY_SET; + } + + dfaStateMaps[i] = new HashMap<TopicWord, TopicMatcherDFAState>(); + simple2DFAMap.put(simpleStates[i], new TopicMatcherDFAState(dfaStateMaps[i],results)); + + } + for(int i = 0; i < simpleStates.length; i++) + { + SimpleState simpleState = simpleStates[i]; + + Map<TopicWord, SimpleState> nextSimpleStateMap = simpleState._nextState; + for(Map.Entry<TopicWord, SimpleState> stateMapEntry : nextSimpleStateMap.entrySet()) + { + dfaStateMaps[i].put(stateMapEntry.getKey(), simple2DFAMap.get(stateMapEntry.getValue())); + } + + } + + return simple2DFAMap.get(state); + + } + + + + private void calculateNextStates(final SimpleState state, + final Map<Set<Position>, SimpleState> stateMap, + final Position[] positions) + { + Map<TopicWord, Set<Position>> transitions = new HashMap<TopicWord,Set<Position>>(); + + for(Position pos : state._positions) + { + if(pos._selfTransition) + { + Set<Position> dest = transitions.get(TopicWord.ANY_WORD); + if(dest == null) + { + dest = new HashSet<Position>(); + transitions.put(TopicWord.ANY_WORD,dest); + } + dest.add(pos); + } + + final int nextPos = pos._position + 1; + Position nextPosition = nextPos == positions.length ? ERROR_POSITION : positions[nextPos]; + + Set<Position> dest = transitions.get(pos._word); + if(dest == null) + { + dest = new HashSet<Position>(); + transitions.put(pos._word,dest); + } + dest.add(nextPosition); + + } + + Set<Position> anyWordTransitions = transitions.get(TopicWord.ANY_WORD); + if(anyWordTransitions != null) + { + for(Set<Position> dest : transitions.values()) + { + dest.addAll(anyWordTransitions); + } + } + + state._nextState = new HashMap<TopicWord, SimpleState>(); + + for(Map.Entry<TopicWord,Set<Position>> dest : transitions.entrySet()) + { + + if(dest.getValue().size()>1) + { + dest.getValue().remove(ERROR_POSITION); + } + Position loopingTerminal = null; + for(Position destPos : dest.getValue()) + { + if(destPos._selfTransition && destPos._endState) + { + loopingTerminal = destPos; + break; + } + } + + if(loopingTerminal!=null) + { + dest.setValue(Collections.singleton(loopingTerminal)); + } + else + { + Position anyLoop = null; + for(Position destPos : dest.getValue()) + { + if(destPos._followedByAnyLoop) + { + if(anyLoop == null || anyLoop._position<destPos._position) + { + anyLoop = destPos; + } + } + } + if(anyLoop != null) + { + Collection<Position> removals = new ArrayList<Position>(); + for(Position destPos : dest.getValue()) + { + if(destPos._position < anyLoop._position) + { + removals.add(destPos); + } + } + dest.getValue().removeAll(removals); + } + } + + SimpleState stateForEntry = stateMap.get(dest.getValue()); + if(stateForEntry == null) + { + stateForEntry = new SimpleState(); + stateForEntry._positions = dest.getValue(); + stateMap.put(dest.getValue(),stateForEntry); + calculateNextStates(stateForEntry, + stateMap, + positions); + } + state._nextState.put(dest.getKey(),stateForEntry); + + + + } + + // remove redundant transitions + SimpleState anyWordState = state._nextState.get(TopicWord.ANY_WORD); + if(anyWordState != null) + { + List<TopicWord> removeList = new ArrayList<TopicWord>(); + for(Map.Entry<TopicWord,SimpleState> entry : state._nextState.entrySet()) + { + if(entry.getValue() == anyWordState && entry.getKey() != TopicWord.ANY_WORD) + { + removeList.add(entry.getKey()); + } + } + for(TopicWord removeKey : removeList) + { + state._nextState.remove(removeKey); + } + } + + + } + + private List<TopicWord> createTopicWordList(final AMQShortString bindingKey) + { + AMQShortStringTokenizer tokens = bindingKey.tokenize(TOPIC_DELIMITER); + TopicWord previousWord = null; + + List<TopicWord> wordList = new ArrayList<TopicWord>(); + + while(tokens.hasMoreTokens()) + { + TopicWord nextWord = _dictionary.getOrCreateWord(tokens.nextToken()); + if(previousWord == TopicWord.WILDCARD_WORD) + { + + if(nextWord == TopicWord.WILDCARD_WORD) + { + // consecutive wildcards can be merged + // i.e. subsequent wildcards can be discarded + continue; + } + else if(nextWord == TopicWord.ANY_WORD) + { + // wildcard and anyword can be reordered to always put anyword first + wordList.set(wordList.size()-1,TopicWord.ANY_WORD); + nextWord = TopicWord.WILDCARD_WORD; + } + } + wordList.add(nextWord); + previousWord = nextWord; + + } + return wordList; + } + + + public static void main(String[] args) + { + + printMatches("#.b.*.*.*.*.*.h.#.j.*.*.*.*.*.*.q.#.r.*.*.*.*.*.*.*.*","a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z"); + printMatches(new String[]{ + "#.a.#", + "#.b.#", + "#.c.#", + "#.d.#", + "#.e.#", + "#.f.#", + "#.g.#", + "#.h.#", + "#.i.#", + "#.j.#", + "#.k.#", + "#.l.#", + "#.m.#", + "#.n.#", + "#.o.#", + "#.p.#", + "#.q.#" + + }, "a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z"); +/* + printMatches(new String[]{ + "#.a.#", + "#.b.#", + "#.c.#", + "#.d.#", + "#.e.#", + "#.f.#", + "#.g.#", + "#.h.#", + "#.i.#", + "#.j.#", + "#.k.#", + "#.l.#", + "#.m.#", + "#.n.#", + "#.o.#", + "#.p.#", + "#.q.#", + "#.r.#", + "#.s.#", + "#.t.#", + "#.u.#", + "#.v.#", + "#.w.#", + "#.x.#", + "#.y.#", + "#.z.#" + + + },"a.b"); + + printMatches("#.b.*.*.*.*.*.h.#.j.*.*.*.*.*.p.#.r.*.*.*.*.*","a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z"); + printMatches("#.b.*.*.*.*.*.h.#.j.*.*.*.*.*.p.#.r.*.*.*.*.*.*.*.*","a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z"); + printMatches("a.#.b.#","a.b.b.b.b.b.b.b.c"); + +*/ + + printMatches("",""); + printMatches("a","a"); + printMatches("a",""); + printMatches("","a"); + printMatches("a.b","a.b"); + printMatches("a","a.b"); + printMatches("a.b","a"); + printMatches("*","a"); + printMatches("*.b","a.b"); + printMatches("*.*","a.b"); + printMatches("a.*","a.b"); + printMatches("a.*.#","a.b"); + printMatches("a.#.b","a.b"); + + printMatches("#.b","a"); + printMatches("#.b","a.b"); + printMatches("#.a.b","a.b"); + + + printMatches("#",""); + printMatches("#","a"); + printMatches("#","a.b"); + printMatches("#.#","a.b"); + printMatches("#.*","a.b"); + + printMatches("#.a.b","a.b"); + printMatches("a.b.#","a.b"); + printMatches("a.#","a.b"); + printMatches("#.*.#","a.b"); + printMatches("#.*.b.#","a.b"); + printMatches("#.a.*.#","a.b"); + printMatches("#.a.#.b.#","a.b"); + printMatches("#.*.#.*.#","a.b"); + printMatches("*.#.*.#","a.b"); + printMatches("#.*.#.*","a.b"); + + + printMatches(new String[]{"a.#.b.#","a.*.#.b.#"},"a.b.b.b.b.b.b.b.c"); + + + printMatches(new String[]{"a.b", "a.c"},"a.b"); + printMatches(new String[]{"a.#", "a.c", "#.b"},"a.b"); + printMatches(new String[]{"a.#", "a.c", "#.b", "#", "*.*"},"a.b"); + + printMatches(new String[]{"a.b.c.d.e.#", "a.b.c.d.#", "a.b.c.d.*", "a.b.c.#", "#.e", "a.*.c.d.e","#.c.*.#.*.*"},"a.b.c.d.e"); + printMatches(new String[]{"a.b.c.d.e.#", "a.b.c.d.#", "a.b.c.d.*", "a.b.c.#", "#.e", "a.*.c.d.e","#.c.*.#.*.*"},"a.b.c.d.f.g"); + + + + + } + + private static void printMatches(final String[] bindingKeys, final String routingKey) + { + TopicMatcherDFAState sm = null; + Map<TopicMatcherResult, String> resultMap = new HashMap<TopicMatcherResult, String>(); + + TopicParser parser = new TopicParser(); + + long start = System.currentTimeMillis(); + for(int i = 0; i < bindingKeys.length; i++) + { + System.out.println((System.currentTimeMillis() - start) + ":\t" + bindingKeys[i]); + TopicMatcherResult r = new TopicMatcherResult(){}; + resultMap.put(r, bindingKeys[i]); + AMQShortString bindingKeyShortString = new AMQShortString(bindingKeys[i]); + + System.err.println("====================================================="); + System.err.println("Adding binding key: " + bindingKeyShortString); + System.err.println("-----------------------------------------------------"); + + + if(i==0) + { + sm = parser.createStateMachine(bindingKeyShortString, r); + } + else + { + sm = sm.mergeStateMachines(parser.createStateMachine(bindingKeyShortString, r)); + } + System.err.println(sm.reachableStates()); + System.err.println("====================================================="); + try + { + System.in.read(); + } + catch (IOException e) + { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + } + AMQShortString routingKeyShortString = new AMQShortString(routingKey); + + Collection<TopicMatcherResult> results = sm.parse(parser._dictionary, routingKeyShortString); + Collection<String> resultStrings = new ArrayList<String>(); + + for(TopicMatcherResult result : results) + { + resultStrings.add(resultMap.get(result)); + } + + final ArrayList<String> nonMatches = new ArrayList<String>(Arrays.asList(bindingKeys)); + nonMatches.removeAll(resultStrings); + System.out.println("\""+routingKeyShortString+"\" matched with " + resultStrings + " DID NOT MATCH with " + nonMatches); + + + } + + private static void printMatches(String bindingKey, String routingKey) + { + printMatches(new String[] { bindingKey }, routingKey); + } + + + private static boolean matches(String bindingKey, String routingKey) + { + AMQShortString bindingKeyShortString = new AMQShortString(bindingKey); + AMQShortString routingKeyShortString = new AMQShortString(routingKey); + TopicParser parser = new TopicParser(); + + final TopicMatcherResult result = new TopicMatcherResult(){}; + + TopicMatcherDFAState sm = parser.createStateMachine(bindingKeyShortString, result); + return !sm.parse(parser._dictionary,routingKeyShortString).isEmpty(); + + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicWord.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicWord.java new file mode 100644 index 0000000000..f14d70f8a1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicWord.java @@ -0,0 +1,54 @@ +package org.apache.qpid.server.exchange.topic; + +import org.apache.qpid.framing.AMQShortString; + +import java.util.Map; +import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ +public final class TopicWord +{ + public static final TopicWord ANY_WORD = new TopicWord("*"); + public static final TopicWord WILDCARD_WORD = new TopicWord("#"); + private String _word; + + public TopicWord() + { + + } + + public TopicWord(String s) + { + _word = s; + } + + public TopicWord(final AMQShortString name) + { + _word = name.toString(); + } + + public String toString() + { + return _word; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicWordDictionary.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicWordDictionary.java new file mode 100644 index 0000000000..65a0cd3107 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/exchange/topic/TopicWordDictionary.java @@ -0,0 +1,63 @@ +package org.apache.qpid.server.exchange.topic; + +import org.apache.qpid.framing.AMQShortString; + +import java.util.concurrent.ConcurrentHashMap; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ +public class TopicWordDictionary +{ + private final ConcurrentHashMap<AMQShortString,TopicWord> _dictionary = + new ConcurrentHashMap<AMQShortString,TopicWord>(); + + + + public TopicWordDictionary() + { + _dictionary.put(new AMQShortString("*"), TopicWord.ANY_WORD); + _dictionary.put(new AMQShortString("#"), TopicWord.WILDCARD_WORD); + } + + + + + public TopicWord getOrCreateWord(AMQShortString name) + { + TopicWord word = _dictionary.putIfAbsent(name, new TopicWord(name)); + if(word == null) + { + word = _dictionary.get(name); + } + return word; + } + + + public TopicWord getWord(AMQShortString name) + { + TopicWord word = _dictionary.get(name); + if(word == null) + { + word = TopicWord.ANY_WORD; + } + return word; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/federation/Bridge.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/federation/Bridge.java new file mode 100644 index 0000000000..fbc5387daf --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/federation/Bridge.java @@ -0,0 +1,823 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.federation; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.configuration.BridgeConfig; +import org.apache.qpid.server.configuration.BridgeConfigType; +import org.apache.qpid.server.configuration.ConfiguredObject; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.flow.FlowCreditManager_0_10; +import org.apache.qpid.server.flow.WindowCreditManager; +import org.apache.qpid.server.message.MessageMetaData_0_10; +import org.apache.qpid.server.message.MessageTransferMessage; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.subscription.Subscription_0_10; +import org.apache.qpid.server.transport.ServerSession; +import org.apache.qpid.server.txn.AutoCommitTransaction; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.transport.DeliveryProperties; +import org.apache.qpid.transport.MessageAcceptMode; +import org.apache.qpid.transport.MessageAcquireMode; +import org.apache.qpid.transport.MessageCreditUnit; +import org.apache.qpid.transport.MessageFlowMode; +import org.apache.qpid.transport.MessageReject; +import org.apache.qpid.transport.MessageRejectCode; +import org.apache.qpid.transport.MessageTransfer; +import org.apache.qpid.transport.Option; +import org.apache.qpid.transport.RangeSet; +import org.apache.qpid.transport.Session; +import org.apache.qpid.transport.SessionException; +import org.apache.qpid.transport.SessionListener; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class Bridge implements BridgeConfig +{ + private final boolean _durable; + private final boolean _dynamic; + private final boolean _queueBridge; + private final boolean _localSource; + private final String _source; + private final String _destination; + private final String _key; + private final String _tag; + private final String _excludes; + private final BrokerLink _link; + private UUID _id; + private long _createTime = System.currentTimeMillis(); + + private Session _session; + + private BridgeImpl _delegate; + + private final int _bridgeNo; + private AutoCommitTransaction _transaction; + + public Bridge(final BrokerLink brokerLink, + final int bridgeNo, + final boolean durable, + final boolean dynamic, + final boolean srcIsQueue, + final boolean srcIsLocal, + final String src, + final String dest, + final String key, + final String tag, + final String excludes) + { + _link = brokerLink; + _bridgeNo = bridgeNo; + _durable = durable; + _dynamic = dynamic; + _queueBridge = srcIsQueue; + _localSource = srcIsLocal; + _source = src; + _destination = dest; + _key = key; + _tag = tag; + _excludes = excludes; + _id = brokerLink.getConfigStore().createId(); + + _transaction = new AutoCommitTransaction(getVirtualHost().getMessageStore()); + + if(dynamic) + { + if(srcIsLocal) + { + // TODO + } + else + { + if(srcIsQueue) + { + // TODO + } + else + { + _delegate = new DynamicExchangeBridge(); + } + } + } + else + { + if(srcIsLocal) + { + if(srcIsQueue) + { + _delegate = new StaticQueuePushBridge(); + } + else + { + _delegate = new StaticExchangePushBridge(); + } + } + else + { + if(srcIsQueue) + { + _delegate = new StaticQueuePullBridge(); + } + else + { + _delegate = new StaticExchangePullBridge(); + } + } + } + } + + public UUID getId() + { + return _id; + } + + public BridgeConfigType getConfigType() + { + return BridgeConfigType.getInstance(); + } + + public ConfiguredObject getParent() + { + return getLink(); + } + + public boolean isDurable() + { + return _durable; + } + + public boolean isDynamic() + { + return _dynamic; + } + + public boolean isQueueBridge() + { + return _queueBridge; + } + + public boolean isLocalSource() + { + return _localSource; + } + + public String getSource() + { + return _source; + } + + public String getDestination() + { + return _destination; + } + + public String getKey() + { + return _key; + } + + public String getTag() + { + return _tag; + } + + public String getExcludes() + { + return _excludes; + } + + public BrokerLink getLink() + { + return _link; + } + + public Integer getChannelId() + { + return (_session == null) ? 0 : _session.getChannel(); + } + + public int getAckBatching() + { + return 0; + } + + public long getCreateTime() + { + return _createTime; + } + + @Override + public boolean equals(final Object o) + { + if (this == o) + { + return true; + } + if (o == null || getClass() != o.getClass()) + { + return false; + } + + final Bridge bridge = (Bridge) o; + + if (_durable != bridge._durable) + { + return false; + } + if (_dynamic != bridge._dynamic) + { + return false; + } + if (_localSource != bridge._localSource) + { + return false; + } + if (_queueBridge != bridge._queueBridge) + { + return false; + } + if (_destination != null ? !_destination.equals(bridge._destination) : bridge._destination != null) + { + return false; + } + if (_excludes != null ? !_excludes.equals(bridge._excludes) : bridge._excludes != null) + { + return false; + } + if (_key != null ? !_key.equals(bridge._key) : bridge._key != null) + { + return false; + } + if (_source != null ? !_source.equals(bridge._source) : bridge._source != null) + { + return false; + } + if (_tag != null ? !_tag.equals(bridge._tag) : bridge._tag != null) + { + return false; + } + + return true; + } + + @Override + public int hashCode() + { + int result = (_durable ? 1 : 0); + result = 31 * result + (_dynamic ? 1 : 0); + result = 31 * result + (_queueBridge ? 1 : 0); + result = 31 * result + (_localSource ? 1 : 0); + result = 31 * result + (_source != null ? _source.hashCode() : 0); + result = 31 * result + (_destination != null ? _destination.hashCode() : 0); + result = 31 * result + (_key != null ? _key.hashCode() : 0); + result = 31 * result + (_tag != null ? _tag.hashCode() : 0); + result = 31 * result + (_excludes != null ? _excludes.hashCode() : 0); + return result; + } + + public void setSession(final Session session) + { + _session = session; + _delegate.setSession(session); + } + + private long getMessageWindowSize() + { + return 10l; + } + + + VirtualHost getVirtualHost() + { + return _link.getVirtualHost(); + } + + public void close() + { + // TODO + _delegate.close(); + _session = null; + } + + + private interface BridgeImpl + { + void setSession(Session session); + + void close(); + } + + private abstract class AbstractPullBridge implements BridgeImpl, SessionListener + { + public final void setSession(final Session session) + { + session.setSessionListener(this); + onSession(); + + } + + abstract void onSession(); + + + + public void message(final Session ssn, final MessageTransfer xfr) + { + ExchangeRegistry exchangeRegistry = getVirtualHost().getExchangeRegistry(); + + Exchange exchange = exchangeRegistry.getExchange(_destination); + + // TODO - deal with exchange not existing + + DeliveryProperties delvProps = null; + if(xfr.getHeader() != null && (delvProps = xfr.getHeader().get(DeliveryProperties.class)) != null && delvProps.hasTtl() && !delvProps.hasExpiration()) + { + delvProps.setExpiration(System.currentTimeMillis() + delvProps.getTtl()); + } + + MessageMetaData_0_10 messageMetaData = new MessageMetaData_0_10(xfr); + final MessageStore store = getVirtualHost().getMessageStore(); + StoredMessage<MessageMetaData_0_10> storeMessage = store.addMessage(messageMetaData); + storeMessage.addContent(0,xfr.getBody()); + storeMessage.flushToStore(); + MessageTransferMessage message = new MessageTransferMessage(storeMessage, ((ServerSession)_session).getReference()); + + ArrayList<? extends BaseQueue> queues = exchange.route(message); + + + + if(queues != null && queues.size() != 0) + { + enqueue(message, queues); + } + else + { + if(delvProps == null || !delvProps.hasDiscardUnroutable() || !delvProps.getDiscardUnroutable()) + { + if(xfr.getAcceptMode() == MessageAcceptMode.EXPLICIT) + { + RangeSet rejects = new RangeSet(); + rejects.add(xfr.getId()); + MessageReject reject = new MessageReject(rejects, MessageRejectCode.UNROUTABLE, "Unroutable"); + ssn.invoke(reject); + } + else + { + Exchange alternate = exchange.getAlternateExchange(); + if(alternate != null) + { + queues = alternate.route(message); + if(queues != null && queues.size() != 0) + { + enqueue(message, queues); + } + else + { + //TODO - log the message discard + } + } + else + { + //TODO - log the message discard + } + + + } + } + + + } + + ssn.processed(xfr); + + } + + + private void enqueue(final ServerMessage message, final ArrayList<? extends BaseQueue> queues) + { + _transaction.enqueue(queues,message, new ServerTransaction.Action() + { + + BaseQueue[] _queues = queues.toArray(new BaseQueue[queues.size()]); + + public void postCommit() + { + for(int i = 0; i < _queues.length; i++) + { + try + { + _queues[i].enqueue(message); + } + catch (AMQException e) + { + // TODO + + throw new RuntimeException(e); + } + } + } + + public void onRollback() + { + // NO-OP + } + }); + + } + + public void exception(final Session session, final SessionException exception) + { + // TODO - Handle exceptions + } + + public void closed(final Session session) + { + // TODO - handle close + } + + public void opened(final Session session) + { + // this method never called + } + + public void resumed(final Session session) + { + // will never resume these sessions + } + + + + } + + private final class StaticExchangePullBridge extends AbstractPullBridge + { + private final String _tmpQueueName = "bridge_queue_" + _bridgeNo + "_" + _link.getFederationTag();; + + public void onSession() + { + + final HashMap<String, Object> options = new HashMap<String, Object>(); + options.put("qpid.trace.exclude", _link.getFederationTag()); + options.put("qpid.trace.id",_link.getRemoteFederationTag()); + _session.queueDeclare(_tmpQueueName,null, options, Option.AUTO_DELETE, Option.EXCLUSIVE); + _session.sync(); + // todo check exception + final Map<String,Object> bindingArgs = new HashMap<String,Object>(); + _session.exchangeBind(_tmpQueueName, _source, _key, bindingArgs); + _session.sync(); + // todo check exception + + final Map<String,Object> subscribeOptions = Collections.EMPTY_MAP; + final String subName = String.valueOf(_bridgeNo); + _session.messageSubscribe(_tmpQueueName, + subName,MessageAcceptMode.NONE,MessageAcquireMode.PRE_ACQUIRED,null,0l, subscribeOptions); + _session.sync(); + // todo check exception + + _session.messageSetFlowMode(subName,MessageFlowMode.WINDOW); + _session.messageFlow(subName, MessageCreditUnit.MESSAGE, getMessageWindowSize()); + _session.messageFlow(subName, MessageCreditUnit.BYTE, 0xFFFFFFFF); + + } + + public void close() + { + // TODO + } + } + + private final class StaticQueuePullBridge extends AbstractPullBridge + { + + public void onSession() + { + + final Map<String,Object> subscribeOptions = Collections.EMPTY_MAP; + final String subName = String.valueOf(_bridgeNo); + _session.messageSubscribe(_source, + subName,MessageAcceptMode.NONE,MessageAcquireMode.PRE_ACQUIRED,null,0l, subscribeOptions); + _session.sync(); + // todo check exception + + _session.messageSetFlowMode(subName,MessageFlowMode.WINDOW); + _session.messageFlow(subName, MessageCreditUnit.MESSAGE, getMessageWindowSize()); + _session.messageFlow(subName, MessageCreditUnit.BYTE, 0xFFFFFFFF); + + } + + public void close() + { + // TODO + } + } + + private final class DynamicExchangeBridge extends AbstractPullBridge implements Exchange.BindingListener + { + private final String _tmpQueueName = "bridge_queue_" + _bridgeNo + "_" + _link.getFederationTag(); + + private final ConcurrentMap<Binding,Binding> _bindings = new ConcurrentHashMap<Binding,Binding>(); + + + void onSession() + { + + + final HashMap<String, Object> options = new HashMap<String, Object>(); + options.put("qpid.trace.exclude", _link.getFederationTag()); + options.put("qpid.trace.id",_link.getRemoteFederationTag()); + _session.queueDeclare(_tmpQueueName,null, options, Option.AUTO_DELETE, Option.EXCLUSIVE); + _session.sync(); + // todo - check exception + + final Map<String,Object> subscribeOptions = Collections.EMPTY_MAP; + final String subName = String.valueOf(_bridgeNo); + _session.messageSubscribe(_tmpQueueName, + subName,MessageAcceptMode.NONE,MessageAcquireMode.PRE_ACQUIRED,null,0l, subscribeOptions); + _session.sync(); + // todo check exception + _session.messageSetFlowMode(subName,MessageFlowMode.WINDOW); + _session.messageFlow(subName, MessageCreditUnit.MESSAGE, getMessageWindowSize()); + _session.messageFlow(subName, MessageCreditUnit.BYTE, 0xFFFFFFFF); + _session.sync(); + // todo check exception + + + ExchangeRegistry exchangeRegistry = getVirtualHost().getExchangeRegistry(); + + Exchange exchange = exchangeRegistry.getExchange(_destination); + + // TODO - check null + + exchange.addBindingListener(this); + + Collection<Binding> bindings = exchange.getBindings(); + for(Binding binding : bindings) + { + propogateBinding(binding); + } + + } + + private void propogateBinding(final Binding binding) + { + if(_bindings.putIfAbsent(binding,binding)== null) + { + Map<String,Object> arguments = new HashMap<String,Object>(binding.getArguments()); + + if(arguments.get("qpid.fed.origin") == null) + { + arguments.put("qpid.fed.op",""); + arguments.put("qpid.fed.origin",_link.getFederationTag()); + arguments.put("qpid.fed.tags",_link.getFederationTag()); + } + else + { + String tags = (String) arguments.get("qpid.fed.tags"); + if(tags == null) + { + tags = _link.getFederationTag(); + } + else + { + if(Arrays.asList(tags.split(",")).contains(_link.getFederationTag())) + { + return; + } + tags += "," + _link.getFederationTag(); + } + arguments.put("qpid.fed.tags", tags); + } + + _session.exchangeBind(_tmpQueueName, _source, binding.getBindingKey(), arguments); + _session.sync(); + // TODO - check exception? + + } + } + + private void propogateBindingRemoval(final Binding binding) + { + if(_bindings.remove(binding) != null) + { + // TODO - this is wrong!!!! + _session.exchangeUnbind(_tmpQueueName, _source, binding.getBindingKey()); + } + } + + + public void bindingAdded(final Exchange exchange, final Binding binding) + { + propogateBinding(binding); + } + + public void bindingRemoved(final Exchange exchange, final Binding binding) + { + propogateBindingRemoval(binding); + } + + public void close() + { + // TODO + } + } + + private class StaticExchangePushBridge implements BridgeImpl, SessionListener + { + private final String _tmpQueueName = "bridge_queue_" + _bridgeNo + "_" + _link.getFederationTag(); + private AMQQueue _queue; + + public void setSession(final Session session) + { + assert session instanceof ServerSession; + + session.setSessionListener(this); + + ExchangeRegistry exchangeRegistry = getVirtualHost().getExchangeRegistry(); + + Exchange exchange = exchangeRegistry.getExchange(_source); + + // TODO - Check null + + final HashMap<String, Object> options = new HashMap<String, Object>(); + options.put("qpid.trace.exclude", _link.getFederationTag()); + options.put("qpid.trace.id",_link.getRemoteFederationTag()); + + try + { + _queue = AMQQueueFactory.createAMQQueueImpl(_tmpQueueName, + isDurable(), + _link.getFederationTag(), + false, + false, + getVirtualHost(), + options); + } + catch (AMQException e) + { + // TODO + throw new RuntimeException(e); + } + + FlowCreditManager_0_10 creditManager = new WindowCreditManager(0xFFFFFFFF,getMessageWindowSize()); + + //TODO Handle the passing of non-null Filters and Arguments here + + Subscription_0_10 sub = new Subscription_0_10((ServerSession)session, + _destination, + MessageAcceptMode.NONE, + MessageAcquireMode.PRE_ACQUIRED, + MessageFlowMode.WINDOW, + creditManager, null,null); + + ((ServerSession)session).register(_destination, sub); + + try + { + _queue.registerSubscription(sub, true); + getVirtualHost().getBindingFactory().addBinding(_key, _queue, exchange, Collections.<String, Object>emptyMap()); + } + catch (AMQException e) + { + // TODO + throw new RuntimeException(e); + } + } + + public void close() + { + // TODO + } + + public void opened(final Session session) + { + // this method never called + } + + public void resumed(final Session session) + { + // this session will never be resumed + } + + public void message(final Session ssn, final MessageTransfer xfr) + { + // messages should not be sent ... should probably log error + } + + public void exception(final Session session, final SessionException exception) + { + // TODO + } + + public void closed(final Session session) + { + // TODO + } + } + + private class StaticQueuePushBridge implements BridgeImpl, SessionListener + { + private AMQQueue _queue; + + public void setSession(final Session session) + { + assert session instanceof ServerSession; + + session.setSessionListener(this); + + QueueRegistry queueRegistry = getVirtualHost().getQueueRegistry(); + + _queue = queueRegistry.getQueue(_source); + + // TODO - null check + + FlowCreditManager_0_10 creditManager = new WindowCreditManager(0xFFFFFFFF,getMessageWindowSize()); + + //TODO Handle the passing of non-null Filters and Arguments here + + Subscription_0_10 sub = new Subscription_0_10((ServerSession)session, + _destination, + MessageAcceptMode.NONE, + MessageAcquireMode.PRE_ACQUIRED, + MessageFlowMode.WINDOW, + creditManager, null,null); + + ((ServerSession)session).register(_destination, sub); + + try + { + _queue.registerSubscription(sub, false); + } + catch (AMQException e) + { + // TODO + throw new RuntimeException(e); + } + + } + + public void close() + { + // TODO + } + + public void opened(final Session session) + { + // never called + } + + public void resumed(final Session session) + { + // session will not resume + } + + public void message(final Session ssn, final MessageTransfer xfr) + { + // should never be called ... should probably log error + } + + public void exception(final Session session, final SessionException exception) + { + // TODO + } + + public void closed(final Session session) + { + // TODO + } + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/federation/BrokerLink.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/federation/BrokerLink.java new file mode 100644 index 0000000000..fa2fb9ead1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/federation/BrokerLink.java @@ -0,0 +1,512 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.federation; + +import org.apache.qpid.server.configuration.ConfigStore; +import org.apache.qpid.server.configuration.ConfiguredObject; +import org.apache.qpid.server.configuration.ConnectionConfig; +import org.apache.qpid.server.configuration.ConnectionConfigType; +import org.apache.qpid.server.configuration.LinkConfig; +import org.apache.qpid.server.configuration.LinkConfigType; +import org.apache.qpid.server.transport.ServerSession; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.transport.Binary; +import org.apache.qpid.transport.Connection; +import org.apache.qpid.transport.ConnectionException; +import org.apache.qpid.transport.ConnectionListener; +import org.apache.qpid.transport.Session; +import org.apache.qpid.transport.SessionDelegate; +import org.apache.qpid.transport.TransportException; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +public class BrokerLink implements LinkConfig, ConnectionListener +{ + + private static final int CORE_POOL_SIZE = 4; + + private static final ScheduledThreadPoolExecutor _threadPool = + new ScheduledThreadPoolExecutor(CORE_POOL_SIZE); + + + private final String _transport; + private final String _host; + private final int _port; + private final String _remoteVhost; + private final boolean _durable; + private final String _authMechanism; + private final String _username; + private final String _password; + private final VirtualHost _virtualHost; + private UUID _id; + private AtomicBoolean _closing = new AtomicBoolean(); + private final long _createTime = System.currentTimeMillis(); + private Connection _qpidConnection; + private AtomicReference<Thread> _executor = new AtomicReference<Thread>(); + private AtomicInteger _bridgeId = new AtomicInteger(); + + private final ConcurrentHashMap<Bridge,Bridge> _bridges = new ConcurrentHashMap<Bridge,Bridge>(); + private final ConcurrentHashMap<Bridge,Bridge> _activeBridges = new ConcurrentHashMap<Bridge,Bridge>(); + private final ConcurrentLinkedQueue<Bridge> _pendingBridges = new ConcurrentLinkedQueue<Bridge>(); + private String _remoteFederationTag; + + private ConnectionConfig _connectionConfig; + private ConnectionException _exception; + private String _lastErrorMessage; + private int _retryDelay = 1; + private final Runnable _makeConnectionTask = new Runnable() + { + public void run() + { + doMakeConnection(); + } + };; + ; + + public static enum State + { + OPERATIONAL, + DOWN, + ESTABLISHING, + DELETED + } + + + private volatile State _state = State.DOWN; + + private static final AtomicReferenceFieldUpdater<BrokerLink, State> _stateUpdater = + AtomicReferenceFieldUpdater.newUpdater(BrokerLink.class, State.class, "_state"); + + private class ConnectionConfigAdapter implements ConnectionConfig + { + private long _adapterCreateTime = System.currentTimeMillis(); + private UUID _id = BrokerLink.this.getConfigStore().createId(); + + public VirtualHost getVirtualHost() + { + return BrokerLink.this.getVirtualHost(); + } + + public String getAddress() + { + return _host+":"+_port; + } + + public Boolean isIncoming() + { + return false; + } + + public Boolean isSystemConnection() + { + return true; + } + + public Boolean isFederationLink() + { + return true; + } + + public String getAuthId() + { + return _username; + } + + public String getRemoteProcessName() + { + return null; + } + + public Integer getRemotePID() + { + return null; + } + + public Integer getRemoteParentPID() + { + return null; + } + + public ConfigStore getConfigStore() + { + return getVirtualHost().getConfigStore(); + } + + public UUID getId() + { + return _id; + } + + public ConnectionConfigType getConfigType() + { + return ConnectionConfigType.getInstance(); + } + + public ConfiguredObject getParent() + { + return getVirtualHost(); + } + + public boolean isDurable() + { + return false; + } + + public long getCreateTime() + { + return _adapterCreateTime; + } + + public Boolean isShadow() + { + return false; + } + + public void mgmtClose() + { + _connectionConfig.mgmtClose(); + } + } + + private class SessionFactory implements Connection.SessionFactory + { + + public Session newSession(final Connection conn, final Binary name, final long expiry) + { + return new ServerSession(conn, new SessionDelegate(), name, expiry, _connectionConfig); + } + }; + + + public BrokerLink(final VirtualHost virtualHost, + final String transport, + final String host, + final int port, + final String remoteVhost, + final boolean durable, + final String authMechanism, final String username, final String password) + { + _virtualHost = virtualHost; + _transport = transport; + _host = host; + _port = port; + _remoteVhost = remoteVhost; + _durable = durable; + _authMechanism = authMechanism; + _username = username; + _password = password; + _id = virtualHost.getConfigStore().createId(); + _qpidConnection = new Connection(); + _connectionConfig = new ConnectionConfigAdapter(); + _qpidConnection.addConnectionListener(this); + + + makeConnection(); + } + + private final boolean updateState(State expected, State newState) + { + return _stateUpdater.compareAndSet(this,expected,newState); + } + + private void makeConnection() + { + _threadPool.execute(_makeConnectionTask); + } + + + + private void doMakeConnection() + { + if(updateState(State.DOWN, State.ESTABLISHING)) + { + try + { + _qpidConnection.connect(_host, _port, _remoteVhost, _username, _password, "ssl".equals(_transport), _authMechanism); + + final Map<String,Object> serverProps = _qpidConnection.getServerProperties(); + _remoteFederationTag = (String) serverProps.get("qpid.federation_tag"); + if(_remoteFederationTag == null) + { + _remoteFederationTag = UUID.fromString(_transport+":"+_host+":"+_port).toString(); + } + _qpidConnection.setSessionFactory(new SessionFactory()); + _qpidConnection.setAuthorizationID(_username == null ? "" : _username); + + updateState(State.ESTABLISHING, State.OPERATIONAL); + + _retryDelay = 1; + + for(Bridge bridge : _bridges.values()) + { + if(_state != State.OPERATIONAL) + { + break; + } + addBridge(bridge); + } + + + } + catch (TransportException e) + { + _lastErrorMessage = e.getMessage(); + if(_retryDelay < 60) + { + _retryDelay <<= 1; + } + + updateState(State.ESTABLISHING, State.DOWN); + _activeBridges.clear(); + scheduleConnectionRetry(); + } + } + } + + private void scheduleConnectionRetry() + { + if(_state != State.DELETED) + { + _threadPool.schedule(_makeConnectionTask, _retryDelay, TimeUnit.SECONDS); + } + } + + public VirtualHost getVirtualHost() + { + return _virtualHost; + } + + public String getTransport() + { + return _transport; + } + + public String getHost() + { + return _host; + } + + public int getPort() + { + return _port; + } + + public String getRemoteVhost() + { + return _remoteVhost; + } + + public UUID getId() + { + return _id; + } + + public LinkConfigType getConfigType() + { + return LinkConfigType.getInstance(); + } + + public ConfiguredObject getParent() + { + return getVirtualHost(); + } + + public boolean isDurable() + { + return _durable; + } + + public String getAuthMechanism() + { + return _authMechanism; + } + + public String getUsername() + { + return _username; + } + + public String getPassword() + { + return _password; + } + + @Override + public boolean equals(final Object o) + { + if (this == o) + { + return true; + } + if (o == null || getClass() != o.getClass()) + { + return false; + } + + final BrokerLink that = (BrokerLink) o; + + if (_port != that._port) + { + return false; + } + if (_host != null ? !_host.equals(that._host) : that._host != null) + { + return false; + } + if (_remoteVhost != null ? !_remoteVhost.equals(that._remoteVhost) : that._remoteVhost != null) + { + return false; + } + if (_transport != null ? !_transport.equals(that._transport) : that._transport != null) + { + return false; + } + + return true; + } + + @Override + public int hashCode() + { + int result = _transport != null ? _transport.hashCode() : 0; + result = 31 * result + (_host != null ? _host.hashCode() : 0); + result = 31 * result + _port; + result = 31 * result + (_remoteVhost != null ? _remoteVhost.hashCode() : 0); + return result; + } + + public void close() + { + if(_closing.compareAndSet(false,true)) + { + // TODO - close connection + for(Bridge bridge : _bridges.values()) + { + bridge.close(); + } + _bridges.clear(); + + _virtualHost.removeBrokerConnection(this); + } + } + + public long getCreateTime() + { + return _createTime; + } + + public void createBridge(final boolean durable, + final boolean dynamic, + final boolean srcIsQueue, + final boolean srcIsLocal, + final String src, + final String dest, + final String key, + final String tag, + final String excludes) + { + if(!_closing.get()) + { + Bridge bridge = new Bridge(this, _bridgeId.incrementAndGet(), durable,dynamic,srcIsQueue,srcIsLocal,src,dest,key,tag,excludes); + if(_bridges.putIfAbsent(bridge, bridge) == null) + { + + addBridge(bridge); + } + } + + + } + + private void addBridge(final Bridge bridge) + { + getConfigStore().addConfiguredObject(bridge); + + if(_state == State.OPERATIONAL && (_activeBridges.putIfAbsent(bridge,bridge) == null)) + { + + + Session session = _qpidConnection.createSession("Bridge(" + + (bridge.isDurable() ? "durable" : "transient") + + "," + (bridge.isDynamic() ? "dynamic" : "static") + + "," + (bridge.isQueueBridge() ? "queue" : "exchange") + + "," + (bridge.isLocalSource() ? "local-src" : "remote-src") + + ",[Source: '" + bridge.getSource() + "']" + + ",[Destination: '" + bridge.getDestination() + "']" + + ",[Key: '" + bridge.getKey() + "']" + + ",[Tag: '" + bridge.getTag() + "']" + + ".[Excludes: '" + bridge.getExcludes() + "'])"); + bridge.setSession(session); + + + if(_closing.get()) + { + bridge.close(); + } + } + + } + + public void opened(final Connection connection) + { + // this method not called + } + + public void exception(final Connection connection, final ConnectionException exception) + { + _exception = exception; + _lastErrorMessage = exception.getMessage(); + + } + + public void closed(final Connection connection) + { + State currentState = _state; + if(currentState != State.DOWN && currentState != State.DELETED && updateState(currentState, State.DOWN)) + { + scheduleConnectionRetry(); + } + } + + public ConfigStore getConfigStore() + { + return getVirtualHost().getConfigStore(); + } + + public String getFederationTag() + { + return getVirtualHost().getFederationTag(); + } + + public String getRemoteFederationTag() + { + return _remoteFederationTag; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ArithmeticExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ArithmeticExpression.java new file mode 100644 index 0000000000..221d23ef0d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ArithmeticExpression.java @@ -0,0 +1,275 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.qpid.server.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.Filterable; + +/** + * An expression which performs an operation on two expression values + */ +public abstract class ArithmeticExpression extends BinaryExpression +{ + + protected static final int INTEGER = 1; + protected static final int LONG = 2; + protected static final int DOUBLE = 3; + + /** + * @param left + * @param right + */ + public ArithmeticExpression(Expression left, Expression right) + { + super(left, right); + } + + public static Expression createPlus(Expression left, Expression right) + { + return new ArithmeticExpression(left, right) + { + protected Object evaluate(Object lvalue, Object rvalue) + { + if (lvalue instanceof String) + { + String text = (String) lvalue; + String answer = text + rvalue; + + return answer; + } + else if (lvalue instanceof Number) + { + return plus((Number) lvalue, asNumber(rvalue)); + } + + throw new RuntimeException("Cannot call plus operation on: " + lvalue + " and: " + rvalue); + } + + public String getExpressionSymbol() + { + return "+"; + } + }; + } + + public static Expression createMinus(Expression left, Expression right) + { + return new ArithmeticExpression(left, right) + { + protected Object evaluate(Object lvalue, Object rvalue) + { + if (lvalue instanceof Number) + { + return minus((Number) lvalue, asNumber(rvalue)); + } + + throw new RuntimeException("Cannot call minus operation on: " + lvalue + " and: " + rvalue); + } + + public String getExpressionSymbol() + { + return "-"; + } + }; + } + + public static Expression createMultiply(Expression left, Expression right) + { + return new ArithmeticExpression(left, right) + { + + protected Object evaluate(Object lvalue, Object rvalue) + { + if (lvalue instanceof Number) + { + return multiply((Number) lvalue, asNumber(rvalue)); + } + + throw new RuntimeException("Cannot call multiply operation on: " + lvalue + " and: " + rvalue); + } + + public String getExpressionSymbol() + { + return "*"; + } + }; + } + + public static Expression createDivide(Expression left, Expression right) + { + return new ArithmeticExpression(left, right) + { + + protected Object evaluate(Object lvalue, Object rvalue) + { + if (lvalue instanceof Number) + { + return divide((Number) lvalue, asNumber(rvalue)); + } + + throw new RuntimeException("Cannot call divide operation on: " + lvalue + " and: " + rvalue); + } + + public String getExpressionSymbol() + { + return "/"; + } + }; + } + + public static Expression createMod(Expression left, Expression right) + { + return new ArithmeticExpression(left, right) + { + + protected Object evaluate(Object lvalue, Object rvalue) + { + if (lvalue instanceof Number) + { + return mod((Number) lvalue, asNumber(rvalue)); + } + + throw new RuntimeException("Cannot call mod operation on: " + lvalue + " and: " + rvalue); + } + + public String getExpressionSymbol() + { + return "%"; + } + }; + } + + protected Number plus(Number left, Number right) + { + switch (numberType(left, right)) + { + + case INTEGER: + return Integer.valueOf(left.intValue() + right.intValue()); + + case LONG: + return Long.valueOf(left.longValue() + right.longValue()); + + default: + return Double.valueOf(left.doubleValue() + right.doubleValue()); + } + } + + protected Number minus(Number left, Number right) + { + switch (numberType(left, right)) + { + + case INTEGER: + return Integer.valueOf(left.intValue() - right.intValue()); + + case LONG: + return Long.valueOf(left.longValue() - right.longValue()); + + default: + return Double.valueOf(left.doubleValue() - right.doubleValue()); + } + } + + protected Number multiply(Number left, Number right) + { + switch (numberType(left, right)) + { + + case INTEGER: + return Integer.valueOf(left.intValue() * right.intValue()); + + case LONG: + return Long.valueOf(left.longValue() * right.longValue()); + + default: + return Double.valueOf(left.doubleValue() * right.doubleValue()); + } + } + + protected Number divide(Number left, Number right) + { + return Double.valueOf(left.doubleValue() / right.doubleValue()); + } + + protected Number mod(Number left, Number right) + { + return Double.valueOf(left.doubleValue() % right.doubleValue()); + } + + private int numberType(Number left, Number right) + { + if (isDouble(left) || isDouble(right)) + { + return DOUBLE; + } + else if ((left instanceof Long) || (right instanceof Long)) + { + return LONG; + } + else + { + return INTEGER; + } + } + + private boolean isDouble(Number n) + { + return (n instanceof Float) || (n instanceof Double); + } + + protected Number asNumber(Object value) + { + if (value instanceof Number) + { + return (Number) value; + } + else + { + throw new RuntimeException("Cannot convert value: " + value + " into a number"); + } + } + + public Object evaluate(Filterable message) + { + Object lvalue = left.evaluate(message); + if (lvalue == null) + { + return null; + } + + Object rvalue = right.evaluate(message); + if (rvalue == null) + { + return null; + } + + return evaluate(lvalue, rvalue); + } + + /** + * @param lvalue + * @param rvalue + * @return + */ + protected abstract Object evaluate(Object lvalue, Object rvalue); + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/BinaryExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/BinaryExpression.java new file mode 100644 index 0000000000..024257bea9 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/BinaryExpression.java @@ -0,0 +1,106 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.qpid.server.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +/** + * An expression which performs an operation on two expression values. + */ +public abstract class BinaryExpression implements Expression +{ + protected Expression left; + protected Expression right; + + public BinaryExpression(Expression left, Expression right) + { + this.left = left; + this.right = right; + } + + public Expression getLeft() + { + return left; + } + + public Expression getRight() + { + return right; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() + { + return "(" + left.toString() + " " + getExpressionSymbol() + " " + right.toString() + ")"; + } + + /** + * TODO: more efficient hashCode() + * + * @see java.lang.Object#hashCode() + */ + public int hashCode() + { + return toString().hashCode(); + } + + /** + * TODO: more efficient hashCode() + * + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object o) + { + + if ((o == null) || !this.getClass().equals(o.getClass())) + { + return false; + } + + return toString().equals(o.toString()); + + } + + /** + * Returns the symbol that represents this binary expression. For example, addition is + * represented by "+" + * + * @return + */ + public abstract String getExpressionSymbol(); + + /** + * @param expression + */ + public void setRight(Expression expression) + { + right = expression; + } + + /** + * @param expression + */ + public void setLeft(Expression expression) + { + left = expression; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/BooleanExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/BooleanExpression.java new file mode 100644 index 0000000000..06e8664470 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/BooleanExpression.java @@ -0,0 +1,38 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.qpid.server.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +import org.apache.qpid.server.queue.Filterable; + +/** + * A BooleanExpression is an expression that always + * produces a Boolean result. + */ +public interface BooleanExpression extends Expression +{ + + /** + * @param message + * @return true if the expression evaluates to Boolean.TRUE. + */ + public boolean matches(Filterable message); + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ComparisonExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ComparisonExpression.java new file mode 100644 index 0000000000..aad9d41174 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ComparisonExpression.java @@ -0,0 +1,599 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +import java.util.HashSet; +import java.util.List; +import java.util.regex.Pattern; + +import org.apache.qpid.server.queue.Filterable; + +/** + * A filter performing a comparison of two objects + */ +public abstract class ComparisonExpression extends BinaryExpression implements BooleanExpression +{ + + public static BooleanExpression createBetween(Expression value, Expression left, Expression right) + { + return LogicExpression.createAND(createGreaterThanEqual(value, left), createLessThanEqual(value, right)); + } + + public static BooleanExpression createNotBetween(Expression value, Expression left, Expression right) + { + return LogicExpression.createOR(createLessThan(value, left), createGreaterThan(value, right)); + } + + private static final HashSet<Character> REGEXP_CONTROL_CHARS = new HashSet<Character>(); + + static + { + REGEXP_CONTROL_CHARS.add('.'); + REGEXP_CONTROL_CHARS.add('\\'); + REGEXP_CONTROL_CHARS.add('['); + REGEXP_CONTROL_CHARS.add(']'); + REGEXP_CONTROL_CHARS.add('^'); + REGEXP_CONTROL_CHARS.add('$'); + REGEXP_CONTROL_CHARS.add('?'); + REGEXP_CONTROL_CHARS.add('*'); + REGEXP_CONTROL_CHARS.add('+'); + REGEXP_CONTROL_CHARS.add('{'); + REGEXP_CONTROL_CHARS.add('}'); + REGEXP_CONTROL_CHARS.add('|'); + REGEXP_CONTROL_CHARS.add('('); + REGEXP_CONTROL_CHARS.add(')'); + REGEXP_CONTROL_CHARS.add(':'); + REGEXP_CONTROL_CHARS.add('&'); + REGEXP_CONTROL_CHARS.add('<'); + REGEXP_CONTROL_CHARS.add('>'); + REGEXP_CONTROL_CHARS.add('='); + REGEXP_CONTROL_CHARS.add('!'); + } + + static class LikeExpression extends UnaryExpression implements BooleanExpression + { + + Pattern likePattern; + + /** + * @param right + */ + public LikeExpression(Expression right, String like, int escape) + { + super(right); + + StringBuffer regexp = new StringBuffer(like.length() * 2); + regexp.append("\\A"); // The beginning of the input + for (int i = 0; i < like.length(); i++) + { + char c = like.charAt(i); + if (escape == (0xFFFF & c)) + { + i++; + if (i >= like.length()) + { + // nothing left to escape... + break; + } + + char t = like.charAt(i); + regexp.append("\\x"); + regexp.append(Integer.toHexString(0xFFFF & t)); + } + else if (c == '%') + { + regexp.append(".*?"); // Do a non-greedy match + } + else if (c == '_') + { + regexp.append("."); // match one + } + else if (REGEXP_CONTROL_CHARS.contains(c)) + { + regexp.append("\\x"); + regexp.append(Integer.toHexString(0xFFFF & c)); + } + else + { + regexp.append(c); + } + } + + regexp.append("\\z"); // The end of the input + + likePattern = Pattern.compile(regexp.toString(), Pattern.DOTALL); + } + + /** + * org.apache.activemq.filter.UnaryExpression#getExpressionSymbol() + */ + public String getExpressionSymbol() + { + return "LIKE"; + } + + /** + * org.apache.activemq.filter.Expression#evaluate(MessageEvaluationContext) + */ + public Object evaluate(Filterable message) + { + + Object rv = this.getRight().evaluate(message); + + if (rv == null) + { + return null; + } + + if (!(rv instanceof String)) + { + return + Boolean.FALSE; + // throw new RuntimeException("LIKE can only operate on String identifiers. LIKE attemped on: '" + rv.getClass()); + } + + return likePattern.matcher((String) rv).matches() ? Boolean.TRUE : Boolean.FALSE; + } + + public boolean matches(Filterable message) + { + Object object = evaluate(message); + + return (object != null) && (object == Boolean.TRUE); + } + } + + public static BooleanExpression createLike(Expression left, String right, String escape) + { + if ((escape != null) && (escape.length() != 1)) + { + throw new RuntimeException( + "The ESCAPE string litteral is invalid. It can only be one character. Litteral used: " + escape); + } + + int c = -1; + if (escape != null) + { + c = 0xFFFF & escape.charAt(0); + } + + return new LikeExpression(left, right, c); + } + + public static BooleanExpression createNotLike(Expression left, String right, String escape) + { + return UnaryExpression.createNOT(createLike(left, right, escape)); + } + + public static BooleanExpression createInFilter(Expression left, List elements) + { + + if (!(left instanceof PropertyExpression)) + { + throw new RuntimeException("Expected a property for In expression, got: " + left); + } + + return UnaryExpression.createInExpression((PropertyExpression) left, elements, false); + + } + + public static BooleanExpression createNotInFilter(Expression left, List elements) + { + + if (!(left instanceof PropertyExpression)) + { + throw new RuntimeException("Expected a property for In expression, got: " + left); + } + + return UnaryExpression.createInExpression((PropertyExpression) left, elements, true); + + } + + public static BooleanExpression createIsNull(Expression left) + { + return doCreateEqual(left, ConstantExpression.NULL); + } + + public static BooleanExpression createIsNotNull(Expression left) + { + return UnaryExpression.createNOT(doCreateEqual(left, ConstantExpression.NULL)); + } + + public static BooleanExpression createNotEqual(Expression left, Expression right) + { + return UnaryExpression.createNOT(createEqual(left, right)); + } + + public static BooleanExpression createEqual(Expression left, Expression right) + { + checkEqualOperand(left); + checkEqualOperand(right); + checkEqualOperandCompatability(left, right); + + return doCreateEqual(left, right); + } + + private static BooleanExpression doCreateEqual(Expression left, Expression right) + { + return new EqualExpression(left, right); + } + + public static BooleanExpression createGreaterThan(final Expression left, final Expression right) + { + checkLessThanOperand(left); + checkLessThanOperand(right); + + return new ComparisonExpression(left, right) + { + protected boolean asBoolean(int answer) + { + return answer > 0; + } + + public String getExpressionSymbol() + { + return ">"; + } + }; + } + + public static BooleanExpression createGreaterThanEqual(final Expression left, final Expression right) + { + checkLessThanOperand(left); + checkLessThanOperand(right); + + return new ComparisonExpression(left, right) + { + protected boolean asBoolean(int answer) + { + return answer >= 0; + } + + public String getExpressionSymbol() + { + return ">="; + } + }; + } + + public static BooleanExpression createLessThan(final Expression left, final Expression right) + { + checkLessThanOperand(left); + checkLessThanOperand(right); + + return new ComparisonExpression(left, right) + { + + protected boolean asBoolean(int answer) + { + return answer < 0; + } + + public String getExpressionSymbol() + { + return "<"; + } + + }; + } + + public static BooleanExpression createLessThanEqual(final Expression left, final Expression right) + { + checkLessThanOperand(left); + checkLessThanOperand(right); + + return new ComparisonExpression(left, right) + { + + protected boolean asBoolean(int answer) + { + return answer <= 0; + } + + public String getExpressionSymbol() + { + return "<="; + } + }; + } + + /** + * Only Numeric expressions can be used in >, >=, < or <= expressions.s + * + * @param expr + */ + public static void checkLessThanOperand(Expression expr) + { + if (expr instanceof ConstantExpression) + { + Object value = ((ConstantExpression) expr).getValue(); + if (value instanceof Number) + { + return; + } + + // Else it's boolean or a String.. + throw new RuntimeException("Value '" + expr + "' cannot be compared."); + } + + if (expr instanceof BooleanExpression) + { + throw new RuntimeException("Value '" + expr + "' cannot be compared."); + } + } + + /** + * Validates that the expression can be used in == or <> expression. + * Cannot not be NULL TRUE or FALSE litterals. + * + * @param expr + */ + public static void checkEqualOperand(Expression expr) + { + if (expr instanceof ConstantExpression) + { + Object value = ((ConstantExpression) expr).getValue(); + if (value == null) + { + throw new RuntimeException("'" + expr + "' cannot be compared."); + } + } + } + + /** + * + * @param left + * @param right + */ + private static void checkEqualOperandCompatability(Expression left, Expression right) + { + if ((left instanceof ConstantExpression) && (right instanceof ConstantExpression)) + { + if ((left instanceof BooleanExpression) && !(right instanceof BooleanExpression)) + { + throw new RuntimeException("'" + left + "' cannot be compared with '" + right + "'"); + } + } + } + + /** + * @param left + * @param right + */ + public ComparisonExpression(Expression left, Expression right) + { + super(left, right); + } + + public Object evaluate(Filterable message) + { + Comparable lv = (Comparable) left.evaluate(message); + if (lv == null) + { + return null; + } + + Comparable rv = (Comparable) right.evaluate(message); + if (rv == null) + { + return null; + } + + return compare(lv, rv); + } + + protected Boolean compare(Comparable lv, Comparable rv) + { + Class lc = lv.getClass(); + Class rc = rv.getClass(); + // If the the objects are not of the same type, + // try to convert up to allow the comparison. + if (lc != rc) + { + if (lc == Byte.class) + { + if (rc == Short.class) + { + lv = ((Number) lv).shortValue(); + } + else if (rc == Integer.class) + { + lv = ((Number) lv).intValue(); + } + else if (rc == Long.class) + { + lv = ((Number) lv).longValue(); + } + else if (rc == Float.class) + { + lv = ((Number) lv).floatValue(); + } + else if (rc == Double.class) + { + lv = ((Number) lv).doubleValue(); + } + else + { + return Boolean.FALSE; + } + } + else if (lc == Short.class) + { + if (rc == Integer.class) + { + lv = ((Number) lv).intValue(); + } + else if (rc == Long.class) + { + lv = ((Number) lv).longValue(); + } + else if (rc == Float.class) + { + lv = ((Number) lv).floatValue(); + } + else if (rc == Double.class) + { + lv = ((Number) lv).doubleValue(); + } + else + { + return Boolean.FALSE; + } + } + else if (lc == Integer.class) + { + if (rc == Long.class) + { + lv = ((Number) lv).longValue(); + } + else if (rc == Float.class) + { + lv = ((Number) lv).floatValue(); + } + else if (rc == Double.class) + { + lv = ((Number) lv).doubleValue(); + } + else + { + return Boolean.FALSE; + } + } + else if (lc == Long.class) + { + if (rc == Integer.class) + { + rv = ((Number) rv).longValue(); + } + else if (rc == Float.class) + { + lv = ((Number) lv).floatValue(); + } + else if (rc == Double.class) + { + lv = ((Number) lv).doubleValue(); + } + else + { + return Boolean.FALSE; + } + } + else if (lc == Float.class) + { + if (rc == Integer.class) + { + rv = ((Number) rv).floatValue(); + } + else if (rc == Long.class) + { + rv = ((Number) rv).floatValue(); + } + else if (rc == Double.class) + { + lv = ((Number) lv).doubleValue(); + } + else + { + return Boolean.FALSE; + } + } + else if (lc == Double.class) + { + if (rc == Integer.class) + { + rv = ((Number) rv).doubleValue(); + } + else if (rc == Long.class) + { + rv = ((Number) rv).doubleValue(); + } + else if (rc == Float.class) + { + rv = ((Number) rv).doubleValue(); + } + else + { + return Boolean.FALSE; + } + } + else + { + return Boolean.FALSE; + } + } + + return asBoolean(lv.compareTo(rv)) ? Boolean.TRUE : Boolean.FALSE; + } + + protected abstract boolean asBoolean(int answer); + + public boolean matches(Filterable message) + { + Object object = evaluate(message); + + return (object != null) && (object == Boolean.TRUE); + } + + private static class EqualExpression extends ComparisonExpression + { + public EqualExpression(final Expression left, final Expression right) + { + super(left, right); + } + + public Object evaluate(Filterable message) + { + Object lv = left.evaluate(message); + Object rv = right.evaluate(message); + + // Iff one of the values is null + if ((lv == null) ^ (rv == null)) + { + return Boolean.FALSE; + } + + if ((lv == rv) || lv.equals(rv)) + { + return Boolean.TRUE; + } + + if ((lv instanceof Comparable) && (rv instanceof Comparable)) + { + return compare((Comparable) lv, (Comparable) rv); + } + + return Boolean.FALSE; + } + + protected boolean asBoolean(int answer) + { + return answer == 0; + } + + public String getExpressionSymbol() + { + return "="; + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ConstantExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ConstantExpression.java new file mode 100644 index 0000000000..5cc9ca8ef2 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/ConstantExpression.java @@ -0,0 +1,209 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +import java.math.BigDecimal; + +import org.apache.qpid.server.queue.Filterable; + +/** + * Represents a constant expression + */ +public class ConstantExpression implements Expression +{ + + static class BooleanConstantExpression extends ConstantExpression implements BooleanExpression + { + public BooleanConstantExpression(Object value) + { + super(value); + } + + public boolean matches(Filterable message) + { + Object object = evaluate(message); + + return (object != null) && (object == Boolean.TRUE); + } + } + + public static final BooleanConstantExpression NULL = new BooleanConstantExpression(null); + public static final BooleanConstantExpression TRUE = new BooleanConstantExpression(Boolean.TRUE); + public static final BooleanConstantExpression FALSE = new BooleanConstantExpression(Boolean.FALSE); + + private Object value; + + public static ConstantExpression createFromDecimal(String text) + { + + // Strip off the 'l' or 'L' if needed. + if (text.endsWith("l") || text.endsWith("L")) + { + text = text.substring(0, text.length() - 1); + } + + Number value; + try + { + value = new Long(text); + } + catch (NumberFormatException e) + { + // The number may be too big to fit in a long. + value = new BigDecimal(text); + } + + long l = value.longValue(); + if ((Integer.MIN_VALUE <= l) && (l <= Integer.MAX_VALUE)) + { + value = value.intValue(); + } + + return new ConstantExpression(value); + } + + public static ConstantExpression createFromHex(String text) + { + Number value = Long.parseLong(text.substring(2), 16); + long l = value.longValue(); + if ((Integer.MIN_VALUE <= l) && (l <= Integer.MAX_VALUE)) + { + value = value.intValue(); + } + + return new ConstantExpression(value); + } + + public static ConstantExpression createFromOctal(String text) + { + Number value = Long.parseLong(text, 8); + long l = value.longValue(); + if ((Integer.MIN_VALUE <= l) && (l <= Integer.MAX_VALUE)) + { + value = value.intValue(); + } + + return new ConstantExpression(value); + } + + public static ConstantExpression createFloat(String text) + { + Number value = new Double(text); + + return new ConstantExpression(value); + } + + public ConstantExpression(Object value) + { + this.value = value; + } + + public Object evaluate(Filterable message) + { + return value; + } + + public Object getValue() + { + return value; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() + { + if (value == null) + { + return "NULL"; + } + + if (value instanceof Boolean) + { + return ((Boolean) value) ? "TRUE" : "FALSE"; + } + + if (value instanceof String) + { + return encodeString((String) value); + } + + return value.toString(); + } + + /** + * TODO: more efficient hashCode() + * + * @see java.lang.Object#hashCode() + */ + public int hashCode() + { + return toString().hashCode(); + } + + /** + * TODO: more efficient hashCode() + * + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object o) + { + + if ((o == null) || !this.getClass().equals(o.getClass())) + { + return false; + } + + return toString().equals(o.toString()); + + } + + /** + * Encodes the value of string so that it looks like it would look like + * when it was provided in a selector. + * + * @param s + * @return + */ + public static String encodeString(String s) + { + StringBuffer b = new StringBuffer(); + b.append('\''); + for (int i = 0; i < s.length(); i++) + { + char c = s.charAt(i); + if (c == '\'') + { + b.append(c); + } + + b.append(c); + } + + b.append('\''); + + return b.toString(); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/Expression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/Expression.java new file mode 100644 index 0000000000..97e9915271 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/Expression.java @@ -0,0 +1,36 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.qpid.server.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +import org.apache.qpid.server.queue.Filterable; + +/** + * Represents an expression + */ +public interface Expression +{ + + /** + * @return the value of this expression + */ + public Object evaluate(Filterable message); + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManager.java new file mode 100644 index 0000000000..b5e282038b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManager.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +import org.apache.qpid.server.queue.Filterable; + +public interface FilterManager +{ + void add(MessageFilter filter); + + void remove(MessageFilter filter); + + boolean allAllow(Filterable msg); + + boolean hasFilters(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManagerFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManagerFactory.java new file mode 100644 index 0000000000..dac517150a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/FilterManagerFactory.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.filter; + +import java.util.Map; + +import org.apache.qpid.AMQException; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.framing.FieldTable; +import org.apache.log4j.Logger; + + +public class FilterManagerFactory +{ + + private final static Logger _logger = Logger.getLogger(FilterManagerFactory.class); + + //fixme move to a common class so it can be refered to from client code. + + public static FilterManager createManager(FieldTable filters) throws AMQException + { + FilterManager manager = null; + + if (filters != null) + { + + + + if(filters.containsKey(AMQPFilterTypes.JMS_SELECTOR.getValue())) + { + String selector = filters.getString(AMQPFilterTypes.JMS_SELECTOR.getValue()); + + if (selector != null && !selector.equals("")) + { + manager = new SimpleFilterManager(); + manager.add(new JMSSelectorFilter(selector)); + } + + } + + + } + else + { + _logger.debug("No Filters found."); + } + + + return manager; + + } + + public static FilterManager createManager(Map<String,Object> map) throws AMQException + { + return createManager(FieldTable.convertToFieldTable(map)); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/JMSSelectorFilter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/JMSSelectorFilter.java new file mode 100644 index 0000000000..f32de03841 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/JMSSelectorFilter.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.filter; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQInvalidArgumentException; +import org.apache.qpid.server.filter.jms.selector.SelectorParser; +import org.apache.qpid.server.queue.Filterable; + + +public class JMSSelectorFilter implements MessageFilter +{ + private final static Logger _logger = org.apache.log4j.Logger.getLogger(JMSSelectorFilter.class); + + private String _selector; + private BooleanExpression _matcher; + + public JMSSelectorFilter(String selector) throws AMQInvalidArgumentException + { + _selector = selector; + _matcher = new SelectorParser().parse(selector); + } + + public boolean matches(Filterable message) + { + boolean match = _matcher.matches(message); + if(_logger.isDebugEnabled()) + { + _logger.debug(message + " match(" + match + ") selector(" + System.identityHashCode(_selector) + "):" + _selector); + } + return match; + } + + public String getSelector() + { + return _selector; + } + + @Override + public String toString() + { + return "JMSSelector("+_selector+")"; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/LogicExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/LogicExpression.java new file mode 100644 index 0000000000..fdba184da4 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/LogicExpression.java @@ -0,0 +1,120 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.qpid.server.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +import org.apache.qpid.server.queue.Filterable; + +/** + * A filter performing a comparison of two objects + */ +public abstract class LogicExpression extends BinaryExpression implements BooleanExpression +{ + + public static BooleanExpression createOR(BooleanExpression lvalue, BooleanExpression rvalue) + { + return new OrExpression(lvalue, rvalue); + } + + public static BooleanExpression createAND(BooleanExpression lvalue, BooleanExpression rvalue) + { + return new AndExpression(lvalue, rvalue); + } + + /** + * @param left + * @param right + */ + public LogicExpression(BooleanExpression left, BooleanExpression right) + { + super(left, right); + } + + public abstract Object evaluate(Filterable message); + + public boolean matches(Filterable message) + { + Object object = evaluate(message); + + return (object != null) && (object == Boolean.TRUE); + } + + private static class OrExpression extends LogicExpression + { + public OrExpression(final BooleanExpression lvalue, final BooleanExpression rvalue) + { + super(lvalue, rvalue); + } + + public Object evaluate(Filterable message) + { + + Boolean lv = (Boolean) left.evaluate(message); + // Can we do an OR shortcut?? + if ((lv != null) && lv.booleanValue()) + { + return Boolean.TRUE; + } + + Boolean rv = (Boolean) right.evaluate(message); + + return (rv == null) ? null : rv; + } + + public String getExpressionSymbol() + { + return "OR"; + } + } + + private static class AndExpression extends LogicExpression + { + public AndExpression(final BooleanExpression lvalue, final BooleanExpression rvalue) + { + super(lvalue, rvalue); + } + + public Object evaluate(Filterable message) + { + + Boolean lv = (Boolean) left.evaluate(message); + + // Can we do an AND shortcut?? + if (lv == null) + { + return null; + } + + if (!lv.booleanValue()) + { + return Boolean.FALSE; + } + + Boolean rv = (Boolean) right.evaluate(message); + + return (rv == null) ? null : rv; + } + + public String getExpressionSymbol() + { + return "AND"; + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/MessageFilter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/MessageFilter.java new file mode 100644 index 0000000000..f5416af09a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/MessageFilter.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.filter; + +import org.apache.qpid.server.queue.Filterable; + +public interface MessageFilter +{ + boolean matches(Filterable message); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/NoConsumerFilter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/NoConsumerFilter.java new file mode 100644 index 0000000000..65ddf19fc4 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/NoConsumerFilter.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.filter; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.Filterable; + +public class NoConsumerFilter implements MessageFilter +{ + private final static Logger _logger = org.apache.log4j.Logger.getLogger(NoConsumerFilter.class); + + + public NoConsumerFilter() throws AMQException + { + _logger.info("Created NoConsumerFilter"); + } + + public boolean matches(Filterable message) + { + return true; + } + + @Override + public String toString() + { + return "NoConsumer"; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/PropertyExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/PropertyExpression.java new file mode 100644 index 0000000000..11fdeae2b1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/PropertyExpression.java @@ -0,0 +1,235 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +import java.util.HashMap; + +import org.apache.log4j.Logger; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.CommonContentHeaderProperties; +import org.apache.qpid.server.queue.Filterable; + +/** + * Represents a property expression + */ +public class PropertyExpression implements Expression +{ + // Constants - defined the same as JMS + private static final int NON_PERSISTENT = 1; + private static final int PERSISTENT = 2; + private static final int DEFAULT_PRIORITY = 4; + + private static final Logger _logger = org.apache.log4j.Logger.getLogger(PropertyExpression.class); + + private static final HashMap<String, Expression> JMS_PROPERTY_EXPRESSIONS = new HashMap<String, Expression>(); + + { + JMS_PROPERTY_EXPRESSIONS.put("JMSDestination", new Expression() + { + public Object evaluate(Filterable message) + { + //TODO + return null; + } + }); + JMS_PROPERTY_EXPRESSIONS.put("JMSReplyTo", new ReplyToExpression()); + + JMS_PROPERTY_EXPRESSIONS.put("JMSType", new TypeExpression()); + + JMS_PROPERTY_EXPRESSIONS.put("JMSDeliveryMode", new DeliveryModeExpression()); + + JMS_PROPERTY_EXPRESSIONS.put("JMSPriority", new PriorityExpression()); + + JMS_PROPERTY_EXPRESSIONS.put("JMSMessageID", new MessageIDExpression()); + + JMS_PROPERTY_EXPRESSIONS.put("AMQMessageID", new MessageIDExpression()); + + JMS_PROPERTY_EXPRESSIONS.put("JMSTimestamp", new TimestampExpression()); + + JMS_PROPERTY_EXPRESSIONS.put("JMSCorrelationID", new CorrelationIdExpression()); + + JMS_PROPERTY_EXPRESSIONS.put("JMSExpiration", new ExpirationExpression()); + + JMS_PROPERTY_EXPRESSIONS.put("JMSRedelivered", new Expression() + { + public Object evaluate(Filterable message) + { + return message.isRedelivered(); + } + }); + } + + private final String name; + private final Expression jmsPropertyExpression; + + public boolean outerTest() + { + return false; + } + + public PropertyExpression(String name) + { + this.name = name; + + + + jmsPropertyExpression = (Expression) JMS_PROPERTY_EXPRESSIONS.get(name); + } + + public Object evaluate(Filterable message) + { + + if (jmsPropertyExpression != null) + { + return jmsPropertyExpression.evaluate(message); + } + else + { + return message.getMessageHeader().getHeader(name); + } + } + + public String getName() + { + return name; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() + { + return name; + } + + /** + * @see java.lang.Object#hashCode() + */ + public int hashCode() + { + return name.hashCode(); + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object o) + { + + if ((o == null) || !this.getClass().equals(o.getClass())) + { + return false; + } + + return name.equals(((PropertyExpression) o).name); + + } + + private static class ReplyToExpression implements Expression + { + public Object evaluate(Filterable message) + { + String replyTo = message.getMessageHeader().getReplyTo(); + return replyTo; + } + + } + + private static class TypeExpression implements Expression + { + public Object evaluate(Filterable message) + { + + String type = message.getMessageHeader().getType(); + return type; + + } + } + + private static class DeliveryModeExpression implements Expression + { + public Object evaluate(Filterable message) + { + int mode = message.isPersistent() ? PERSISTENT : NON_PERSISTENT; + if (_logger.isDebugEnabled()) + { + _logger.debug("JMSDeliveryMode is :" + mode); + } + + return mode; + } + } + + private static class PriorityExpression implements Expression + { + public Object evaluate(Filterable message) + { + byte priority = message.getMessageHeader().getPriority(); + return (int) priority; + } + } + + private static class MessageIDExpression implements Expression + { + public Object evaluate(Filterable message) + { + + String messageId = message.getMessageHeader().getMessageId(); + + return messageId; + + } + } + + private static class TimestampExpression implements Expression + { + public Object evaluate(Filterable message) + { + long timestamp = message.getMessageHeader().getTimestamp(); + return timestamp; + } + } + + private static class CorrelationIdExpression implements Expression + { + public Object evaluate(Filterable message) + { + + String correlationId = message.getMessageHeader().getCorrelationId(); + + return correlationId; + } + } + + private static class ExpirationExpression implements Expression + { + public Object evaluate(Filterable message) + { + long expiration = message.getMessageHeader().getExpiration(); + return expiration; + + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/SimpleFilterManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/SimpleFilterManager.java new file mode 100644 index 0000000000..c563569cb4 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/SimpleFilterManager.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.filter; + +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.Filterable; + +public class SimpleFilterManager implements FilterManager +{ + private final Logger _logger = Logger.getLogger(SimpleFilterManager.class); + + private final ConcurrentLinkedQueue<MessageFilter> _filters; + private String _toString = ""; + + public SimpleFilterManager() + { + _logger.debug("Creating SimpleFilterManager"); + _filters = new ConcurrentLinkedQueue<MessageFilter>(); + } + + public void add(MessageFilter filter) + { + _filters.add(filter); + updateStringValue(); + } + + public void remove(MessageFilter filter) + { + _filters.remove(filter); + updateStringValue(); + } + + public boolean allAllow(Filterable msg) + { + for (MessageFilter filter : _filters) + { + if (!filter.matches(msg)) + { + return false; + } + } + return true; + } + + public boolean hasFilters() + { + return !_filters.isEmpty(); + } + + + @Override + public String toString() + { + return _toString; + } + + private void updateStringValue() + { + StringBuilder toString = new StringBuilder(); + for (MessageFilter filter : _filters) + { + toString.append(filter.toString()); + toString.append(","); + } + + if (_filters.size() > 0) + { + //Remove the last ',' + toString.deleteCharAt(toString.length()-1); + } + _toString = toString.toString(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/UnaryExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/UnaryExpression.java new file mode 100644 index 0000000000..557af95001 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/UnaryExpression.java @@ -0,0 +1,362 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.Filterable; + +/** + * An expression which performs an operation on two expression values + */ +public abstract class UnaryExpression implements Expression +{ + + private static final BigDecimal BD_LONG_MIN_VALUE = BigDecimal.valueOf(Long.MIN_VALUE); + protected Expression right; + + public static Expression createNegate(Expression left) + { + return new NegativeExpression(left); + } + + public static BooleanExpression createInExpression(PropertyExpression right, List elements, final boolean not) + { + + // Use a HashSet if there are many elements. + Collection t; + if (elements.size() == 0) + { + t = null; + } + else if (elements.size() < 5) + { + t = elements; + } + else + { + t = new HashSet(elements); + } + + final Collection inList = t; + + return new InExpression(right, inList, not); + } + + abstract static class BooleanUnaryExpression extends UnaryExpression implements BooleanExpression + { + public BooleanUnaryExpression(Expression left) + { + super(left); + } + + public boolean matches(Filterable message) + { + Object object = evaluate(message); + + return (object != null) && (object == Boolean.TRUE); + } + } + ; + + public static<E extends Exception> BooleanExpression createNOT(BooleanExpression left) + { + return new NotExpression(left); + } + + public static BooleanExpression createXPath(final String xpath) + { + return new XPathExpression(xpath); + } + + public static BooleanExpression createXQuery(final String xpath) + { + return new XQueryExpression(xpath); + } + + public static<E extends Exception> BooleanExpression createBooleanCast(Expression left) + { + return new BooleanCastExpression(left); + } + + private static Number negate(Number left) + { + Class clazz = left.getClass(); + if (clazz == Integer.class) + { + return -left.intValue(); + } + else if (clazz == Long.class) + { + return -left.longValue(); + } + else if (clazz == Float.class) + { + return -left.floatValue(); + } + else if (clazz == Double.class) + { + return -left.doubleValue(); + } + else if (clazz == BigDecimal.class) + { + // We ussually get a big deciamal when we have Long.MIN_VALUE constant in the + // Selector. Long.MIN_VALUE is too big to store in a Long as a positive so we store it + // as a Big decimal. But it gets Negated right away.. to here we try to covert it back + // to a Long. + BigDecimal bd = (BigDecimal) left; + bd = bd.negate(); + + if (BD_LONG_MIN_VALUE.compareTo(bd) == 0) + { + return Long.MIN_VALUE; + } + + return bd; + } + else + { + throw new RuntimeException("Don't know how to negate: " + left); + } + } + + public UnaryExpression(Expression left) + { + this.right = left; + } + + public Expression getRight() + { + return right; + } + + public void setRight(Expression expression) + { + right = expression; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() + { + return "(" + getExpressionSymbol() + " " + right.toString() + ")"; + } + + /** + * TODO: more efficient hashCode() + * + * @see java.lang.Object#hashCode() + */ + public int hashCode() + { + return toString().hashCode(); + } + + /** + * TODO: more efficient hashCode() + * + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object o) + { + return ((o != null) && this.getClass().equals(o.getClass())) && toString().equals(o.toString()); + } + + /** + * Returns the symbol that represents this binary expression. For example, addition is + * represented by "+" + * + * @return + */ + public abstract String getExpressionSymbol(); + + private static class NegativeExpression extends UnaryExpression + { + public NegativeExpression(final Expression left) + { + super(left); + } + + public Object evaluate(Filterable message) + { + Object rvalue = right.evaluate(message); + if (rvalue == null) + { + return null; + } + + if (rvalue instanceof Number) + { + return negate((Number) rvalue); + } + + return null; + } + + public String getExpressionSymbol() + { + return "-"; + } + } + + private static class InExpression extends BooleanUnaryExpression + { + private final Collection _inList; + private final boolean _not; + + public InExpression(final PropertyExpression right, final Collection inList, final boolean not) + { + super(right); + _inList = inList; + _not = not; + } + + public Object evaluate(Filterable message) + { + + Object rvalue = right.evaluate(message); + if (rvalue == null) + { + return null; + } + + if (rvalue.getClass() != String.class) + { + return null; + } + + if (((_inList != null) && _inList.contains(rvalue)) ^ _not) + { + return Boolean.TRUE; + } + else + { + return Boolean.FALSE; + } + + } + + public String toString() + { + StringBuffer answer = new StringBuffer(); + answer.append(right); + answer.append(" "); + answer.append(getExpressionSymbol()); + answer.append(" ( "); + + int count = 0; + for (Iterator i = _inList.iterator(); i.hasNext();) + { + Object o = (Object) i.next(); + if (count != 0) + { + answer.append(", "); + } + + answer.append(o); + count++; + } + + answer.append(" )"); + + return answer.toString(); + } + + public String getExpressionSymbol() + { + if (_not) + { + return "NOT IN"; + } + else + { + return "IN"; + } + } + } + + private static class NotExpression extends BooleanUnaryExpression + { + public NotExpression(final BooleanExpression left) + { + super(left); + } + + public Object evaluate(Filterable message) + { + Boolean lvalue = (Boolean) right.evaluate(message); + if (lvalue == null) + { + return null; + } + + return lvalue.booleanValue() ? Boolean.FALSE : Boolean.TRUE; + } + + public String getExpressionSymbol() + { + return "NOT"; + } + } + + private static class BooleanCastExpression extends BooleanUnaryExpression + { + public BooleanCastExpression(final Expression left) + { + super(left); + } + + public Object evaluate(Filterable message) + { + Object rvalue = right.evaluate(message); + if (rvalue == null) + { + return null; + } + + if (!rvalue.getClass().equals(Boolean.class)) + { + return Boolean.FALSE; + } + + return ((Boolean) rvalue).booleanValue() ? Boolean.TRUE : Boolean.FALSE; + } + + public String toString() + { + return right.toString(); + } + + public String getExpressionSymbol() + { + return ""; + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XPathExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XPathExpression.java new file mode 100644 index 0000000000..aa35cb5a76 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XPathExpression.java @@ -0,0 +1,126 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.qpid.server.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.Filterable; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +/** + * Used to evaluate an XPath Expression in a JMS selector. + */ +public final class XPathExpression implements BooleanExpression { + + private static final Logger log = Logger.getLogger(XPathExpression.class); + private static final String EVALUATOR_SYSTEM_PROPERTY = "org.apache.qpid.server.filter.XPathEvaluatorClassName"; + private static final String DEFAULT_EVALUATOR_CLASS_NAME=XalanXPathEvaluator.class.getName(); + + private static final Constructor EVALUATOR_CONSTRUCTOR; + + static { + String cn = System.getProperty(EVALUATOR_SYSTEM_PROPERTY, DEFAULT_EVALUATOR_CLASS_NAME); + Constructor m = null; + try { + try { + m = getXPathEvaluatorConstructor(cn); + } catch (Throwable e) { + log.warn("Invalid "+XPathEvaluator.class.getName()+" implementation: "+cn+", reason: "+e,e); + cn = DEFAULT_EVALUATOR_CLASS_NAME; + try { + m = getXPathEvaluatorConstructor(cn); + } catch (Throwable e2) { + log.error("Default XPath evaluator could not be loaded",e); + } + } + } finally { + EVALUATOR_CONSTRUCTOR = m; + } + } + + private static Constructor getXPathEvaluatorConstructor(String cn) throws ClassNotFoundException, SecurityException, NoSuchMethodException { + Class c = XPathExpression.class.getClassLoader().loadClass(cn); + if( !XPathEvaluator.class.isAssignableFrom(c) ) { + throw new ClassCastException(""+c+" is not an instance of "+XPathEvaluator.class); + } + return c.getConstructor(new Class[]{String.class}); + } + + private final String xpath; + private final XPathEvaluator evaluator; + + static public interface XPathEvaluator { + public boolean evaluate(Filterable message); + } + + XPathExpression(String xpath) { + this.xpath = xpath; + this.evaluator = createEvaluator(xpath); + } + + private XPathEvaluator createEvaluator(String xpath2) { + try { + return (XPathEvaluator)EVALUATOR_CONSTRUCTOR.newInstance(new Object[]{xpath}); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if( cause instanceof RuntimeException ) { + throw (RuntimeException)cause; + } + throw new RuntimeException("Invalid XPath Expression: "+xpath+" reason: "+e.getMessage(), e); + } catch (Throwable e) { + throw new RuntimeException("Invalid XPath Expression: "+xpath+" reason: "+e.getMessage(), e); + } + } + + public Object evaluate(Filterable message) { +// try { +//FIXME this is flow to disk work +// if( message.isDropped() ) +// return null; + return evaluator.evaluate(message) ? Boolean.TRUE : Boolean.FALSE; +// } catch (IOException e) { +// +// JMSException exception = new JMSException(e.getMessage()); +// exception.initCause(e); +// throw exception; +// +// } + + } + + public String toString() { + return "XPATH "+ConstantExpression.encodeString(xpath); + } + + /** + * @param message + * @return true if the expression evaluates to Boolean.TRUE. + * @throws AMQException + */ + public boolean matches(Filterable message) + { + Object object = evaluate(message); + return object!=null && object==Boolean.TRUE; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XQueryExpression.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XQueryExpression.java new file mode 100644 index 0000000000..ae22f17413 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XQueryExpression.java @@ -0,0 +1,57 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.qpid.server.filter; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.queue.Filterable; + +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +/** + * Used to evaluate an XQuery Expression in a JMS selector. + */ +public final class XQueryExpression implements BooleanExpression { + private final String xpath; + + XQueryExpression(String xpath) { + super(); + this.xpath = xpath; + } + + public Object evaluate(Filterable message) { + return Boolean.FALSE; + } + + public String toString() { + return "XQUERY "+ConstantExpression.encodeString(xpath); + } + + /** + * @param message + * @return true if the expression evaluates to Boolean.TRUE. + * @throws AMQException + */ + public boolean matches(Filterable message) + { + Object object = evaluate(message); + return object!=null && object==Boolean.TRUE; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XalanXPathEvaluator.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XalanXPathEvaluator.java new file mode 100644 index 0000000000..f83eb63ac5 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/filter/XalanXPathEvaluator.java @@ -0,0 +1,101 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.qpid.server.filter; +// +// Based on like named file from r450141 of the Apache ActiveMQ project <http://www.activemq.org/site/home.html> +// + +import java.io.ByteArrayInputStream; +import java.io.StringReader; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.apache.qpid.server.queue.Filterable; +import org.apache.xpath.CachedXPathAPI; +import org.w3c.dom.Document; +import org.w3c.dom.traversal.NodeIterator; +import org.xml.sax.InputSource; + +public class XalanXPathEvaluator implements XPathExpression.XPathEvaluator { + + private final String xpath; + + public XalanXPathEvaluator(String xpath) { + this.xpath = xpath; + } + + public boolean evaluate(Filterable m) + { + // TODO - we would have to check the content type and then evaluate the content + // here... is this really a feature we wish to implement? - RobG + /* + + if( m instanceof TextMessage ) { + String text = ((TextMessage)m).getText(); + return evaluate(text); + } else if ( m instanceof BytesMessage ) { + BytesMessage bm = (BytesMessage) m; + byte data[] = new byte[(int) bm.getBodyLength()]; + bm.readBytes(data); + return evaluate(data); + } + */ + return false; + + } + + private boolean evaluate(byte[] data) { + try { + + InputSource inputSource = new InputSource(new ByteArrayInputStream(data)); + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + DocumentBuilder dbuilder = factory.newDocumentBuilder(); + Document doc = dbuilder.parse(inputSource); + + CachedXPathAPI cachedXPathAPI = new CachedXPathAPI(); + NodeIterator iterator = cachedXPathAPI.selectNodeIterator(doc,xpath); + return iterator.nextNode()!=null; + + } catch (Throwable e) { + return false; + } + } + + private boolean evaluate(String text) { + try { + InputSource inputSource = new InputSource(new StringReader(text)); + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + DocumentBuilder dbuilder = factory.newDocumentBuilder(); + Document doc = dbuilder.parse(inputSource); + + // We should associated the cachedXPathAPI object with the message being evaluated + // since that should speedup subsequent xpath expressions. + CachedXPathAPI cachedXPathAPI = new CachedXPathAPI(); + NodeIterator iterator = cachedXPathAPI.selectNodeIterator(doc,xpath); + return iterator.nextNode()!=null; + } catch (Throwable e) { + return false; + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/AbstractFlowCreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/AbstractFlowCreditManager.java new file mode 100644 index 0000000000..cfe5aedd61 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/AbstractFlowCreditManager.java @@ -0,0 +1,71 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.flow; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.Set; +import java.util.HashSet; + +public abstract class AbstractFlowCreditManager implements FlowCreditManager +{ + protected final AtomicBoolean _suspended = new AtomicBoolean(false); + private final Set<FlowCreditManagerListener> _listeners = new HashSet<FlowCreditManagerListener>(); + + public final void addStateListener(FlowCreditManagerListener listener) + { + synchronized(_listeners) + { + _listeners.add(listener); + } + } + + public final boolean removeListener(FlowCreditManagerListener listener) + { + synchronized(_listeners) + { + return _listeners.remove(listener); + } + } + + private void notifyListeners(final boolean suspended) + { + synchronized(_listeners) + { + for(FlowCreditManagerListener listener : _listeners) + { + listener.creditStateChanged(!suspended); + } + } + } + + protected final void setSuspended(final boolean suspended) + { + if(_suspended.compareAndSet(!suspended, suspended)) + { + notifyListeners(suspended); + } + } + + protected final void notifyIncreaseBytesCredit() + { + notifyListeners(false); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/BytesOnlyCreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/BytesOnlyCreditManager.java new file mode 100644 index 0000000000..c5f2d1e808 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/BytesOnlyCreditManager.java @@ -0,0 +1,89 @@ +package org.apache.qpid.server.flow; + +import org.apache.qpid.server.message.ServerMessage; + +import java.util.concurrent.atomic.AtomicLong; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ +public class BytesOnlyCreditManager extends AbstractFlowCreditManager +{ + private final AtomicLong _bytesCredit; + + public BytesOnlyCreditManager(long initialCredit) + { + _bytesCredit = new AtomicLong(initialCredit); + } + + public long getMessageCredit() + { + return -1L; + } + + public long getBytesCredit() + { + return _bytesCredit.get(); + } + + public void restoreCredit(long messageCredit, long bytesCredit) + { + _bytesCredit.addAndGet(bytesCredit); + setSuspended(false); + } + + public void removeAllCredit() + { + _bytesCredit.set(0L); + } + + public boolean hasCredit() + { + return _bytesCredit.get() > 0L; + } + + public boolean useCreditForMessage(ServerMessage msg) + { + final long msgSize = msg.getSize(); + if(hasCredit()) + { + if(_bytesCredit.addAndGet(-msgSize) >= 0) + { + return true; + } + else + { + _bytesCredit.addAndGet(msgSize); + setSuspended(true); + return false; + } + } + else + { + return false; + } + + } + + public void setBytesCredit(long bytesCredit) + { + _bytesCredit.set( bytesCredit ); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/CreditCreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/CreditCreditManager.java new file mode 100644 index 0000000000..b47f986155 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/CreditCreditManager.java @@ -0,0 +1,188 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.flow; + +import org.apache.qpid.server.message.ServerMessage; + +public class CreditCreditManager extends AbstractFlowCreditManager implements FlowCreditManager_0_10 +{ + private volatile long _bytesCredit; + private volatile long _messageCredit; + + + public CreditCreditManager() + { + this(0L, 0L); + } + + public CreditCreditManager(long bytesCredit, long messageCredit) + { + _bytesCredit = bytesCredit; + _messageCredit = messageCredit; + setSuspended(!hasCredit()); + + } + + + public synchronized void setCreditLimits(final long bytesCredit, final long messageCredit) + { + _bytesCredit = bytesCredit; + _messageCredit = messageCredit; + + setSuspended(!hasCredit()); + + } + + + public long getMessageCredit() + { + return _messageCredit == -1L + ? Long.MAX_VALUE + : _messageCredit; + } + + public long getBytesCredit() + { + return _bytesCredit == -1L + ? Long.MAX_VALUE + : _bytesCredit; + } + + public synchronized void restoreCredit(final long messageCredit, final long bytesCredit) + { + /*_bytesCredit = 0l; + _messageCredit = 0l; + setSuspended(true);*/ + } + + + public synchronized void addCredit(final long messageCredit, final long bytesCredit) + { + boolean notifyIncrease = true; + if(_messageCredit >= 0L && messageCredit > 0L) + { + notifyIncrease = _messageCredit != 0L; + _messageCredit += messageCredit; + } + + + + if(_bytesCredit >= 0L && bytesCredit > 0L) + { + notifyIncrease = notifyIncrease && bytesCredit>0; + _bytesCredit += bytesCredit; + + + + if(notifyIncrease) + { + notifyIncreaseBytesCredit(); + } + } + + + + setSuspended(!hasCredit()); + + } + + public void clearCredit() + { + _bytesCredit = 0l; + _messageCredit = 0l; + setSuspended(true); + } + + + public synchronized boolean hasCredit() + { + // Note !=, if credit is < 0 that indicates infinite credit + return (_bytesCredit != 0L && _messageCredit != 0L); + } + + public synchronized boolean useCreditForMessage(final ServerMessage msg) + { + if(_messageCredit >= 0L) + { + if(_messageCredit > 0) + { + if(_bytesCredit < 0L) + { + _messageCredit--; + + return true; + } + else if(msg.getSize() <= _bytesCredit) + { + _messageCredit--; + _bytesCredit -= msg.getSize(); + + return true; + } + else + { + //setSuspended(true); + return false; + } + } + else + { + setSuspended(true); + return false; + } + } + else if(_bytesCredit >= 0L) + { + if(msg.getSize() <= _bytesCredit) + { + _bytesCredit -= msg.getSize(); + + return true; + } + else + { + //setSuspended(true); + return false; + } + + } + else + { + return true; + } + + } + + public synchronized void stop() + { + if(_bytesCredit > 0) + { + _bytesCredit = 0; + } + if(_messageCredit > 0) + { + _messageCredit = 0; + } + + } + + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/FlowCreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/FlowCreditManager.java new file mode 100644 index 0000000000..bec51d361d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/FlowCreditManager.java @@ -0,0 +1,46 @@ +package org.apache.qpid.server.flow; + +import org.apache.qpid.server.message.ServerMessage; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ +public interface FlowCreditManager +{ + long getMessageCredit(); + + long getBytesCredit(); + + public static interface FlowCreditManagerListener + { + void creditStateChanged(boolean hasCredit); + } + + void addStateListener(FlowCreditManagerListener listener); + + boolean removeListener(FlowCreditManagerListener listener); + + public void restoreCredit(long messageCredit, long bytesCredit); + + public boolean hasCredit(); + + public boolean useCreditForMessage(ServerMessage msg); + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/FlowCreditManager_0_10.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/FlowCreditManager_0_10.java new file mode 100755 index 0000000000..48c336c0b1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/FlowCreditManager_0_10.java @@ -0,0 +1,28 @@ +package org.apache.qpid.server.flow; + +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ +public interface FlowCreditManager_0_10 extends FlowCreditManager +{ + public void addCredit(long count, long bytes); + + void clearCredit(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/LimitlessCreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/LimitlessCreditManager.java new file mode 100644 index 0000000000..901b71fd1f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/LimitlessCreditManager.java @@ -0,0 +1,54 @@ +package org.apache.qpid.server.flow; + +import org.apache.qpid.server.message.ServerMessage; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ +public class LimitlessCreditManager extends AbstractFlowCreditManager implements FlowCreditManager +{ + public long getMessageCredit() + { + return -1L; + } + + public long getBytesCredit() + { + return -1L; + } + + public void restoreCredit(long messageCredit, long bytesCredit) + { + } + + public void removeAllCredit() + { + } + + public boolean hasCredit() + { + return true; + } + + public boolean useCreditForMessage(ServerMessage msg) + { + return true; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/MessageAndBytesCreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/MessageAndBytesCreditManager.java new file mode 100644 index 0000000000..19a9ac1d23 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/MessageAndBytesCreditManager.java @@ -0,0 +1,92 @@ +package org.apache.qpid.server.flow; + +import org.apache.qpid.server.message.ServerMessage; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ +public class MessageAndBytesCreditManager extends AbstractFlowCreditManager implements FlowCreditManager +{ + private long _messageCredit; + private long _bytesCredit; + + public MessageAndBytesCreditManager(final long messageCredit, final long bytesCredit) + { + _messageCredit = messageCredit; + _bytesCredit = bytesCredit; + } + + public synchronized long getMessageCredit() + { + return _messageCredit; + } + + public synchronized long getBytesCredit() + { + return _bytesCredit; + } + + public synchronized void restoreCredit(long messageCredit, long bytesCredit) + { + _messageCredit += messageCredit; + _bytesCredit += bytesCredit; + setSuspended(hasCredit()); + } + + public synchronized void removeAllCredit() + { + _messageCredit = 0L; + _bytesCredit = 0L; + setSuspended(true); + } + + public synchronized boolean hasCredit() + { + return (_messageCredit > 0L) && ( _bytesCredit > 0L ); + } + + public synchronized boolean useCreditForMessage(ServerMessage msg) + { + if(_messageCredit == 0L) + { + setSuspended(true); + return false; + } + else + { + final long msgSize = msg.getSize(); + if(msgSize > _bytesCredit) + { + setSuspended(true); + return false; + } + _messageCredit--; + _bytesCredit -= msgSize; + setSuspended(false); + return true; + } + + } + + public synchronized void setBytesCredit(long bytesCredit) + { + _bytesCredit = bytesCredit; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/MessageOnlyCreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/MessageOnlyCreditManager.java new file mode 100644 index 0000000000..a386f66b11 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/MessageOnlyCreditManager.java @@ -0,0 +1,88 @@ +package org.apache.qpid.server.flow; + +import org.apache.qpid.server.message.ServerMessage; + +import java.util.concurrent.atomic.AtomicLong; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ +public class MessageOnlyCreditManager extends AbstractFlowCreditManager implements FlowCreditManager +{ + private final AtomicLong _messageCredit; + + public MessageOnlyCreditManager(final long initialCredit) + { + _messageCredit = new AtomicLong(initialCredit); + } + + public long getMessageCredit() + { + return _messageCredit.get(); + } + + public long getBytesCredit() + { + return -1L; + } + + public void restoreCredit(long messageCredit, long bytesCredit) + { + _messageCredit.addAndGet(messageCredit); + setSuspended(false); + + } + + public void removeAllCredit() + { + setSuspended(true); + _messageCredit.set(0L); + } + + public boolean hasCredit() + { + return _messageCredit.get() > 0L; + } + + public boolean useCreditForMessage(ServerMessage msg) + { + if(hasCredit()) + { + if(_messageCredit.addAndGet(-1L) >= 0) + { + setSuspended(false); + return true; + } + else + { + _messageCredit.addAndGet(1L); + setSuspended(true); + return false; + } + } + else + { + setSuspended(true); + return false; + } + + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/Pre0_10CreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/Pre0_10CreditManager.java new file mode 100644 index 0000000000..026804439c --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/Pre0_10CreditManager.java @@ -0,0 +1,195 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.flow; + +import org.apache.qpid.server.message.ServerMessage; + +public class Pre0_10CreditManager extends AbstractFlowCreditManager implements FlowCreditManager +{ + + private volatile long _bytesCreditLimit; + private volatile long _messageCreditLimit; + + private volatile long _bytesCredit; + private volatile long _messageCredit; + + public Pre0_10CreditManager(long bytesCreditLimit, long messageCreditLimit) + { + _bytesCreditLimit = bytesCreditLimit; + _messageCreditLimit = messageCreditLimit; + _bytesCredit = bytesCreditLimit; + _messageCredit = messageCreditLimit; + } + + + public synchronized void setCreditLimits(final long bytesCreditLimit, final long messageCreditLimit) + { + long bytesCreditChange = bytesCreditLimit - _bytesCreditLimit; + long messageCreditChange = messageCreditLimit - _messageCreditLimit; + + + + if(bytesCreditChange != 0L) + { + if(bytesCreditLimit == 0L) + { + _bytesCredit = 0; + } + else + { + _bytesCredit += bytesCreditChange; + } + } + + + if(messageCreditChange != 0L) + { + if(messageCreditLimit == 0L) + { + _messageCredit = 0; + } + else + { + _messageCredit += messageCreditChange; + } + } + + + _bytesCreditLimit = bytesCreditLimit; + _messageCreditLimit = messageCreditLimit; + + setSuspended(!hasCredit()); + + } + + + public long getMessageCredit() + { + return _messageCredit; + } + + public long getBytesCredit() + { + return _bytesCredit; + } + + public synchronized void restoreCredit(final long messageCredit, final long bytesCredit) + { + final long messageCreditLimit = _messageCreditLimit; + boolean notifyIncrease = true; + if(messageCreditLimit != 0L) + { + notifyIncrease = (_messageCredit != 0); + long newCredit = _messageCredit + messageCredit; + _messageCredit = newCredit > messageCreditLimit ? messageCreditLimit : newCredit; + } + + + final long bytesCreditLimit = _bytesCreditLimit; + if(bytesCreditLimit != 0L) + { + long newCredit = _bytesCredit + bytesCredit; + _bytesCredit = newCredit > bytesCreditLimit ? bytesCreditLimit : newCredit; + if(notifyIncrease && bytesCredit>0) + { + notifyIncreaseBytesCredit(); + } + } + + + + setSuspended(!hasCredit()); + + } + + public synchronized void removeAllCredit() + { + _bytesCredit = 0L; + _messageCredit = 0L; + setSuspended(!hasCredit()); + } + + public synchronized boolean hasCredit() + { + return (_bytesCreditLimit == 0L || _bytesCredit > 0) + && (_messageCreditLimit == 0L || _messageCredit > 0); + } + + public synchronized boolean useCreditForMessage(final ServerMessage msg) + { + if(_messageCreditLimit != 0L) + { + if(_messageCredit != 0L) + { + if(_bytesCreditLimit == 0L) + { + _messageCredit--; + + return true; + } + else + { + if((_bytesCredit >= msg.getSize()) || (_bytesCredit == _bytesCreditLimit)) + { + _messageCredit--; + _bytesCredit -= msg.getSize(); + + return true; + } + else + { + //setSuspended(true); + return false; + } + } + } + else + { + setSuspended(true); + return false; + } + } + else + { + if(_bytesCreditLimit == 0L) + { + + return true; + } + else + { + if((_bytesCredit >= msg.getSize()) || (_bytesCredit == _bytesCreditLimit)) + { + _bytesCredit -= msg.getSize(); + + return true; + } + else + { + //setSuspended(true); + return false; + } + } + + } + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/WindowCreditManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/WindowCreditManager.java new file mode 100644 index 0000000000..10f578551a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/flow/WindowCreditManager.java @@ -0,0 +1,213 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.flow; + +import org.apache.qpid.server.message.ServerMessage; + +public class WindowCreditManager extends AbstractFlowCreditManager implements FlowCreditManager_0_10 +{ + private volatile long _bytesCreditLimit; + private volatile long _messageCreditLimit; + + private volatile long _bytesUsed; + private volatile long _messageUsed; + + public WindowCreditManager() + { + this(0L, 0L); + } + + public WindowCreditManager(long bytesCreditLimit, long messageCreditLimit) + { + _bytesCreditLimit = bytesCreditLimit; + _messageCreditLimit = messageCreditLimit; + setSuspended(!hasCredit()); + + } + + + public synchronized void setCreditLimits(final long bytesCreditLimit, final long messageCreditLimit) + { + _bytesCreditLimit = bytesCreditLimit; + _messageCreditLimit = messageCreditLimit; + + setSuspended(!hasCredit()); + + } + + + public long getMessageCredit() + { + return _messageCreditLimit == -1L + ? Long.MAX_VALUE + : _messageUsed < _messageCreditLimit ? _messageCreditLimit - _messageUsed : 0L; + } + + public long getBytesCredit() + { + return _bytesCreditLimit == -1L + ? Long.MAX_VALUE + : _bytesUsed < _bytesCreditLimit ? _bytesCreditLimit - _bytesUsed : 0L; + } + + public synchronized void restoreCredit(final long messageCredit, final long bytesCredit) + { + boolean notifyIncrease = true; + if(_messageCreditLimit > 0L) + { + notifyIncrease = (_messageUsed != _messageCreditLimit); + _messageUsed -= messageCredit; + + //TODO log warning + if(_messageUsed < 0L) + { + _messageUsed = 0; + } + } + + + + if(_bytesCreditLimit > 0L) + { + notifyIncrease = notifyIncrease && bytesCredit>0; + _bytesUsed -= bytesCredit; + + //TODO log warning + if(_bytesUsed < 0L) + { + _bytesUsed = 0; + } + + if(notifyIncrease) + { + notifyIncreaseBytesCredit(); + } + } + + + + setSuspended(!hasCredit()); + + } + + + + public synchronized boolean hasCredit() + { + return (_bytesCreditLimit < 0L || _bytesCreditLimit > _bytesUsed) + && (_messageCreditLimit < 0L || _messageCreditLimit > _messageUsed); + } + + public synchronized boolean useCreditForMessage(final ServerMessage msg) + { + if(_messageCreditLimit >= 0L) + { + if(_messageUsed < _messageCreditLimit) + { + if(_bytesCreditLimit < 0L) + { + _messageUsed++; + + return true; + } + else if(_bytesUsed + msg.getSize() <= _bytesCreditLimit) + { + _messageUsed++; + _bytesUsed += msg.getSize(); + + return true; + } + else + { + //setSuspended(true); + return false; + } + } + else + { + setSuspended(true); + return false; + } + } + else if(_bytesCreditLimit >= 0L) + { + if(_bytesUsed + msg.getSize() <= _bytesCreditLimit) + { + _bytesUsed += msg.getSize(); + + return true; + } + else + { + //setSuspended(true); + return false; + } + + } + else + { + return true; + } + + } + + public void stop() + { + if(_bytesCreditLimit > 0) + { + _bytesCreditLimit = 0; + } + if(_messageCreditLimit > 0) + { + _messageCreditLimit = 0; + } + + } + + public synchronized void addCredit(long count, long bytes) + { + if(bytes > 0) + { + _bytesCreditLimit += bytes; + } + else if(bytes == -1) + { + _bytesCreditLimit = -1; + } + + + if(count > 0) + { + _messageCreditLimit += count; + } + else if(count == -1) + { + _messageCreditLimit = -1; + } + } + + public void clearCredit() + { + _bytesCreditLimit = 0l; + _messageCreditLimit = 0l; + setSuspended(true); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/AccessRequestHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/AccessRequestHandler.java new file mode 100644 index 0000000000..d587ef0c16 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/AccessRequestHandler.java @@ -0,0 +1,77 @@ +package org.apache.qpid.server.handler;
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ *
+ */
+
+
+import org.apache.qpid.framing.*;
+import org.apache.qpid.framing.amqp_8_0.MethodRegistry_8_0;
+import org.apache.qpid.framing.amqp_0_9.MethodRegistry_0_9;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.protocol.AMQConstant;
+
+/**
+ * @author Apache Software Foundation
+ *
+ *
+ */
+public class AccessRequestHandler implements StateAwareMethodListener<AccessRequestBody>
+{
+ private static final AccessRequestHandler _instance = new AccessRequestHandler();
+
+
+ public static AccessRequestHandler getInstance()
+ {
+ return _instance;
+ }
+
+ private AccessRequestHandler()
+ {
+ }
+
+ public void methodReceived(AMQStateManager stateManager, AccessRequestBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+ MethodRegistry methodRegistry = session.getMethodRegistry();
+
+ // We don't implement access control class, but to keep clients happy that expect it
+ // always use the "0" ticket.
+ AccessRequestOkBody response;
+ if(methodRegistry instanceof MethodRegistry_0_9)
+ {
+ response = ((MethodRegistry_0_9)methodRegistry).createAccessRequestOkBody(0);
+ }
+ else if(methodRegistry instanceof MethodRegistry_8_0)
+ {
+ response = ((MethodRegistry_8_0)methodRegistry).createAccessRequestOkBody(0);
+ }
+ else
+ {
+ throw new AMQException(AMQConstant.COMMAND_INVALID, "AccessRequest not present in AMQP versions other than 0-8, 0-9");
+ }
+
+
+ session.writeFrame(response.generateFrame(channelId));
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicAckMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicAckMethodHandler.java new file mode 100644 index 0000000000..f90e7c3dff --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicAckMethodHandler.java @@ -0,0 +1,67 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicAckBody; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class BasicAckMethodHandler implements StateAwareMethodListener<BasicAckBody> +{ + private static final Logger _log = Logger.getLogger(BasicAckMethodHandler.class); + + private static final BasicAckMethodHandler _instance = new BasicAckMethodHandler(); + + public static BasicAckMethodHandler getInstance() + { + return _instance; + } + + private BasicAckMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, BasicAckBody body, int channelId) throws AMQException + { + AMQProtocolSession protocolSession = stateManager.getProtocolSession(); + + + if (_log.isDebugEnabled()) + { + _log.debug("Ack(Tag:" + body.getDeliveryTag() + ":Mult:" + body.getMultiple() + ") received on channel " + channelId); + } + + final AMQChannel channel = protocolSession.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + // this method throws an AMQException if the delivery tag is not known + channel.acknowledgeMessage(body.getDeliveryTag(), body.getMultiple()); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicCancelMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicCancelMethodHandler.java new file mode 100644 index 0000000000..29054f55c1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicCancelMethodHandler.java @@ -0,0 +1,74 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicCancelBody; +import org.apache.qpid.framing.BasicCancelOkBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.log4j.Logger; + +public class BasicCancelMethodHandler implements StateAwareMethodListener<BasicCancelBody> +{ + private static final Logger _log = Logger.getLogger(BasicCancelMethodHandler.class); + + private static final BasicCancelMethodHandler _instance = new BasicCancelMethodHandler(); + + public static BasicCancelMethodHandler getInstance() + { + return _instance; + } + + private BasicCancelMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, BasicCancelBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + final AMQChannel channel = session.getChannel(channelId); + + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + if (_log.isDebugEnabled()) + { + _log.debug("BasicCancel: for:" + body.getConsumerTag() + + " nowait:" + body.getNowait()); + } + + channel.unsubscribeConsumer(body.getConsumerTag()); + if (!body.getNowait()) + { + MethodRegistry methodRegistry = session.getMethodRegistry(); + BasicCancelOkBody cancelOkBody = methodRegistry.createBasicCancelOkBody(body.getConsumerTag()); + session.writeFrame(cancelOkBody.generateFrame(channelId)); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java new file mode 100644 index 0000000000..a5999711bc --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicConsumeMethodHandler.java @@ -0,0 +1,173 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.*; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class BasicConsumeMethodHandler implements StateAwareMethodListener<BasicConsumeBody> +{ + private static final Logger _logger = Logger.getLogger(BasicConsumeMethodHandler.class); + + private static final BasicConsumeMethodHandler _instance = new BasicConsumeMethodHandler(); + + public static BasicConsumeMethodHandler getInstance() + { + return _instance; + } + + private BasicConsumeMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, BasicConsumeBody body, int channelId) throws AMQException + { + AMQProtocolSession protocolConnection = stateManager.getProtocolSession(); + + AMQChannel channel = protocolConnection.getChannel(channelId); + + VirtualHost vHost = protocolConnection.getVirtualHost(); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + else + { + if (_logger.isDebugEnabled()) + { + _logger.debug("BasicConsume: from '" + body.getQueue() + + "' for:" + body.getConsumerTag() + + " nowait:" + body.getNowait() + + " args:" + body.getArguments()); + } + + AMQQueue queue = body.getQueue() == null ? channel.getDefaultQueue() : vHost.getQueueRegistry().getQueue(body.getQueue().intern()); + + if (queue == null) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("No queue for '" + body.getQueue() + "'"); + } + if (body.getQueue() != null) + { + String msg = "No such queue, '" + body.getQueue() + "'"; + throw body.getChannelException(AMQConstant.NOT_FOUND, msg); + } + else + { + String msg = "No queue name provided, no default queue defined."; + throw body.getConnectionException(AMQConstant.NOT_ALLOWED, msg); + } + } + else + { + final AMQShortString consumerTagName; + + if (queue.isExclusive() && !queue.isDurable()) + { + AMQSessionModel session = queue.getExclusiveOwningSession(); + if (session == null || session.getConnectionModel() != protocolConnection) + { + throw body.getConnectionException(AMQConstant.NOT_ALLOWED, + "Queue " + queue.getNameShortString() + " is exclusive, but not created on this Connection."); + } + } + + if (body.getConsumerTag() != null) + { + consumerTagName = body.getConsumerTag().intern(); + } + else + { + consumerTagName = null; + } + + try + { + if(consumerTagName == null || channel.getSubscription(consumerTagName) == null) + { + + AMQShortString consumerTag = channel.subscribeToQueue(consumerTagName, queue, !body.getNoAck(), + body.getArguments(), body.getNoLocal(), body.getExclusive()); + if (!body.getNowait()) + { + MethodRegistry methodRegistry = protocolConnection.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createBasicConsumeOkBody(consumerTag); + protocolConnection.writeFrame(responseBody.generateFrame(channelId)); + + } + } + else + { + AMQShortString msg = new AMQShortString("Non-unique consumer tag, '" + body.getConsumerTag() + "'"); + + MethodRegistry methodRegistry = protocolConnection.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createConnectionCloseBody(AMQConstant.NOT_ALLOWED.getCode(), // replyCode + msg, // replytext + body.getClazz(), + body.getMethod()); + protocolConnection.writeFrame(responseBody.generateFrame(0)); + } + + } + catch (org.apache.qpid.AMQInvalidArgumentException ise) + { + _logger.debug("Closing connection due to invalid selector"); + + MethodRegistry methodRegistry = protocolConnection.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createChannelCloseBody(AMQConstant.INVALID_ARGUMENT.getCode(), + new AMQShortString(ise.getMessage()), + body.getClazz(), + body.getMethod()); + protocolConnection.writeFrame(responseBody.generateFrame(channelId)); + + + } + catch (AMQQueue.ExistingExclusiveSubscription e) + { + throw body.getChannelException(AMQConstant.ACCESS_REFUSED, + "Cannot subscribe to queue " + + queue.getNameShortString() + + " as it already has an existing exclusive consumer"); + } + catch (AMQQueue.ExistingSubscriptionPreventsExclusive e) + { + throw body.getChannelException(AMQConstant.ACCESS_REFUSED, + "Cannot subscribe to queue " + + queue.getNameShortString() + + " exclusively as it already has a consumer"); + } + + } + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicGetMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicGetMethodHandler.java new file mode 100644 index 0000000000..790027f293 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicGetMethodHandler.java @@ -0,0 +1,206 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicGetBody; +import org.apache.qpid.framing.BasicGetEmptyBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.message.AMQMessage; +import org.apache.qpid.server.flow.FlowCreditManager; +import org.apache.qpid.server.flow.MessageOnlyCreditManager; +import org.apache.qpid.server.subscription.SubscriptionImpl; +import org.apache.qpid.server.subscription.ClientDeliveryMethod; +import org.apache.qpid.server.subscription.RecordDeliveryMethod; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.subscription.SubscriptionFactoryImpl; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class BasicGetMethodHandler implements StateAwareMethodListener<BasicGetBody> +{ + private static final Logger _log = Logger.getLogger(BasicGetMethodHandler.class); + + private static final BasicGetMethodHandler _instance = new BasicGetMethodHandler(); + + public static BasicGetMethodHandler getInstance() + { + return _instance; + } + + private BasicGetMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, BasicGetBody body, int channelId) throws AMQException + { + AMQProtocolSession protocolConnection = stateManager.getProtocolSession(); + + + VirtualHost vHost = protocolConnection.getVirtualHost(); + + AMQChannel channel = protocolConnection.getChannel(channelId); + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + else + { + AMQQueue queue = body.getQueue() == null ? channel.getDefaultQueue() : vHost.getQueueRegistry().getQueue(body.getQueue()); + if (queue == null) + { + _log.info("No queue for '" + body.getQueue() + "'"); + if(body.getQueue()!=null) + { + throw body.getConnectionException(AMQConstant.NOT_FOUND, + "No such queue, '" + body.getQueue()+ "'"); + } + else + { + throw body.getConnectionException(AMQConstant.NOT_ALLOWED, + "No queue name provided, no default queue defined."); + } + } + else + { + if (queue.isExclusive()) + { + AMQSessionModel session = queue.getExclusiveOwningSession(); + if (session == null || session.getConnectionModel() != protocolConnection) + { + throw body.getConnectionException(AMQConstant.NOT_ALLOWED, + "Queue is exclusive, but not created on this Connection."); + } + } + + if (!performGet(queue,protocolConnection, channel, !body.getNoAck())) + { + MethodRegistry methodRegistry = protocolConnection.getMethodRegistry(); + // TODO - set clusterId + BasicGetEmptyBody responseBody = methodRegistry.createBasicGetEmptyBody(null); + + + protocolConnection.writeFrame(responseBody.generateFrame(channelId)); + } + } + } + } + + public static boolean performGet(final AMQQueue queue, + final AMQProtocolSession session, + final AMQChannel channel, + final boolean acks) + throws AMQException + { + + final FlowCreditManager singleMessageCredit = new MessageOnlyCreditManager(1L); + + final ClientDeliveryMethod getDeliveryMethod = new ClientDeliveryMethod() + { + + int _msg; + + public void deliverToClient(final Subscription sub, final QueueEntry entry, final long deliveryTag) + throws AMQException + { + singleMessageCredit.useCreditForMessage(entry.getMessage()); + if(entry.getMessage() instanceof AMQMessage) + { + session.getProtocolOutputConverter().writeGetOk(entry, channel.getChannelId(), + deliveryTag, queue.getMessageCount()); + } + else + { + //TODO Convert AMQP 0-10 message + throw new AMQException(AMQConstant.NOT_IMPLEMENTED, "Not implemented conversion of 0-10 message", null); + } + + } + }; + final RecordDeliveryMethod getRecordMethod = new RecordDeliveryMethod() + { + + public void recordMessageDelivery(final Subscription sub, final QueueEntry entry, final long deliveryTag) + { + channel.addUnacknowledgedMessage(entry, deliveryTag, null); + } + }; + + Subscription sub; + if(acks) + { + sub = SubscriptionFactoryImpl.INSTANCE.createSubscription(channel, session, null, acks, null, false, singleMessageCredit, getDeliveryMethod, getRecordMethod); + } + else + { + sub = new GetNoAckSubscription(channel, + session, + null, + null, + false, + singleMessageCredit, + getDeliveryMethod, + getRecordMethod); + } + + queue.registerSubscription(sub,false); + queue.flushSubscription(sub); + queue.unregisterSubscription(sub); + return(!singleMessageCredit.hasCredit()); + + + } + + public static final class GetNoAckSubscription extends SubscriptionImpl.NoAckSubscription + { + public GetNoAckSubscription(AMQChannel channel, AMQProtocolSession protocolSession, + AMQShortString consumerTag, FieldTable filters, + boolean noLocal, FlowCreditManager creditManager, + ClientDeliveryMethod deliveryMethod, + RecordDeliveryMethod recordMethod) + throws AMQException + { + super(channel, protocolSession, consumerTag, filters, noLocal, creditManager, deliveryMethod, recordMethod); + } + + public boolean isTransient() + { + return true; + } + + public boolean wouldSuspend(QueueEntry msg) + { + return !getCreditManager().useCreditForMessage(msg.getMessage()); + } + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java new file mode 100644 index 0000000000..8f23b1c4d4 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicPublishMethodHandler.java @@ -0,0 +1,96 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.exchange.ExchangeDefaults; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicPublishBody; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class BasicPublishMethodHandler implements StateAwareMethodListener<BasicPublishBody> +{ + private static final Logger _logger = Logger.getLogger(BasicPublishMethodHandler.class); + + private static final BasicPublishMethodHandler _instance = new BasicPublishMethodHandler(); + + + public static BasicPublishMethodHandler getInstance() + { + return _instance; + } + + private BasicPublishMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, BasicPublishBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + if (_logger.isDebugEnabled()) + { + _logger.debug("Publish received on channel " + channelId); + } + + AMQShortString exchangeName = body.getExchange(); + // TODO: check the delivery tag field details - is it unique across the broker or per subscriber? + if (exchangeName == null) + { + exchangeName = ExchangeDefaults.DEFAULT_EXCHANGE_NAME; + } + + VirtualHost vHost = session.getVirtualHost(); + Exchange exch = vHost.getExchangeRegistry().getExchange(exchangeName); + // if the exchange does not exist we raise a channel exception + if (exch == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "Unknown exchange name"); + } + else + { + // The partially populated BasicDeliver frame plus the received route body + // is stored in the channel. Once the final body frame has been received + // it is routed to the exchange. + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + MessagePublishInfo info = session.getMethodRegistry().getProtocolVersionMethodConverter().convertToInfo(body); + info.setExchange(exchangeName); + channel.setPublishFrame(info, exch); + } + } + +} + + + diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicQosHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicQosHandler.java new file mode 100644 index 0000000000..dd3281c65f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicQosHandler.java @@ -0,0 +1,58 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicQosBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.AMQChannel; + +public class BasicQosHandler implements StateAwareMethodListener<BasicQosBody> +{ + private static final BasicQosHandler _instance = new BasicQosHandler(); + + public static BasicQosHandler getInstance() + { + return _instance; + } + + public void methodReceived(AMQStateManager stateManager, BasicQosBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + AMQChannel channel = session.getChannel(channelId); + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + channel.setCredit(body.getPrefetchSize(), body.getPrefetchCount()); + + + MethodRegistry methodRegistry = session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createBasicQosOkBody(); + session.writeFrame(responseBody.generateFrame(channelId)); + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java new file mode 100644 index 0000000000..c7842cd643 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverMethodHandler.java @@ -0,0 +1,73 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicRecoverBody; +import org.apache.qpid.framing.BasicRecoverOkBody; +import org.apache.qpid.framing.ProtocolVersion; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.amqp_8_0.MethodRegistry_8_0; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class BasicRecoverMethodHandler implements StateAwareMethodListener<BasicRecoverBody> +{ + private static final Logger _logger = Logger.getLogger(BasicRecoverMethodHandler.class); + + private static final BasicRecoverMethodHandler _instance = new BasicRecoverMethodHandler(); + + public static BasicRecoverMethodHandler getInstance() + { + return _instance; + } + + public void methodReceived(AMQStateManager stateManager, BasicRecoverBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + _logger.debug("Recover received on protocol session " + session + " and channel " + channelId); + AMQChannel channel = session.getChannel(channelId); + + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + channel.resend(body.getRequeue()); + + // Qpid 0-8 hacks a synchronous -ok onto recover. + // In Qpid 0-9 we create a separate sync-recover, sync-recover-ok pair to be "more" compliant + if(session.getProtocolVersion().equals(ProtocolVersion.v8_0)) + { + MethodRegistry_8_0 methodRegistry = (MethodRegistry_8_0) session.getMethodRegistry(); + AMQMethodBody recoverOk = methodRegistry.createBasicRecoverOkBody(); + session.writeFrame(recoverOk.generateFrame(channelId)); + + } + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverSyncMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverSyncMethodHandler.java new file mode 100644 index 0000000000..f9feada6fe --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRecoverSyncMethodHandler.java @@ -0,0 +1,83 @@ +package org.apache.qpid.server.handler;
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ *
+ */
+
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.framing.BasicRecoverBody;
+import org.apache.qpid.framing.ProtocolVersion;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.framing.BasicRecoverSyncBody;
+import org.apache.qpid.framing.amqp_0_91.MethodRegistry_0_91;
+import org.apache.qpid.framing.amqp_0_9.MethodRegistry_0_9;
+import org.apache.qpid.framing.amqp_8_0.MethodRegistry_8_0;
+import org.apache.qpid.server.state.StateAwareMethodListener;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.AMQException;
+
+public class BasicRecoverSyncMethodHandler implements StateAwareMethodListener<BasicRecoverSyncBody>
+{
+ private static final Logger _logger = Logger.getLogger(BasicRecoverSyncMethodHandler.class);
+
+ private static final BasicRecoverSyncMethodHandler _instance = new BasicRecoverSyncMethodHandler();
+
+ public static BasicRecoverSyncMethodHandler getInstance()
+ {
+ return _instance;
+ }
+
+ public void methodReceived(AMQStateManager stateManager, BasicRecoverSyncBody body, int channelId) throws AMQException
+ {
+ AMQProtocolSession session = stateManager.getProtocolSession();
+
+ _logger.debug("Recover received on protocol session " + session + " and channel " + channelId);
+ AMQChannel channel = session.getChannel(channelId);
+
+
+ if (channel == null)
+ {
+ throw body.getChannelNotFoundException(channelId);
+ }
+
+ channel.resend(body.getRequeue());
+
+ // Qpid 0-8 hacks a synchronous -ok onto recover.
+ // In Qpid 0-9 we create a separate sync-recover, sync-recover-ok pair to be "more" compliant
+ if(session.getProtocolVersion().equals(ProtocolVersion.v0_9))
+ {
+ MethodRegistry_0_9 methodRegistry = (MethodRegistry_0_9) session.getMethodRegistry();
+ AMQMethodBody recoverOk = methodRegistry.createBasicRecoverSyncOkBody();
+ session.writeFrame(recoverOk.generateFrame(channelId));
+
+ }
+ else if(session.getProtocolVersion().equals(ProtocolVersion.v0_91))
+ {
+ MethodRegistry_0_91 methodRegistry = (MethodRegistry_0_91) session.getMethodRegistry();
+ AMQMethodBody recoverOk = methodRegistry.createBasicRecoverSyncOkBody();
+ session.writeFrame(recoverOk.generateFrame(channelId));
+
+ }
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRejectMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRejectMethodHandler.java new file mode 100644 index 0000000000..62dd76f832 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/BasicRejectMethodHandler.java @@ -0,0 +1,125 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicRejectBody; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.log4j.Logger; + +public class BasicRejectMethodHandler implements StateAwareMethodListener<BasicRejectBody> +{ + private static final Logger _logger = Logger.getLogger(BasicRejectMethodHandler.class); + + private static BasicRejectMethodHandler _instance = new BasicRejectMethodHandler(); + + public static BasicRejectMethodHandler getInstance() + { + return _instance; + } + + private BasicRejectMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, BasicRejectBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + if (_logger.isDebugEnabled()) + { + _logger.debug("Rejecting:" + body.getDeliveryTag() + + ": Requeue:" + body.getRequeue() + + //": Resend:" + evt.getMethod().resend + + " on channel:" + channel.debugIdentity()); + } + + long deliveryTag = body.getDeliveryTag(); + + QueueEntry message = channel.getUnacknowledgedMessageMap().get(deliveryTag); + + if (message == null) + { + _logger.warn("Dropping reject request as message is null for tag:" + deliveryTag); +// throw evt.getMethod().getChannelException(AMQConstant.NOT_FOUND, "Delivery Tag(" + deliveryTag + ")not known"); + } + else + { + if (message.isQueueDeleted()) + { + _logger.warn("Message's Queue as already been purged, unable to Reject. " + + "Dropping message should use Dead Letter Queue"); + message = channel.getUnacknowledgedMessageMap().remove(deliveryTag); + if(message != null) + { + message.discard(); + } + //sendtoDeadLetterQueue(msg) + return; + } + + if (message.getMessage() == null) + { + _logger.warn("Message as already been purged, unable to Reject."); + return; + } + + + if (_logger.isDebugEnabled()) + { + _logger.debug("Rejecting: DT:" + deliveryTag + "-" + message.getMessage() + + ": Requeue:" + body.getRequeue() + + //": Resend:" + evt.getMethod().resend + + " on channel:" + channel.debugIdentity()); + } + + // If we haven't requested message to be resent to this consumer then reject it from ever getting it. + //if (!evt.getMethod().resend) + { + message.reject(); + } + + if (body.getRequeue()) + { + channel.requeue(deliveryTag); + } + else + { + _logger.warn("Dropping message as requeue not required and there is no dead letter queue"); + message = channel.getUnacknowledgedMessageMap().remove(deliveryTag); + //sendtoDeadLetterQueue(AMQMessage message) +// message.queue = channel.getDefaultDeadLetterQueue(); +// channel.requeue(deliveryTag); + } + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseHandler.java new file mode 100644 index 0000000000..9133cce6b7 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseHandler.java @@ -0,0 +1,77 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ChannelCloseBody; +import org.apache.qpid.framing.ChannelCloseOkBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.AMQChannel; + +public class ChannelCloseHandler implements StateAwareMethodListener<ChannelCloseBody> +{ + private static final Logger _logger = Logger.getLogger(ChannelCloseHandler.class); + + private static ChannelCloseHandler _instance = new ChannelCloseHandler(); + + public static ChannelCloseHandler getInstance() + { + return _instance; + } + + private ChannelCloseHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ChannelCloseBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + if (_logger.isInfoEnabled()) + { + _logger.info("Received channel close for id " + channelId + " citing class " + body.getClassId() + + " and method " + body.getMethodId()); + } + + + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getConnectionException(AMQConstant.CHANNEL_ERROR, "Trying to close unknown channel"); + } + + session.closeChannel(channelId); + // Client requested closure so we don't wait for ok we send it + stateManager.getProtocolSession().closeChannelOk(channelId); + + MethodRegistry methodRegistry = session.getMethodRegistry(); + ChannelCloseOkBody responseBody = methodRegistry.createChannelCloseOkBody(); + session.writeFrame(responseBody.generateFrame(channelId)); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseOkHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseOkHandler.java new file mode 100644 index 0000000000..a857490e7e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelCloseOkHandler.java @@ -0,0 +1,53 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ChannelCloseOkBody; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class ChannelCloseOkHandler implements StateAwareMethodListener<ChannelCloseOkBody> +{ + private static final Logger _logger = Logger.getLogger(ChannelCloseOkHandler.class); + + private static ChannelCloseOkHandler _instance = new ChannelCloseOkHandler(); + + public static ChannelCloseOkHandler getInstance() + { + return _instance; + } + + private ChannelCloseOkHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ChannelCloseOkBody body, int channelId) throws AMQException + { + + _logger.info("Received channel-close-ok for channel-id " + channelId); + + // Let the Protocol Session know the channel is now closed. + stateManager.getProtocolSession().closeChannelOk(channelId); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelFlowHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelFlowHandler.java new file mode 100644 index 0000000000..696ca8a63b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelFlowHandler.java @@ -0,0 +1,66 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.*; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class ChannelFlowHandler implements StateAwareMethodListener<ChannelFlowBody> +{ + private static final Logger _logger = Logger.getLogger(ChannelFlowHandler.class); + + private static ChannelFlowHandler _instance = new ChannelFlowHandler(); + + public static ChannelFlowHandler getInstance() + { + return _instance; + } + + private ChannelFlowHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ChannelFlowBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + channel.setSuspended(!body.getActive()); + _logger.debug("Channel.Flow for channel " + channelId + ", active=" + body.getActive()); + + MethodRegistry methodRegistry = session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createChannelFlowOkBody(body.getActive()); + session.writeFrame(responseBody.generateFrame(channelId)); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelOpenHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelOpenHandler.java new file mode 100644 index 0000000000..6d874ee971 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ChannelOpenHandler.java @@ -0,0 +1,141 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.UUID; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ChannelOpenBody; +import org.apache.qpid.framing.ChannelOpenOkBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.ProtocolVersion; +import org.apache.qpid.framing.amqp_0_9.MethodRegistry_0_9; +import org.apache.qpid.framing.amqp_0_91.MethodRegistry_0_91; +import org.apache.qpid.framing.amqp_8_0.MethodRegistry_8_0; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class ChannelOpenHandler implements StateAwareMethodListener<ChannelOpenBody> +{ + private static final Logger _logger = Logger.getLogger(ChannelOpenHandler.class); + + private static ChannelOpenHandler _instance = new ChannelOpenHandler(); + + public static ChannelOpenHandler getInstance() + { + return _instance; + } + + private ChannelOpenHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ChannelOpenBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + VirtualHost virtualHost = session.getVirtualHost(); + + // Protect the broker against out of order frame request. + if (virtualHost == null) + { + throw new AMQException(AMQConstant.COMMAND_INVALID, "Virtualhost has not yet been set. ConnectionOpen has not been called.", null); + } + _logger.info("Connecting to: " + virtualHost.getName()); + + final AMQChannel channel = new AMQChannel(session,channelId, virtualHost.getMessageStore()); + + session.addChannel(channel); + + ChannelOpenOkBody response; + + ProtocolVersion pv = session.getProtocolVersion(); + + if(pv.equals(ProtocolVersion.v8_0)) + { + MethodRegistry_8_0 methodRegistry = (MethodRegistry_8_0) MethodRegistry.getMethodRegistry(ProtocolVersion.v8_0); + response = methodRegistry.createChannelOpenOkBody(); + + } + else if(pv.equals(ProtocolVersion.v0_9)) + { + MethodRegistry_0_9 methodRegistry = (MethodRegistry_0_9) MethodRegistry.getMethodRegistry(ProtocolVersion.v0_9); + UUID uuid = UUID.randomUUID(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + DataOutputStream dataOut = new DataOutputStream(output); + try + { + dataOut.writeLong(uuid.getMostSignificantBits()); + dataOut.writeLong(uuid.getLeastSignificantBits()); + dataOut.flush(); + dataOut.close(); + } + catch (IOException e) + { + // This *really* shouldn't happen as we're not doing any I/O + throw new RuntimeException("I/O exception when writing to byte array", e); + } + + // should really associate this channelId to the session + byte[] channelName = output.toByteArray(); + + response = methodRegistry.createChannelOpenOkBody(channelName); + } + else if(pv.equals(ProtocolVersion.v0_91)) + { + MethodRegistry_0_91 methodRegistry = (MethodRegistry_0_91) MethodRegistry.getMethodRegistry(ProtocolVersion.v0_91); + UUID uuid = UUID.randomUUID(); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + DataOutputStream dataOut = new DataOutputStream(output); + try + { + dataOut.writeLong(uuid.getMostSignificantBits()); + dataOut.writeLong(uuid.getLeastSignificantBits()); + dataOut.flush(); + dataOut.close(); + } + catch (IOException e) + { + // This *really* shouldn't happen as we're not doing any I/O + throw new RuntimeException("I/O exception when writing to byte array", e); + } + + // should really associate this channelId to the session + byte[] channelName = output.toByteArray(); + + response = methodRegistry.createChannelOpenOkBody(channelName); + } + else + { + throw new AMQException(AMQConstant.INTERNAL_ERROR, "Got channel open for protocol version not catered for: " + pv, null); + } + + + session.writeFrame(response.generateFrame(channelId)); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.java new file mode 100644 index 0000000000..dade5d5f54 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseMethodHandler.java @@ -0,0 +1,72 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.ConnectionCloseBody; +import org.apache.qpid.framing.ConnectionCloseOkBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class ConnectionCloseMethodHandler implements StateAwareMethodListener<ConnectionCloseBody> +{ + private static final Logger _logger = Logger.getLogger(ConnectionCloseMethodHandler.class); + + private static ConnectionCloseMethodHandler _instance = new ConnectionCloseMethodHandler(); + + public static ConnectionCloseMethodHandler getInstance() + { + return _instance; + } + + private ConnectionCloseMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ConnectionCloseBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + if (_logger.isInfoEnabled()) + { + _logger.info("ConnectionClose received with reply code/reply text " + body.getReplyCode() + "/" + + body.getReplyText() + " for " + session); + } + try + { + session.closeSession(); + } + catch (Exception e) + { + _logger.error("Error closing protocol session: " + e, e); + } + + MethodRegistry methodRegistry = session.getMethodRegistry(); + ConnectionCloseOkBody responseBody = methodRegistry.createConnectionCloseOkBody(); + session.writeFrame(responseBody.generateFrame(channelId)); + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java new file mode 100644 index 0000000000..bc6e5ab403 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionCloseOkMethodHandler.java @@ -0,0 +1,63 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ConnectionCloseOkBody; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQState; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class ConnectionCloseOkMethodHandler implements StateAwareMethodListener<ConnectionCloseOkBody> +{ + private static final Logger _logger = Logger.getLogger(ConnectionCloseOkMethodHandler.class); + + private static ConnectionCloseOkMethodHandler _instance = new ConnectionCloseOkMethodHandler(); + + public static ConnectionCloseOkMethodHandler getInstance() + { + return _instance; + } + + private ConnectionCloseOkMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ConnectionCloseOkBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + //todo should this not do more than just log the method? + _logger.info("Received Connection-close-ok"); + + try + { + stateManager.changeState(AMQState.CONNECTION_CLOSED); + session.closeSession(); + } + catch (Exception e) + { + _logger.error("Error closing protocol session: " + e, e); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java new file mode 100644 index 0000000000..9a79467526 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionOpenMethodHandler.java @@ -0,0 +1,101 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ConnectionOpenBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQState; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class ConnectionOpenMethodHandler implements StateAwareMethodListener<ConnectionOpenBody> +{ + private static final Logger _logger = Logger.getLogger(ConnectionOpenMethodHandler.class); + + private static ConnectionOpenMethodHandler _instance = new ConnectionOpenMethodHandler(); + + public static ConnectionOpenMethodHandler getInstance() + { + return _instance; + } + + private ConnectionOpenMethodHandler() + { + } + + private static AMQShortString generateClientID() + { + return new AMQShortString(Long.toString(System.currentTimeMillis())); + } + + public void methodReceived(AMQStateManager stateManager, ConnectionOpenBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + //ignore leading '/' + String virtualHostName; + if ((body.getVirtualHost() != null) && body.getVirtualHost().charAt(0) == '/') + { + virtualHostName = new StringBuilder(body.getVirtualHost().subSequence(1, body.getVirtualHost().length())).toString(); + } + else + { + virtualHostName = body.getVirtualHost() == null ? null : String.valueOf(body.getVirtualHost()); + } + + VirtualHost virtualHost = stateManager.getVirtualHostRegistry().getVirtualHost(virtualHostName); + + if (virtualHost == null) + { + throw body.getConnectionException(AMQConstant.NOT_FOUND, "Unknown virtual host: '" + virtualHostName + "'"); + } + else + { + // Check virtualhost access + if (!virtualHost.getSecurityManager().accessVirtualhost(virtualHostName, session.getRemoteAddress())) + { + throw body.getConnectionException(AMQConstant.ACCESS_REFUSED, "Permission denied: '" + virtualHost.getName() + "'"); + } + + session.setVirtualHost(virtualHost); + + // See Spec (0.8.2). Section 3.1.2 Virtual Hosts + if (session.getContextKey() == null) + { + session.setContextKey(generateClientID()); + } + + MethodRegistry methodRegistry = session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createConnectionOpenOkBody(body.getVirtualHost()); + + stateManager.changeState(AMQState.CONNECTION_OPEN); + + session.writeFrame(responseBody.generateFrame(channelId)); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java new file mode 100644 index 0000000000..d4b79134a2 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionSecureOkMethodHandler.java @@ -0,0 +1,126 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ConnectionCloseBody; +import org.apache.qpid.framing.ConnectionSecureBody; +import org.apache.qpid.framing.ConnectionSecureOkBody; +import org.apache.qpid.framing.ConnectionTuneBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.state.AMQState; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class ConnectionSecureOkMethodHandler implements StateAwareMethodListener<ConnectionSecureOkBody> +{ + private static final Logger _logger = Logger.getLogger(ConnectionSecureOkMethodHandler.class); + + private static ConnectionSecureOkMethodHandler _instance = new ConnectionSecureOkMethodHandler(); + + public static ConnectionSecureOkMethodHandler getInstance() + { + return _instance; + } + + private ConnectionSecureOkMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ConnectionSecureOkBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + AuthenticationManager authMgr = ApplicationRegistry.getInstance().getAuthenticationManager(); + + SaslServer ss = session.getSaslServer(); + if (ss == null) + { + throw new AMQException("No SASL context set up in session"); + } + MethodRegistry methodRegistry = session.getMethodRegistry(); + AuthenticationResult authResult = authMgr.authenticate(ss, body.getResponse()); + switch (authResult.status) + { + case ERROR: + Exception cause = authResult.getCause(); + + _logger.info("Authentication failed:" + (cause == null ? "" : cause.getMessage())); + + // This should be abstracted + stateManager.changeState(AMQState.CONNECTION_CLOSING); + + ConnectionCloseBody connectionCloseBody = + methodRegistry.createConnectionCloseBody(AMQConstant.NOT_ALLOWED.getCode(), + AMQConstant.NOT_ALLOWED.getName(), + body.getClazz(), + body.getMethod()); + + session.writeFrame(connectionCloseBody.generateFrame(0)); + disposeSaslServer(session); + break; + case SUCCESS: + _logger.info("Connected as: " + ss.getAuthorizationID()); + stateManager.changeState(AMQState.CONNECTION_NOT_TUNED); + + ConnectionTuneBody tuneBody = + methodRegistry.createConnectionTuneBody(ApplicationRegistry.getInstance().getConfiguration().getMaxChannelCount(), + ConnectionStartOkMethodHandler.getConfiguredFrameSize(), + ApplicationRegistry.getInstance().getConfiguration().getHeartBeatDelay()); + session.writeFrame(tuneBody.generateFrame(0)); + session.setAuthorizedID(new UsernamePrincipal(ss.getAuthorizationID())); + disposeSaslServer(session); + break; + case CONTINUE: + stateManager.changeState(AMQState.CONNECTION_NOT_AUTH); + + ConnectionSecureBody secureBody = methodRegistry.createConnectionSecureBody(authResult.challenge); + session.writeFrame(secureBody.generateFrame(0)); + } + } + + private void disposeSaslServer(AMQProtocolSession ps) + { + SaslServer ss = ps.getSaslServer(); + if (ss != null) + { + ps.setSaslServer(null); + try + { + ss.dispose(); + } + catch (SaslException e) + { + _logger.error("Error disposing of Sasl server: " + e); + } + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java new file mode 100644 index 0000000000..4442f969c4 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionStartOkMethodHandler.java @@ -0,0 +1,162 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ConnectionCloseBody; +import org.apache.qpid.framing.ConnectionSecureBody; +import org.apache.qpid.framing.ConnectionStartOkBody; +import org.apache.qpid.framing.ConnectionTuneBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.auth.AuthenticationResult; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.state.AMQState; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + + +public class ConnectionStartOkMethodHandler implements StateAwareMethodListener<ConnectionStartOkBody> +{ + private static final Logger _logger = Logger.getLogger(ConnectionStartOkMethodHandler.class); + + private static ConnectionStartOkMethodHandler _instance = new ConnectionStartOkMethodHandler(); + + public static ConnectionStartOkMethodHandler getInstance() + { + return _instance; + } + + private ConnectionStartOkMethodHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ConnectionStartOkBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + _logger.info("SASL Mechanism selected: " + body.getMechanism()); + _logger.info("Locale selected: " + body.getLocale()); + + AuthenticationManager authMgr = ApplicationRegistry.getInstance().getAuthenticationManager(); + + SaslServer ss = null; + try + { + ss = authMgr.createSaslServer(String.valueOf(body.getMechanism()), session.getLocalFQDN()); + + if (ss == null) + { + throw body.getConnectionException(AMQConstant.RESOURCE_ERROR, "Unable to create SASL Server:" + body.getMechanism()); + } + + session.setSaslServer(ss); + + AuthenticationResult authResult = authMgr.authenticate(ss, body.getResponse()); + + //save clientProperties + if (session.getClientProperties() == null) + { + session.setClientProperties(body.getClientProperties()); + } + + MethodRegistry methodRegistry = session.getMethodRegistry(); + + switch (authResult.status) + { + case ERROR: + Exception cause = authResult.getCause(); + + _logger.info("Authentication failed:" + (cause == null ? "" : cause.getMessage())); + + stateManager.changeState(AMQState.CONNECTION_CLOSING); + + ConnectionCloseBody closeBody = + methodRegistry.createConnectionCloseBody(AMQConstant.NOT_ALLOWED.getCode(), // replyCode + AMQConstant.NOT_ALLOWED.getName(), + body.getClazz(), + body.getMethod()); + + session.writeFrame(closeBody.generateFrame(0)); + disposeSaslServer(session); + break; + + case SUCCESS: + _logger.info("Connected as: " + ss.getAuthorizationID()); + session.setAuthorizedID(new UsernamePrincipal(ss.getAuthorizationID())); + + stateManager.changeState(AMQState.CONNECTION_NOT_TUNED); + + ConnectionTuneBody tuneBody = methodRegistry.createConnectionTuneBody(ApplicationRegistry.getInstance().getConfiguration().getMaxChannelCount(), + getConfiguredFrameSize(), + ApplicationRegistry.getInstance().getConfiguration().getHeartBeatDelay()); + session.writeFrame(tuneBody.generateFrame(0)); + break; + case CONTINUE: + stateManager.changeState(AMQState.CONNECTION_NOT_AUTH); + + ConnectionSecureBody secureBody = methodRegistry.createConnectionSecureBody(authResult.challenge); + session.writeFrame(secureBody.generateFrame(0)); + } + } + catch (SaslException e) + { + disposeSaslServer(session); + throw new AMQException("SASL error: " + e, e); + } + } + + private void disposeSaslServer(AMQProtocolSession ps) + { + SaslServer ss = ps.getSaslServer(); + if (ss != null) + { + ps.setSaslServer(null); + try + { + ss.dispose(); + } + catch (SaslException e) + { + _logger.error("Error disposing of Sasl server: " + e); + } + } + } + + static int getConfiguredFrameSize() + { + final ServerConfiguration config = ApplicationRegistry.getInstance().getConfiguration(); + final int framesize = config.getFrameSize(); + _logger.info("Framesize set to " + framesize); + return framesize; + } +} + + + diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java new file mode 100644 index 0000000000..1da2760639 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ConnectionTuneOkMethodHandler.java @@ -0,0 +1,58 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ConnectionTuneOkBody; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQState; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class ConnectionTuneOkMethodHandler implements StateAwareMethodListener<ConnectionTuneOkBody> +{ + private static final Logger _logger = Logger.getLogger(ConnectionTuneOkMethodHandler.class); + + private static ConnectionTuneOkMethodHandler _instance = new ConnectionTuneOkMethodHandler(); + + public static ConnectionTuneOkMethodHandler getInstance() + { + return _instance; + } + + public void methodReceived(AMQStateManager stateManager, ConnectionTuneOkBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + if (_logger.isDebugEnabled()) + { + _logger.debug(body); + } + stateManager.changeState(AMQState.CONNECTION_NOT_OPENED); + session.initHeartbeats(body.getHeartbeat()); + session.setMaxFrameSize(body.getFrameMax()); + + long maxChannelNumber = body.getChannelMax(); + //0 means no implied limit, except that forced by protocol limitations (0xFFFF) + session.setMaximumNumberOfChannels( maxChannelNumber == 0 ? 0xFFFFL : maxChannelNumber); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeBoundHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeBoundHandler.java new file mode 100644 index 0000000000..ccd42204d9 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeBoundHandler.java @@ -0,0 +1,178 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.*; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +/** + * @author Apache Software Foundation + * + * + */ +public class ExchangeBoundHandler implements StateAwareMethodListener<ExchangeBoundBody> +{ + private static final ExchangeBoundHandler _instance = new ExchangeBoundHandler(); + + public static final int OK = 0; + + public static final int EXCHANGE_NOT_FOUND = 1; + + public static final int QUEUE_NOT_FOUND = 2; + + public static final int NO_BINDINGS = 3; + + public static final int QUEUE_NOT_BOUND = 4; + + public static final int NO_QUEUE_BOUND_WITH_RK = 5; + + public static final int SPECIFIC_QUEUE_NOT_BOUND_WITH_RK = 6; + + public static ExchangeBoundHandler getInstance() + { + return _instance; + } + + private ExchangeBoundHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ExchangeBoundBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + VirtualHost virtualHost = session.getVirtualHost(); + QueueRegistry queueRegistry = virtualHost.getQueueRegistry(); + MethodRegistry methodRegistry = session.getMethodRegistry(); + + + + + AMQShortString exchangeName = body.getExchange(); + AMQShortString queueName = body.getQueue(); + AMQShortString routingKey = body.getRoutingKey(); + if (exchangeName == null) + { + throw new AMQException("Exchange exchange must not be null"); + } + Exchange exchange = virtualHost.getExchangeRegistry().getExchange(exchangeName); + ExchangeBoundOkBody response; + if (exchange == null) + { + + + response = methodRegistry.createExchangeBoundOkBody(EXCHANGE_NOT_FOUND, + new AMQShortString("Exchange " + exchangeName + " not found")); + } + else if (routingKey == null) + { + if (queueName == null) + { + if (exchange.hasBindings()) + { + response = methodRegistry.createExchangeBoundOkBody(OK, null); + } + else + { + + response = methodRegistry.createExchangeBoundOkBody(NO_BINDINGS, // replyCode + null); // replyText + } + } + else + { + + AMQQueue queue = queueRegistry.getQueue(queueName); + if (queue == null) + { + + response = methodRegistry.createExchangeBoundOkBody(QUEUE_NOT_FOUND, // replyCode + new AMQShortString("Queue " + queueName + " not found")); // replyText + } + else + { + if (exchange.isBound(queue)) + { + + response = methodRegistry.createExchangeBoundOkBody(OK, // replyCode + null); // replyText + } + else + { + + response = methodRegistry.createExchangeBoundOkBody(QUEUE_NOT_BOUND, // replyCode + new AMQShortString("Queue " + queueName + " not bound to exchange " + exchangeName)); // replyText + } + } + } + } + else if (queueName != null) + { + AMQQueue queue = queueRegistry.getQueue(queueName); + if (queue == null) + { + + response = methodRegistry.createExchangeBoundOkBody(QUEUE_NOT_FOUND, // replyCode + new AMQShortString("Queue " + queueName + " not found")); // replyText + } + else + { + if (exchange.isBound(body.getRoutingKey(), queue)) + { + + response = methodRegistry.createExchangeBoundOkBody(OK, // replyCode + null); // replyText + } + else + { + + response = methodRegistry.createExchangeBoundOkBody(SPECIFIC_QUEUE_NOT_BOUND_WITH_RK, // replyCode + new AMQShortString("Queue " + queueName + " not bound with routing key " + + body.getRoutingKey() + " to exchange " + exchangeName)); // replyText + } + } + } + else + { + if (exchange.isBound(body.getRoutingKey())) + { + + response = methodRegistry.createExchangeBoundOkBody(OK, // replyCode + null); // replyText + } + else + { + + response = methodRegistry.createExchangeBoundOkBody(NO_QUEUE_BOUND_WITH_RK, // replyCode + new AMQShortString("No queue bound with routing key " + body.getRoutingKey() + + " to exchange " + exchangeName)); // replyText + } + } + session.writeFrame(response.generateFrame(channelId)); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java new file mode 100644 index 0000000000..98a0d33487 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeclareHandler.java @@ -0,0 +1,108 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQUnknownExchangeType; +import org.apache.qpid.framing.*; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class ExchangeDeclareHandler implements StateAwareMethodListener<ExchangeDeclareBody> +{ + private static final Logger _logger = Logger.getLogger(ExchangeDeclareHandler.class); + + private static final ExchangeDeclareHandler _instance = new ExchangeDeclareHandler(); + + public static ExchangeDeclareHandler getInstance() + { + return _instance; + } + + private ExchangeDeclareHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ExchangeDeclareBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + VirtualHost virtualHost = session.getVirtualHost(); + ExchangeRegistry exchangeRegistry = virtualHost.getExchangeRegistry(); + ExchangeFactory exchangeFactory = virtualHost.getExchangeFactory(); + + if (_logger.isDebugEnabled()) + { + _logger.debug("Request to declare exchange of type " + body.getType() + " with name " + body.getExchange()); + } + + synchronized(exchangeRegistry) + { + Exchange exchange = exchangeRegistry.getExchange(body.getExchange()); + + if (exchange == null) + { + if(body.getPassive() && ((body.getType() == null) || body.getType().length() ==0)) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "Unknown exchange: " + body.getExchange()); + } + else + { + try + { + exchange = exchangeFactory.createExchange(body.getExchange() == null ? null : body.getExchange().intern(), + body.getType() == null ? null : body.getType().intern(), + body.getDurable(), + body.getPassive(), body.getTicket()); + exchangeRegistry.registerExchange(exchange); + + if (exchange.isDurable()) + { + virtualHost.getDurableConfigurationStore().createExchange(exchange); + } + } + catch(AMQUnknownExchangeType e) + { + throw body.getConnectionException(AMQConstant.COMMAND_INVALID, "Unknown exchange: " + body.getExchange(),e); + } + } + } + else if (!exchange.getTypeShortString().equals(body.getType())) + { + + throw new AMQConnectionException(AMQConstant.NOT_ALLOWED, "Attempt to redeclare exchange: " + body.getExchange() + " of type " + exchange.getTypeShortString() + " to " + body.getType() +".",body.getClazz(), body.getMethod(),body.getMajor(),body.getMinor(),null); + } + } + if(!body.getNowait()) + { + MethodRegistry methodRegistry = session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createExchangeDeclareOkBody(); + session.writeFrame(responseBody.generateFrame(channelId)); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeleteHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeleteHandler.java new file mode 100644 index 0000000000..586aaf9336 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ExchangeDeleteHandler.java @@ -0,0 +1,71 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ExchangeDeleteBody; +import org.apache.qpid.framing.ExchangeDeleteOkBody; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.exchange.ExchangeInUseException; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class ExchangeDeleteHandler implements StateAwareMethodListener<ExchangeDeleteBody> +{ + private static final ExchangeDeleteHandler _instance = new ExchangeDeleteHandler(); + + public static ExchangeDeleteHandler getInstance() + { + return _instance; + } + + private ExchangeDeleteHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, ExchangeDeleteBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + VirtualHost virtualHost = session.getVirtualHost(); + ExchangeRegistry exchangeRegistry = virtualHost.getExchangeRegistry(); + + try + { + if(exchangeRegistry.getExchange(body.getExchange()) == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "No such exchange: " + body.getExchange()); + } + exchangeRegistry.unregisterExchange(body.getExchange(), body.getIfUnused()); + + ExchangeDeleteOkBody responseBody = session.getMethodRegistry().createExchangeDeleteOkBody(); + + session.writeFrame(responseBody.generateFrame(channelId)); + } + catch (ExchangeInUseException e) + { + throw body.getChannelException(AMQConstant.IN_USE, "Exchange in use"); + // TODO: sort out consistent channel close mechanism that does all clean up etc. + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java new file mode 100644 index 0000000000..ac516b6133 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/OnCurrentThreadExecutor.java @@ -0,0 +1,34 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import java.util.concurrent.Executor; + +/** + * An executor that executes the task on the current thread. + */ +public class OnCurrentThreadExecutor implements Executor +{ + public void execute(Runnable command) + { + command.run(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueBindHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueBindHandler.java new file mode 100644 index 0000000000..0eb69e4b16 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueBindHandler.java @@ -0,0 +1,159 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.QueueBindBody; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.Map; + +public class QueueBindHandler implements StateAwareMethodListener<QueueBindBody> +{ + private static final Logger _log = Logger.getLogger(QueueBindHandler.class); + + private static final QueueBindHandler _instance = new QueueBindHandler(); + + public static QueueBindHandler getInstance() + { + return _instance; + } + + private QueueBindHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueBindBody body, int channelId) throws AMQException + { + AMQProtocolSession protocolConnection = stateManager.getProtocolSession(); + VirtualHost virtualHost = protocolConnection.getVirtualHost(); + ExchangeRegistry exchangeRegistry = virtualHost.getExchangeRegistry(); + QueueRegistry queueRegistry = virtualHost.getQueueRegistry(); + + final AMQQueue queue; + final AMQShortString routingKey; + + if (body.getQueue() == null) + { + AMQChannel channel = protocolConnection.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + queue = channel.getDefaultQueue(); + + if (queue == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "No default queue defined on channel and queue was null"); + } + + if (body.getRoutingKey() == null) + { + routingKey = queue.getNameShortString(); + } + else + { + routingKey = body.getRoutingKey().intern(); + } + } + else + { + queue = queueRegistry.getQueue(body.getQueue()); + routingKey = body.getRoutingKey() == null ? AMQShortString.EMPTY_STRING : body.getRoutingKey().intern(); + } + + if (queue == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "Queue " + body.getQueue() + " does not exist."); + } + final Exchange exch = exchangeRegistry.getExchange(body.getExchange()); + if (exch == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "Exchange " + body.getExchange() + " does not exist."); + } + + + try + { + if (queue.isExclusive() && !queue.isDurable()) + { + AMQSessionModel session = queue.getExclusiveOwningSession(); + if (session == null || session.getConnectionModel() != protocolConnection) + { + throw body.getConnectionException(AMQConstant.NOT_ALLOWED, + "Queue " + queue.getNameShortString() + " is exclusive, but not created on this Connection."); + } + } + + if (!exch.isBound(routingKey, body.getArguments(), queue)) + { + String bindingKey = String.valueOf(routingKey); + Map<String,Object> arguments = FieldTable.convertToMap(body.getArguments()); + + if(!virtualHost.getBindingFactory().addBinding(bindingKey, queue, exch, arguments)) + { + Binding oldBinding = virtualHost.getBindingFactory().getBinding(bindingKey, queue, exch, arguments); + + Map<String, Object> oldArgs = oldBinding.getArguments(); + if((oldArgs == null && !arguments.isEmpty()) || (oldArgs != null && !oldArgs.equals(arguments))) + { + virtualHost.getBindingFactory().replaceBinding(bindingKey, queue, exch, arguments); + } + } + } + } + catch (AMQException e) + { + throw body.getChannelException(AMQConstant.CHANNEL_ERROR, e.toString()); + } + + if (_log.isInfoEnabled()) + { + _log.info("Binding queue " + queue + " to exchange " + exch + " with routing key " + routingKey); + } + if (!body.getNowait()) + { + MethodRegistry methodRegistry = protocolConnection.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createQueueBindOkBody(); + protocolConnection.writeFrame(responseBody.generateFrame(channelId)); + + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeclareHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeclareHandler.java new file mode 100644 index 0000000000..8939cfa334 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeclareHandler.java @@ -0,0 +1,254 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import java.util.UUID; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.QueueDeclareBody; +import org.apache.qpid.framing.QueueDeclareOkBody; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.store.DurableConfigurationStore; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class QueueDeclareHandler implements StateAwareMethodListener<QueueDeclareBody> +{ + private static final Logger _logger = Logger.getLogger(QueueDeclareHandler.class); + + private static final QueueDeclareHandler _instance = new QueueDeclareHandler(); + + public static QueueDeclareHandler getInstance() + { + return _instance; + } + + public boolean autoRegister = ApplicationRegistry.getInstance().getConfiguration().getQueueAutoRegister(); + + private final AtomicInteger _counter = new AtomicInteger(); + + public void methodReceived(AMQStateManager stateManager, QueueDeclareBody body, int channelId) throws AMQException + { + final AMQProtocolSession protocolConnection = stateManager.getProtocolSession(); + final AMQSessionModel session = protocolConnection.getChannel(channelId); + VirtualHost virtualHost = protocolConnection.getVirtualHost(); + ExchangeRegistry exchangeRegistry = virtualHost.getExchangeRegistry(); + QueueRegistry queueRegistry = virtualHost.getQueueRegistry(); + DurableConfigurationStore store = virtualHost.getDurableConfigurationStore(); + + final AMQShortString queueName; + + // if we aren't given a queue name, we create one which we return to the client + if ((body.getQueue() == null) || (body.getQueue().length() == 0)) + { + queueName = createName(); + } + else + { + queueName = body.getQueue().intern(); + } + + AMQQueue queue; + + //TODO: do we need to check that the queue already exists with exactly the same "configuration"? + + synchronized (queueRegistry) + { + queue = queueRegistry.getQueue(queueName); + + AMQSessionModel owningSession = null; + + if (queue != null) + { + owningSession = queue.getExclusiveOwningSession(); + } + + if (queue == null) + { + if (body.getPassive()) + { + String msg = "Queue: " + queueName + " not found on VirtualHost(" + virtualHost + ")."; + throw body.getChannelException(AMQConstant.NOT_FOUND, msg); + } + else + { + queue = createQueue(queueName, body, virtualHost, protocolConnection); + queue.setPrincipalHolder(protocolConnection); + if (queue.isDurable() && !queue.isAutoDelete()) + { + store.createQueue(queue, body.getArguments()); + } + if(body.getAutoDelete()) + { + queue.setDeleteOnNoConsumers(true); + } + queueRegistry.registerQueue(queue); + if (body.getExclusive()) + { + queue.setExclusiveOwningSession(protocolConnection.getChannel(channelId)); + queue.setPrincipalHolder(protocolConnection); + + if(!body.getDurable()) + { + final AMQQueue q = queue; + final AMQProtocolSession.Task sessionCloseTask = new AMQProtocolSession.Task() + { + public void doTask(AMQProtocolSession session) throws AMQException + { + q.setExclusiveOwningSession(null); + } + }; + protocolConnection.addSessionCloseTask(sessionCloseTask); + queue.addQueueDeleteTask(new AMQQueue.Task() { + public void doTask(AMQQueue queue) throws AMQException + { + protocolConnection.removeSessionCloseTask(sessionCloseTask); + } + }); + } + } + if (autoRegister) + { + Exchange defaultExchange = exchangeRegistry.getDefaultExchange(); + + virtualHost.getBindingFactory().addBinding(String.valueOf(queueName), queue, defaultExchange, Collections.EMPTY_MAP); + _logger.info("Queue " + queueName + " bound to default exchange(" + defaultExchange.getNameShortString() + ")"); + } + } + } + else if (queue.isExclusive() && !queue.isDurable() && (owningSession == null || owningSession.getConnectionModel() != protocolConnection)) + { + throw body.getConnectionException(AMQConstant.NOT_ALLOWED, + "Queue " + queue.getNameShortString() + " is exclusive, but not created on this Connection."); + } + else if(!body.getPassive() && ((queue.isExclusive()) != body.getExclusive())) + { + + throw body.getChannelException(AMQConstant.ALREADY_EXISTS, + "Cannot re-declare queue '" + queue.getNameShortString() + "' with different exclusivity (was: " + + queue.isExclusive() + " requested " + body.getExclusive() + ")"); + } + else if (!body.getPassive() && body.getExclusive() && !(queue.isDurable() ? String.valueOf(queue.getOwner()).equals(session.getClientID()) : (owningSession == null || owningSession.getConnectionModel() == protocolConnection))) + { + throw body.getChannelException(AMQConstant.ALREADY_EXISTS, "Cannot declare queue('" + queueName + "'), " + + "as exclusive queue with same name " + + "declared on another client ID('" + + queue.getOwner() + "') your clientID('" + session.getClientID() + "')"); + + } + else if(!body.getPassive() && queue.isAutoDelete() != body.getAutoDelete()) + { + throw body.getChannelException(AMQConstant.ALREADY_EXISTS, + "Cannot re-declare queue '" + queue.getNameShortString() + "' with different auto-delete (was: " + + queue.isAutoDelete() + " requested " + body.getAutoDelete() + ")"); + } + else if(!body.getPassive() && queue.isDurable() != body.getDurable()) + { + throw body.getChannelException(AMQConstant.ALREADY_EXISTS, + "Cannot re-declare queue '" + queue.getNameShortString() + "' with different durability (was: " + + queue.isDurable() + " requested " + body.getDurable() + ")"); + } + + + AMQChannel channel = protocolConnection.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + //set this as the default queue on the channel: + channel.setDefaultQueue(queue); + } + + if (!body.getNowait()) + { + MethodRegistry methodRegistry = protocolConnection.getMethodRegistry(); + QueueDeclareOkBody responseBody = + methodRegistry.createQueueDeclareOkBody(queueName, + queue.getMessageCount(), + queue.getConsumerCount()); + protocolConnection.writeFrame(responseBody.generateFrame(channelId)); + + _logger.info("Queue " + queueName + " declared successfully"); + } + } + + protected AMQShortString createName() + { + return new AMQShortString("tmp_" + UUID.randomUUID()); + } + + protected AMQQueue createQueue(final AMQShortString queueName, + QueueDeclareBody body, + VirtualHost virtualHost, + final AMQProtocolSession session) + throws AMQException + { + final QueueRegistry registry = virtualHost.getQueueRegistry(); + AMQShortString owner = body.getExclusive() ? session.getContextKey() : null; + + final AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(queueName, body.getDurable(), owner, body.getAutoDelete(), + body.getExclusive(),virtualHost, body.getArguments()); + + if (body.getExclusive() && !body.getDurable()) + { + final AMQProtocolSession.Task deleteQueueTask = + new AMQProtocolSession.Task() + { + public void doTask(AMQProtocolSession session) throws AMQException + { + if (registry.getQueue(queueName) == queue) + { + queue.delete(); + } + } + }; + + session.addSessionCloseTask(deleteQueueTask); + + queue.addQueueDeleteTask(new AMQQueue.Task() + { + public void doTask(AMQQueue queue) + { + session.removeSessionCloseTask(deleteQueueTask); + } + }); + } + + return queue; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeleteHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeleteHandler.java new file mode 100644 index 0000000000..da52268e52 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueDeleteHandler.java @@ -0,0 +1,125 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.QueueDeleteBody; +import org.apache.qpid.framing.QueueDeleteOkBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.store.DurableConfigurationStore; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.AMQChannel; + +public class QueueDeleteHandler implements StateAwareMethodListener<QueueDeleteBody> +{ + private static final QueueDeleteHandler _instance = new QueueDeleteHandler(); + + public static QueueDeleteHandler getInstance() + { + return _instance; + } + + private final boolean _failIfNotFound; + + public QueueDeleteHandler() + { + this(true); + } + + public QueueDeleteHandler(boolean failIfNotFound) + { + _failIfNotFound = failIfNotFound; + + } + + public void methodReceived(AMQStateManager stateManager, QueueDeleteBody body, int channelId) throws AMQException + { + AMQProtocolSession protocolConnection = stateManager.getProtocolSession(); + VirtualHost virtualHost = protocolConnection.getVirtualHost(); + QueueRegistry queueRegistry = virtualHost.getQueueRegistry(); + DurableConfigurationStore store = virtualHost.getDurableConfigurationStore(); + + AMQQueue queue; + if (body.getQueue() == null) + { + AMQChannel channel = protocolConnection.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + //get the default queue on the channel: + queue = channel.getDefaultQueue(); + } + else + { + queue = queueRegistry.getQueue(body.getQueue()); + } + + if (queue == null) + { + if (_failIfNotFound) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "Queue " + body.getQueue() + " does not exist."); + } + } + else + { + if (body.getIfEmpty() && !queue.isEmpty()) + { + throw body.getChannelException(AMQConstant.IN_USE, "Queue: " + body.getQueue() + " is not empty."); + } + else if (body.getIfUnused() && !queue.isUnused()) + { + // TODO - Error code + throw body.getChannelException(AMQConstant.IN_USE, "Queue: " + body.getQueue() + " is still used."); + } + else + { + AMQSessionModel session = queue.getExclusiveOwningSession(); + if (queue.isExclusive() && !queue.isDurable() && (session == null || session.getConnectionModel() != protocolConnection)) + { + throw body.getConnectionException(AMQConstant.NOT_ALLOWED, + "Queue " + queue.getNameShortString() + " is exclusive, but not created on this Connection."); + } + + int purged = queue.delete(); + + if (queue.isDurable()) + { + store.removeQueue(queue); + } + + MethodRegistry methodRegistry = protocolConnection.getMethodRegistry(); + QueueDeleteOkBody responseBody = methodRegistry.createQueueDeleteOkBody(purged); + protocolConnection.writeFrame(responseBody.generateFrame(channelId)); + } + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueuePurgeHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueuePurgeHandler.java new file mode 100644 index 0000000000..759eec0129 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueuePurgeHandler.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.QueuePurgeBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.AMQChannel; + +public class QueuePurgeHandler implements StateAwareMethodListener<QueuePurgeBody> +{ + private static final QueuePurgeHandler _instance = new QueuePurgeHandler(); + + public static QueuePurgeHandler getInstance() + { + return _instance; + } + + private final boolean _failIfNotFound; + + public QueuePurgeHandler() + { + this(true); + } + + public QueuePurgeHandler(boolean failIfNotFound) + { + _failIfNotFound = failIfNotFound; + } + + public void methodReceived(AMQStateManager stateManager, QueuePurgeBody body, int channelId) throws AMQException + { + AMQProtocolSession protocolConnection = stateManager.getProtocolSession(); + VirtualHost virtualHost = protocolConnection.getVirtualHost(); + QueueRegistry queueRegistry = virtualHost.getQueueRegistry(); + + AMQChannel channel = protocolConnection.getChannel(channelId); + + + AMQQueue queue; + if(body.getQueue() == null) + { + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + //get the default queue on the channel: + queue = channel.getDefaultQueue(); + + if(queue == null) + { + if(_failIfNotFound) + { + throw body.getConnectionException(AMQConstant.NOT_ALLOWED,"No queue specified."); + } + } + } + else + { + queue = queueRegistry.getQueue(body.getQueue()); + } + + if(queue == null) + { + if(_failIfNotFound) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "Queue " + body.getQueue() + " does not exist."); + } + } + else + { + AMQSessionModel session = queue.getExclusiveOwningSession(); + + if (queue.isExclusive() && (session == null || session.getConnectionModel() != protocolConnection)) + { + throw body.getConnectionException(AMQConstant.NOT_ALLOWED, + "Queue is exclusive, but not created on this Connection."); + } + + long purged = queue.clearQueue(); + + + if(!body.getNowait()) + { + + MethodRegistry methodRegistry = protocolConnection.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createQueuePurgeOkBody(purged); + protocolConnection.writeFrame(responseBody.generateFrame(channelId)); + + } + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueUnbindHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueUnbindHandler.java new file mode 100644 index 0000000000..8391a4b184 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/QueueUnbindHandler.java @@ -0,0 +1,123 @@ +package org.apache.qpid.server.handler; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.QueueUnbindBody; +import org.apache.qpid.framing.amqp_0_9.MethodRegistry_0_9; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public class QueueUnbindHandler implements StateAwareMethodListener<QueueUnbindBody> +{ + private static final Logger _log = Logger.getLogger(QueueUnbindHandler.class); + + private static final QueueUnbindHandler _instance = new QueueUnbindHandler(); + + public static QueueUnbindHandler getInstance() + { + return _instance; + } + + private QueueUnbindHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, QueueUnbindBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + VirtualHost virtualHost = session.getVirtualHost(); + ExchangeRegistry exchangeRegistry = virtualHost.getExchangeRegistry(); + QueueRegistry queueRegistry = virtualHost.getQueueRegistry(); + + + final AMQQueue queue; + final AMQShortString routingKey; + + if (body.getQueue() == null) + { + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + queue = channel.getDefaultQueue(); + + if (queue == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "No default queue defined on channel and queue was null"); + } + + routingKey = body.getRoutingKey() == null ? null : body.getRoutingKey().intern(); + + } + else + { + queue = queueRegistry.getQueue(body.getQueue()); + routingKey = body.getRoutingKey() == null ? null : body.getRoutingKey().intern(); + } + + if (queue == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "Queue " + body.getQueue() + " does not exist."); + } + final Exchange exch = exchangeRegistry.getExchange(body.getExchange()); + if (exch == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND, "Exchange " + body.getExchange() + " does not exist."); + } + + if(virtualHost.getBindingFactory().getBinding(String.valueOf(routingKey), queue, exch, FieldTable.convertToMap(body.getArguments())) == null) + { + throw body.getChannelException(AMQConstant.NOT_FOUND,"No such binding"); + } + else + { + virtualHost.getBindingFactory().removeBinding(String.valueOf(routingKey), queue, exch, FieldTable.convertToMap(body.getArguments())); + } + + + if (_log.isInfoEnabled()) + { + _log.info("Binding queue " + queue + " to exchange " + exch + " with routing key " + routingKey); + } + + MethodRegistry_0_9 methodRegistry = (MethodRegistry_0_9) session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createQueueUnbindOkBody(); + session.writeFrame(responseBody.generateFrame(channelId)); + + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl.java new file mode 100644 index 0000000000..e290afcde3 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl.java @@ -0,0 +1,574 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.qpid.server.handler;
+
+import java.util.Map;
+import java.util.HashMap;
+
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.AMQException;
+
+public class ServerMethodDispatcherImpl implements MethodDispatcher
+{
+ private final AMQStateManager _stateManager;
+
+ private static interface DispatcherFactory
+ {
+ public MethodDispatcher createMethodDispatcher(AMQStateManager stateManager);
+ }
+
+ private static final Map<ProtocolVersion, DispatcherFactory> _dispatcherFactories =
+ new HashMap<ProtocolVersion, DispatcherFactory>();
+
+
+ static
+ {
+ _dispatcherFactories.put(ProtocolVersion.v8_0,
+ new DispatcherFactory()
+ {
+ public MethodDispatcher createMethodDispatcher(AMQStateManager stateManager)
+ {
+ return new ServerMethodDispatcherImpl_8_0(stateManager);
+ }
+ });
+
+ _dispatcherFactories.put(ProtocolVersion.v0_9,
+ new DispatcherFactory()
+ {
+ public MethodDispatcher createMethodDispatcher(AMQStateManager stateManager)
+ {
+ return new ServerMethodDispatcherImpl_0_9(stateManager);
+ }
+ });
+ _dispatcherFactories.put(ProtocolVersion.v0_91,
+ new DispatcherFactory()
+ {
+ public MethodDispatcher createMethodDispatcher(AMQStateManager stateManager)
+ {
+ return new ServerMethodDispatcherImpl_0_91(stateManager);
+ }
+ });
+
+ }
+
+
+ private static final AccessRequestHandler _accessRequestHandler = AccessRequestHandler.getInstance();
+ private static final ChannelCloseHandler _channelCloseHandler = ChannelCloseHandler.getInstance();
+ private static final ChannelOpenHandler _channelOpenHandler = ChannelOpenHandler.getInstance();
+ private static final ChannelCloseOkHandler _channelCloseOkHandler = ChannelCloseOkHandler.getInstance();
+ private static final ConnectionCloseMethodHandler _connectionCloseMethodHandler = ConnectionCloseMethodHandler.getInstance();
+ private static final ConnectionCloseOkMethodHandler _connectionCloseOkMethodHandler = ConnectionCloseOkMethodHandler.getInstance();
+ private static final ConnectionOpenMethodHandler _connectionOpenMethodHandler = ConnectionOpenMethodHandler.getInstance();
+ private static final ConnectionTuneOkMethodHandler _connectionTuneOkMethodHandler = ConnectionTuneOkMethodHandler.getInstance();
+ private static final ConnectionSecureOkMethodHandler _connectionSecureOkMethodHandler = ConnectionSecureOkMethodHandler.getInstance();
+ private static final ConnectionStartOkMethodHandler _connectionStartOkMethodHandler = ConnectionStartOkMethodHandler.getInstance();
+ private static final ExchangeDeclareHandler _exchangeDeclareHandler = ExchangeDeclareHandler.getInstance();
+ private static final ExchangeDeleteHandler _exchangeDeleteHandler = ExchangeDeleteHandler.getInstance();
+ private static final ExchangeBoundHandler _exchangeBoundHandler = ExchangeBoundHandler.getInstance();
+ private static final BasicAckMethodHandler _basicAckMethodHandler = BasicAckMethodHandler.getInstance();
+ private static final BasicRecoverMethodHandler _basicRecoverMethodHandler = BasicRecoverMethodHandler.getInstance();
+ private static final BasicConsumeMethodHandler _basicConsumeMethodHandler = BasicConsumeMethodHandler.getInstance();
+ private static final BasicGetMethodHandler _basicGetMethodHandler = BasicGetMethodHandler.getInstance();
+ private static final BasicCancelMethodHandler _basicCancelMethodHandler = BasicCancelMethodHandler.getInstance();
+ private static final BasicPublishMethodHandler _basicPublishMethodHandler = BasicPublishMethodHandler.getInstance();
+ private static final BasicQosHandler _basicQosHandler = BasicQosHandler.getInstance();
+ private static final QueueBindHandler _queueBindHandler = QueueBindHandler.getInstance();
+ private static final QueueDeclareHandler _queueDeclareHandler = QueueDeclareHandler.getInstance();
+ private static final QueueDeleteHandler _queueDeleteHandler = QueueDeleteHandler.getInstance();
+ private static final QueuePurgeHandler _queuePurgeHandler = QueuePurgeHandler.getInstance();
+ private static final ChannelFlowHandler _channelFlowHandler = ChannelFlowHandler.getInstance();
+ private static final TxSelectHandler _txSelectHandler = TxSelectHandler.getInstance();
+ private static final TxCommitHandler _txCommitHandler = TxCommitHandler.getInstance();
+ private static final TxRollbackHandler _txRollbackHandler = TxRollbackHandler.getInstance();
+ private static final BasicRejectMethodHandler _basicRejectMethodHandler = BasicRejectMethodHandler.getInstance();
+
+
+
+ public static MethodDispatcher createMethodDispatcher(AMQStateManager stateManager, ProtocolVersion protocolVersion)
+ {
+ return _dispatcherFactories.get(protocolVersion).createMethodDispatcher(stateManager);
+ }
+
+
+ public ServerMethodDispatcherImpl(AMQStateManager stateManager)
+ {
+ _stateManager = stateManager;
+ }
+
+
+ protected AMQStateManager getStateManager()
+ {
+ return _stateManager;
+ }
+
+
+
+ public boolean dispatchAccessRequest(AccessRequestBody body, int channelId) throws AMQException
+ {
+ _accessRequestHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicAck(BasicAckBody body, int channelId) throws AMQException
+ {
+ _basicAckMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicCancel(BasicCancelBody body, int channelId) throws AMQException
+ {
+ _basicCancelMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicConsume(BasicConsumeBody body, int channelId) throws AMQException
+ {
+ _basicConsumeMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicGet(BasicGetBody body, int channelId) throws AMQException
+ {
+ _basicGetMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicPublish(BasicPublishBody body, int channelId) throws AMQException
+ {
+ _basicPublishMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicQos(BasicQosBody body, int channelId) throws AMQException
+ {
+ _basicQosHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicRecover(BasicRecoverBody body, int channelId) throws AMQException
+ {
+ _basicRecoverMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicReject(BasicRejectBody body, int channelId) throws AMQException
+ {
+ _basicRejectMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchChannelOpen(ChannelOpenBody body, int channelId) throws AMQException
+ {
+ _channelOpenHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+ public boolean dispatchAccessRequestOk(AccessRequestOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicCancelOk(BasicCancelOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicConsumeOk(BasicConsumeOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicDeliver(BasicDeliverBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicGetEmpty(BasicGetEmptyBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicGetOk(BasicGetOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicQosOk(BasicQosOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchBasicReturn(BasicReturnBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchChannelClose(ChannelCloseBody body, int channelId) throws AMQException
+ {
+ _channelCloseHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+ public boolean dispatchChannelCloseOk(ChannelCloseOkBody body, int channelId) throws AMQException
+ {
+ _channelCloseOkHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+ public boolean dispatchChannelFlow(ChannelFlowBody body, int channelId) throws AMQException
+ {
+ _channelFlowHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchChannelFlowOk(ChannelFlowOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchChannelOpenOk(ChannelOpenOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+
+ public boolean dispatchConnectionOpen(ConnectionOpenBody body, int channelId) throws AMQException
+ {
+ _connectionOpenMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+ public boolean dispatchConnectionClose(ConnectionCloseBody body, int channelId) throws AMQException
+ {
+ _connectionCloseMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+ public boolean dispatchConnectionCloseOk(ConnectionCloseOkBody body, int channelId) throws AMQException
+ {
+ _connectionCloseOkMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchConnectionOpenOk(ConnectionOpenOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchConnectionRedirect(ConnectionRedirectBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchConnectionSecure(ConnectionSecureBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchConnectionStart(ConnectionStartBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchConnectionTune(ConnectionTuneBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchDtxSelectOk(DtxSelectOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchDtxStartOk(DtxStartOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchExchangeBoundOk(ExchangeBoundOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchExchangeDeclareOk(ExchangeDeclareOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchExchangeDeleteOk(ExchangeDeleteOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileCancelOk(FileCancelOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileConsumeOk(FileConsumeOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileDeliver(FileDeliverBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileOpen(FileOpenBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileOpenOk(FileOpenOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileQosOk(FileQosOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileReturn(FileReturnBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchFileStage(FileStageBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchQueueBindOk(QueueBindOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchQueueDeclareOk(QueueDeclareOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchQueueDeleteOk(QueueDeleteOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchQueuePurgeOk(QueuePurgeOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchStreamCancelOk(StreamCancelOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchStreamConsumeOk(StreamConsumeOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchStreamDeliver(StreamDeliverBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchStreamQosOk(StreamQosOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchStreamReturn(StreamReturnBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchTxCommitOk(TxCommitOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchTxRollbackOk(TxRollbackOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchTxSelectOk(TxSelectOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+
+ public boolean dispatchConnectionSecureOk(ConnectionSecureOkBody body, int channelId) throws AMQException
+ {
+ _connectionSecureOkMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchConnectionStartOk(ConnectionStartOkBody body, int channelId) throws AMQException
+ {
+ _connectionStartOkMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchConnectionTuneOk(ConnectionTuneOkBody body, int channelId) throws AMQException
+ {
+ _connectionTuneOkMethodHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchDtxSelect(DtxSelectBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchDtxStart(DtxStartBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchExchangeBound(ExchangeBoundBody body, int channelId) throws AMQException
+ {
+ _exchangeBoundHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchExchangeDeclare(ExchangeDeclareBody body, int channelId) throws AMQException
+ {
+ _exchangeDeclareHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchExchangeDelete(ExchangeDeleteBody body, int channelId) throws AMQException
+ {
+ _exchangeDeleteHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchFileAck(FileAckBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchFileCancel(FileCancelBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchFileConsume(FileConsumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchFilePublish(FilePublishBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchFileQos(FileQosBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchFileReject(FileRejectBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchQueueBind(QueueBindBody body, int channelId) throws AMQException
+ {
+ _queueBindHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchQueueDeclare(QueueDeclareBody body, int channelId) throws AMQException
+ {
+ _queueDeclareHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchQueueDelete(QueueDeleteBody body, int channelId) throws AMQException
+ {
+ _queueDeleteHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchQueuePurge(QueuePurgeBody body, int channelId) throws AMQException
+ {
+ _queuePurgeHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchStreamCancel(StreamCancelBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchStreamConsume(StreamConsumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchStreamPublish(StreamPublishBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchStreamQos(StreamQosBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTunnelRequest(TunnelRequestBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTxCommit(TxCommitBody body, int channelId) throws AMQException
+ {
+ _txCommitHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchTxRollback(TxRollbackBody body, int channelId) throws AMQException
+ {
+ _txRollbackHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+ public boolean dispatchTxSelect(TxSelectBody body, int channelId) throws AMQException
+ {
+ _txSelectHandler.methodReceived(_stateManager, body, channelId);
+ return true;
+ }
+
+
+
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_0_9.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_0_9.java new file mode 100644 index 0000000000..8b1dca77ba --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_0_9.java @@ -0,0 +1,164 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.qpid.server.handler;
+
+
+import org.apache.qpid.framing.amqp_0_9.MethodDispatcher_0_9;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.AMQException;
+
+
+
+public class ServerMethodDispatcherImpl_0_9
+ extends ServerMethodDispatcherImpl
+ implements MethodDispatcher_0_9
+
+{
+
+ private static final BasicRecoverSyncMethodHandler _basicRecoverSyncMethodHandler =
+ BasicRecoverSyncMethodHandler.getInstance();
+ private static final QueueUnbindHandler _queueUnbindHandler =
+ QueueUnbindHandler.getInstance();
+
+
+ public ServerMethodDispatcherImpl_0_9(AMQStateManager stateManager)
+ {
+ super(stateManager);
+ }
+
+ public boolean dispatchBasicRecoverSync(BasicRecoverSyncBody body, int channelId) throws AMQException
+ {
+ _basicRecoverSyncMethodHandler.methodReceived(getStateManager(), body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicRecoverSyncOk(BasicRecoverSyncOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchChannelOk(ChannelOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelPing(ChannelPingBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelPong(ChannelPongBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelResume(ChannelResumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageAppend(MessageAppendBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageCancel(MessageCancelBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageCheckpoint(MessageCheckpointBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageClose(MessageCloseBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageConsume(MessageConsumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageEmpty(MessageEmptyBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageGet(MessageGetBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageOffset(MessageOffsetBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageOk(MessageOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageOpen(MessageOpenBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageQos(MessageQosBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageRecover(MessageRecoverBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageReject(MessageRejectBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageResume(MessageResumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageTransfer(MessageTransferBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchQueueUnbindOk(QueueUnbindOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchQueueUnbind(QueueUnbindBody body, int channelId) throws AMQException
+ {
+ _queueUnbindHandler.methodReceived(getStateManager(),body,channelId);
+ return true;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_0_91.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_0_91.java new file mode 100644 index 0000000000..32cd4c4e9f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_0_91.java @@ -0,0 +1,168 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.qpid.server.handler;
+
+
+import org.apache.qpid.framing.*;
+import org.apache.qpid.framing.amqp_0_91.MethodDispatcher_0_91;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.AMQException;
+
+
+public class ServerMethodDispatcherImpl_0_91
+ extends ServerMethodDispatcherImpl
+ implements MethodDispatcher_0_91
+
+{
+
+ private static final BasicRecoverSyncMethodHandler _basicRecoverSyncMethodHandler =
+ BasicRecoverSyncMethodHandler.getInstance();
+ private static final QueueUnbindHandler _queueUnbindHandler =
+ QueueUnbindHandler.getInstance();
+
+
+ public ServerMethodDispatcherImpl_0_91(AMQStateManager stateManager)
+ {
+ super(stateManager);
+ }
+
+ public boolean dispatchBasicRecoverSync(BasicRecoverSyncBody body, int channelId) throws AMQException
+ {
+ _basicRecoverSyncMethodHandler.methodReceived(getStateManager(), body, channelId);
+ return true;
+ }
+
+ public boolean dispatchBasicRecoverSyncOk(BasicRecoverSyncOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchChannelOk(ChannelOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelPing(ChannelPingBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelPong(ChannelPongBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchChannelResume(ChannelResumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageAppend(MessageAppendBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageCancel(MessageCancelBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageCheckpoint(MessageCheckpointBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageClose(MessageCloseBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageConsume(MessageConsumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageEmpty(MessageEmptyBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageGet(MessageGetBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageOffset(MessageOffsetBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageOk(MessageOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageOpen(MessageOpenBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageQos(MessageQosBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageRecover(MessageRecoverBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageReject(MessageRejectBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageResume(MessageResumeBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchMessageTransfer(MessageTransferBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchBasicRecoverOk(BasicRecoverOkBody body, int channelId) throws AMQException
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean dispatchQueueUnbindOk(QueueUnbindOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchQueueUnbind(QueueUnbindBody body, int channelId) throws AMQException
+ {
+ _queueUnbindHandler.methodReceived(getStateManager(),body,channelId);
+ return true;
+ }
+}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_8_0.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_8_0.java new file mode 100644 index 0000000000..d599ca3d4e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/ServerMethodDispatcherImpl_8_0.java @@ -0,0 +1,86 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.qpid.server.handler;
+
+import org.apache.qpid.framing.amqp_8_0.MethodDispatcher_8_0;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.server.state.AMQStateManager;
+import org.apache.qpid.AMQException;
+
+public class ServerMethodDispatcherImpl_8_0
+ extends ServerMethodDispatcherImpl
+ implements MethodDispatcher_8_0
+{
+ public ServerMethodDispatcherImpl_8_0(AMQStateManager stateManager)
+ {
+ super(stateManager);
+ }
+
+ public boolean dispatchBasicRecoverOk(BasicRecoverOkBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchChannelAlert(ChannelAlertBody body, int channelId) throws AMQException
+ {
+ throw new UnexpectedMethodException(body);
+ }
+
+ public boolean dispatchTestContent(TestContentBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestContentOk(TestContentOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestInteger(TestIntegerBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestIntegerOk(TestIntegerOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestString(TestStringBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestStringOk(TestStringOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestTable(TestTableBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+
+ public boolean dispatchTestTableOk(TestTableOkBody body, int channelId) throws AMQException
+ {
+ return false;
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxCommitHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxCommitHandler.java new file mode 100644 index 0000000000..abd2bccc8d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxCommitHandler.java @@ -0,0 +1,76 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.TxCommitBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class TxCommitHandler implements StateAwareMethodListener<TxCommitBody> +{ + private static final Logger _log = Logger.getLogger(TxCommitHandler.class); + + private static TxCommitHandler _instance = new TxCommitHandler(); + + public static TxCommitHandler getInstance() + { + return _instance; + } + + private TxCommitHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, TxCommitBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + try + { + if (_log.isDebugEnabled()) + { + _log.debug("Commit received on channel " + channelId); + } + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + channel.commit(); + + MethodRegistry methodRegistry = session.getMethodRegistry(); + AMQMethodBody responseBody = methodRegistry.createTxCommitOkBody(); + session.writeFrame(responseBody.generateFrame(channelId)); + + } + catch (AMQException e) + { + throw body.getChannelException(e.getErrorCode(), "Failed to commit: " + e.getMessage()); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxRollbackHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxRollbackHandler.java new file mode 100644 index 0000000000..4643dee0a3 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxRollbackHandler.java @@ -0,0 +1,82 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.TxRollbackBody; +import org.apache.qpid.framing.TxRollbackOkBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; + +public class TxRollbackHandler implements StateAwareMethodListener<TxRollbackBody> +{ + private static TxRollbackHandler _instance = new TxRollbackHandler(); + + public static TxRollbackHandler getInstance() + { + return _instance; + } + + private TxRollbackHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, TxRollbackBody body, final int channelId) throws AMQException + { + final AMQProtocolSession session = stateManager.getProtocolSession(); + + try + { + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + + + final MethodRegistry methodRegistry = session.getMethodRegistry(); + final AMQMethodBody responseBody = methodRegistry.createTxRollbackOkBody(); + + Runnable task = new Runnable() + { + + public void run() + { + session.writeFrame(responseBody.generateFrame(channelId)); + } + }; + + channel.rollback(task); + + } + catch (AMQException e) + { + throw body.getChannelException(e.getErrorCode(), "Failed to rollback: " + e.getMessage()); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxSelectHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxSelectHandler.java new file mode 100644 index 0000000000..308f5b73cf --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/TxSelectHandler.java @@ -0,0 +1,63 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.handler; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.TxSelectBody; +import org.apache.qpid.framing.TxSelectOkBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.state.StateAwareMethodListener; +import org.apache.qpid.server.AMQChannel; + +public class TxSelectHandler implements StateAwareMethodListener<TxSelectBody> +{ + private static TxSelectHandler _instance = new TxSelectHandler(); + + public static TxSelectHandler getInstance() + { + return _instance; + } + + private TxSelectHandler() + { + } + + public void methodReceived(AMQStateManager stateManager, TxSelectBody body, int channelId) throws AMQException + { + AMQProtocolSession session = stateManager.getProtocolSession(); + + AMQChannel channel = session.getChannel(channelId); + + if (channel == null) + { + throw body.getChannelNotFoundException(channelId); + } + + channel.setLocalTransactional(); + + MethodRegistry methodRegistry = session.getMethodRegistry(); + TxSelectOkBody responseBody = methodRegistry.createTxSelectOkBody(); + session.writeFrame(responseBody.generateFrame(channelId)); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/UnexpectedMethodException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/UnexpectedMethodException.java new file mode 100644 index 0000000000..3526fdcae5 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/handler/UnexpectedMethodException.java @@ -0,0 +1,36 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.qpid.server.handler;
+
+
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.AMQException;
+
+public class UnexpectedMethodException extends AMQException
+{
+
+ private static final long serialVersionUID = -255921574946294892L;
+
+ public UnexpectedMethodException(AMQMethodBody body)
+ {
+ super("Unexpected method recevied: " + body.getClass().getName());
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/information/management/ServerInformationMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/information/management/ServerInformationMBean.java new file mode 100644 index 0000000000..5e6a143d52 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/information/management/ServerInformationMBean.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.information.management; + +import java.io.IOException; + +import org.apache.qpid.common.QpidProperties; +import org.apache.qpid.management.common.mbeans.ServerInformation; +import org.apache.qpid.management.common.mbeans.annotations.MBeanDescription; +import org.apache.qpid.server.management.AMQManagedObject; +import org.apache.qpid.server.registry.ApplicationRegistry; + +import javax.management.JMException; + +/** MBean class for the ServerInformationMBean. */ +@MBeanDescription("Server Information Interface") +public class ServerInformationMBean extends AMQManagedObject implements ServerInformation +{ + private String buildVersion; + private String productVersion; + private ApplicationRegistry registry; + + public ServerInformationMBean(ApplicationRegistry applicationRegistry) throws JMException + { + super(ServerInformation.class, ServerInformation.TYPE); + + registry = applicationRegistry; + buildVersion = QpidProperties.getBuildVersion(); + productVersion = QpidProperties.getReleaseVersion(); + } + + public String getObjectInstanceName() + { + return ServerInformation.TYPE; + } + + public Integer getManagementApiMajorVersion() throws IOException + { + return QPID_JMX_API_MAJOR_VERSION; + } + + public Integer getManagementApiMinorVersion() throws IOException + { + return QPID_JMX_API_MINOR_VERSION; + } + + public String getBuildVersion() throws IOException + { + return buildVersion; + } + + public String getProductVersion() throws IOException + { + return productVersion; + } + + + public void resetStatistics() throws Exception + { + registry.resetStatistics(); + } + + public double getPeakMessageDeliveryRate() + { + return registry.getMessageDeliveryStatistics().getPeak(); + } + + public double getPeakDataDeliveryRate() + { + return registry.getDataDeliveryStatistics().getPeak(); + } + + public double getMessageDeliveryRate() + { + return registry.getMessageDeliveryStatistics().getRate(); + } + + public double getDataDeliveryRate() + { + return registry.getDataDeliveryStatistics().getRate(); + } + + public long getTotalMessagesDelivered() + { + return registry.getMessageDeliveryStatistics().getTotal(); + } + + public long getTotalDataDelivered() + { + return registry.getDataDeliveryStatistics().getTotal(); + } + + public double getPeakMessageReceiptRate() + { + return registry.getMessageReceiptStatistics().getPeak(); + } + + public double getPeakDataReceiptRate() + { + return registry.getDataReceiptStatistics().getPeak(); + } + + public double getMessageReceiptRate() + { + return registry.getMessageReceiptStatistics().getRate(); + } + + public double getDataReceiptRate() + { + return registry.getDataReceiptStatistics().getRate(); + } + + public long getTotalMessagesReceived() + { + return registry.getMessageReceiptStatistics().getTotal(); + } + + public long getTotalDataReceived() + { + return registry.getDataReceiptStatistics().getTotal(); + } + + public boolean isStatisticsEnabled() + { + return registry.isStatisticsEnabled(); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/AbstractRootMessageLogger.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/AbstractRootMessageLogger.java new file mode 100644 index 0000000000..545f2adea2 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/AbstractRootMessageLogger.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging; + +import org.apache.qpid.server.configuration.ServerConfiguration; + +public abstract class AbstractRootMessageLogger implements RootMessageLogger +{ + public static final String DEFAULT_LOG_HIERARCHY_PREFIX = "qpid.message."; + + private boolean _enabled = true; + + public AbstractRootMessageLogger() + { + + } + + public AbstractRootMessageLogger(ServerConfiguration config) + { + _enabled = config.getStatusUpdatesEnabled(); + } + + public boolean isEnabled() + { + return _enabled; + } + + public boolean isMessageEnabled(LogActor actor, LogSubject subject, String logHierarchy) + { + return _enabled; + } + + public boolean isMessageEnabled(LogActor actor, String logHierarchy) + { + return _enabled; + } + + public abstract void rawMessage(String message, String logHierarchy); + + public abstract void rawMessage(String message, Throwable throwable, String logHierarchy); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/CompositeStartupMessageLogger.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/CompositeStartupMessageLogger.java new file mode 100644 index 0000000000..e0a51b3a3e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/CompositeStartupMessageLogger.java @@ -0,0 +1,51 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging; + +public class CompositeStartupMessageLogger extends AbstractRootMessageLogger +{ + private RootMessageLogger[] _loggers; + + public CompositeStartupMessageLogger(RootMessageLogger[] loggers) + { + super(); + _loggers = loggers; + } + + @Override + public void rawMessage(String message, String logHierarchy) + { + for(RootMessageLogger l : _loggers) + { + l.rawMessage(message, logHierarchy); + } + } + + @Override + public void rawMessage(String message, Throwable throwable, String logHierarchy) + { + for(RootMessageLogger l : _loggers) + { + l.rawMessage(message, throwable, logHierarchy); + } + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/Log4jMessageLogger.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/Log4jMessageLogger.java new file mode 100644 index 0000000000..a0285ebfc4 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/Log4jMessageLogger.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging; + +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.qpid.server.configuration.ServerConfiguration; + +public class Log4jMessageLogger extends AbstractRootMessageLogger +{ + public static final Level LEVEL = Level.toLevel("INFO"); + + public Log4jMessageLogger() + { + super(); + } + + public Log4jMessageLogger(ServerConfiguration config) + { + super(config); + } + + @Override + public boolean isMessageEnabled(LogActor actor, LogSubject subject, String logHierarchy) + { + return isMessageEnabled(actor, logHierarchy); + } + + @Override + public boolean isMessageEnabled(LogActor actor, String logHierarchy) + { + if(isEnabled()) + { + Logger logger = Logger.getLogger(logHierarchy); + return logger.isEnabledFor(LEVEL); + } + else + { + return false; + } + } + + @Override + public void rawMessage(String message, String logHierarchy) + { + rawMessage(message, null, logHierarchy); + } + + @Override + public void rawMessage(String message, Throwable throwable, String logHierarchy) + { + Logger logger = Logger.getLogger(logHierarchy); + + logger.log(LEVEL, message, throwable); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/LogActor.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/LogActor.java new file mode 100644 index 0000000000..18f03c2716 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/LogActor.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging; + +/** + * LogActor the entity that is stored as in a ThreadLocal and used to perform logging. + * + * The actor is responsible for formatting its display name for the log entry. + * + * The actor performs the requested logging. + */ +public interface LogActor +{ + /** + * Logs the specified LogMessage about the LogSubject + * + * Currently logging has a global setting however this will later be revised and + * as such the LogActor will need to take into consideration any new configuration + * as a means of enabling the logging of LogActors and LogSubjects. + * + * @param subject The subject that is being logged + * @param message The message to log + */ + public void message(LogSubject subject, LogMessage message); + + /** + * Logs the specified LogMessage against this actor + * + * Currently logging has a global setting however this will later be revised and + * as such the LogActor will need to take into consideration any new configuration + * as a means of enabling the logging of LogActors and LogSubjects. + * + * @param message The message to log + */ + public void message(LogMessage message); + + /** + * + * @return the RootMessageLogger that is currently in use by this LogActor. + */ + RootMessageLogger getRootMessageLogger(); + + /** + * + * @return the String representing this LogActor + */ + public String getLogMessage(); +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/LogMessage.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/LogMessage.java new file mode 100644 index 0000000000..fa18435fab --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/LogMessage.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging; + +public interface LogMessage +{ + String getLogHierarchy(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/LogSubject.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/LogSubject.java new file mode 100644 index 0000000000..09a277e520 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/LogSubject.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging; + +/** + * Each LogSubject that wishes to be logged will implement this to provide their + * own display representation. + * + */ +public interface LogSubject +{ + /** + * Provides the log message as as String. + * + * @returns String the display representation of this LogSubject + */ + public String toLogString(); +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/NullRootMessageLogger.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/NullRootMessageLogger.java new file mode 100644 index 0000000000..db8b24e90e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/NullRootMessageLogger.java @@ -0,0 +1,47 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging; + +public class NullRootMessageLogger extends AbstractRootMessageLogger +{ + + @Override + public boolean isMessageEnabled(LogActor actor, LogSubject subject, String logHeirarchy) + { + return false; + } + + @Override + public boolean isMessageEnabled(LogActor actor, String logHierarchy) + { + return false; + } + + public void rawMessage(String message, String logHierarchy) + { + // drop message + } + + public void rawMessage(String message, Throwable throwable, String logHierarchy) + { + // drop message + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/RootMessageLogger.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/RootMessageLogger.java new file mode 100644 index 0000000000..1431dd1da9 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/RootMessageLogger.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging; + +/** + * The RootMessageLogger is used by the LogActors to query if + * logging is enabled for the requested message and to provide the actual + * message that should be logged. + */ +public interface RootMessageLogger +{ + /** + * Determine whether the MessageLogger is enabled + * + * @return boolean true if enabled. + */ + boolean isEnabled(); + + /** + * Determine if the LogSubject and the LogActor should be + * generating log messages. + * @param actor The actor requesting the logging + * @param subject The subject of this log request + * @param logHierarchy The log hierarchy for this request + * + * @return boolean true if the message should be logged. + */ + boolean isMessageEnabled(LogActor actor, LogSubject subject, String logHierarchy); + + /** + * Determine if the LogActor should be generating log messages. + * + * @param actor The actor requesting the logging + * @param logHierarchy The log hierarchy for this request + * + * @return boolean true if the message should be logged. + */ + boolean isMessageEnabled(LogActor actor, String logHierarchy); + + /** + * Log the raw message to the configured logger. + * + * @param message The message to log + * @param logHierarchy The log hierarchy for this request + */ + public void rawMessage(String message, String logHierarchy); + + /** + * Log the raw message to the configured logger. + * Along with a formated stack trace from the Throwable. + * + * @param message The message to log + * @param throwable Optional Throwable that should provide stact trace + * @param logHierarchy The log hierarchy for this request + */ + void rawMessage(String message, Throwable throwable, String logHierarchy); +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/SystemOutMessageLogger.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/SystemOutMessageLogger.java new file mode 100644 index 0000000000..b384b3fde3 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/SystemOutMessageLogger.java @@ -0,0 +1,51 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging; + + +public class SystemOutMessageLogger extends AbstractRootMessageLogger +{ + @Override + public boolean isMessageEnabled(LogActor actor, LogSubject subject, String logHeirarchy) + { + return true; + } + + @Override + public boolean isMessageEnabled(LogActor actor, String logHierarchy) + { + return true; + } + + public void rawMessage(String message, String logHierarchy) + { + rawMessage(message, null, logHierarchy); + } + + public void rawMessage(String message, Throwable throwable, String logHierarchy) + { + System.out.println(message); + if (throwable != null) + { + throwable.printStackTrace(System.out); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/AMQPChannelActor.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/AMQPChannelActor.java new file mode 100644 index 0000000000..9c7ffcc5f8 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/AMQPChannelActor.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging.actors; + +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.subjects.ChannelLogSubject; +import org.apache.qpid.server.protocol.AMQProtocolSession; + +import java.text.MessageFormat; + +/** + * An AMQPChannelActor represtents a connection through the AMQP port with an + * associated Channel. + * + * <p/> + * This is responsible for correctly formatting the LogActor String in the log + * <p/> + * [con:1(user@127.0.0.1/)/ch:1] + * <p/> + * To do this it requires access to the IO Layers as well as a Channel + */ +public class AMQPChannelActor extends AbstractActor +{ + private final ChannelLogSubject _logString; + + /** + * Create a new ChannelActor + * + * @param channel The Channel for this LogActor + * @param rootLogger The root Logger that this LogActor should use + */ + public AMQPChannelActor(AMQChannel channel, RootMessageLogger rootLogger) + { + super(rootLogger); + + + _logString = new ChannelLogSubject(channel); + } + + public String getLogMessage() + { + return _logString.toLogString(); + } +} + diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/AMQPConnectionActor.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/AMQPConnectionActor.java new file mode 100644 index 0000000000..1b4bc91bc1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/AMQPConnectionActor.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging.actors; + +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.subjects.ConnectionLogSubject; +import org.apache.qpid.server.protocol.AMQProtocolSession; + + + +/** + * An AMQPConnectionActor represtents a connectionthrough the AMQP port. + * <p/> + * This is responsible for correctly formatting the LogActor String in the log + * <p/> + * [ con:1(user@127.0.0.1/) ] + * <p/> + * To do this it requires access to the IO Layers. + */ +public class AMQPConnectionActor extends AbstractActor +{ + private ConnectionLogSubject _logSubject; + + public AMQPConnectionActor(AMQProtocolSession session, RootMessageLogger rootLogger) + { + super(rootLogger); + + _logSubject = new ConnectionLogSubject(session); + } + + public String getLogMessage() + { + return _logSubject.toLogString(); + } +} + diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/AbstractActor.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/AbstractActor.java new file mode 100644 index 0000000000..e0bf180cc4 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/AbstractActor.java @@ -0,0 +1,71 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging.actors; + +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogMessage; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.RootMessageLogger; + +public abstract class AbstractActor implements LogActor +{ + public final String _msgPrefix = System.getProperty("qpid.logging.prefix",""); + + protected RootMessageLogger _rootLogger; + + public AbstractActor(RootMessageLogger rootLogger) + { + if(rootLogger == null) + { + throw new NullPointerException("RootMessageLogger cannot be null"); + } + _rootLogger = rootLogger; + } + + public void message(LogSubject subject, LogMessage message) + { + if (_rootLogger.isMessageEnabled(this, subject, message.getLogHierarchy())) + { + _rootLogger.rawMessage(_msgPrefix + getLogMessage() + subject.toLogString() + message, message.getLogHierarchy()); + } + } + + public void message(LogMessage message) + { + if (_rootLogger.isMessageEnabled(this, message.getLogHierarchy())) + { + _rootLogger.rawMessage(_msgPrefix + getLogMessage() + message, message.getLogHierarchy()); + } + } + + public RootMessageLogger getRootMessageLogger() + { + return _rootLogger; + } + + public String toString() + { + return getLogMessage(); + } + + abstract public String getLogMessage(); + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/BrokerActor.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/BrokerActor.java new file mode 100644 index 0000000000..9e77452228 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/BrokerActor.java @@ -0,0 +1,53 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging.actors; + +import org.apache.qpid.server.logging.RootMessageLogger; + +public class BrokerActor extends AbstractActor +{ + private final String _logString; + + /** + * Create a new BrokerActor + * + * @param logger + */ + public BrokerActor(RootMessageLogger logger) + { + super(logger); + + _logString = "[Broker] "; + } + + public BrokerActor(String name, RootMessageLogger logger) + { + super(logger); + + _logString = "[Broker(" + name + ")] "; + } + + public String getLogMessage() + { + return _logString; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/CurrentActor.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/CurrentActor.java new file mode 100644 index 0000000000..2ebbfeb734 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/CurrentActor.java @@ -0,0 +1,125 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging.actors; + +import org.apache.qpid.server.logging.LogActor; + +import java.util.EmptyStackException; +import java.util.Stack; + +/** + * The CurrentActor is a ThreadLocal wrapper that allows threads in the broker + * to retrieve an actor to perform logging. This approach is used so for two + * reasons: + * 1) We do not have to pass a logging actor around the system + * 2) We can set new actors at the point we have enough information. i.e. + * - Set a low level ConnectionActor when processing bytes from the wire. + * - Set a ChannelActor when we are processing the frame + * - Set a SubscriptionActor when we are handling the subscription. + * <p/> + * The code performing the logging need not worry about what type of actor is + * currently set so can perform its logging. The resulting log entry though will + * contain customised details from the the currently set Actor. + * <p/> + * The Actor model also allows the pre-creation of fixed messages so the + * performance impact of the additional logging data is minimised. + * <p/> + * This class does not perform any checks to ensure that there is an Actor set + * when calling remove or get. As a result the application developer must ensure + * that they have called set before they attempt to use the actor via get or + * remove the set actor. + * <p/> + * The checking of the return via get should not be done as the logging is + * desired. It is preferable to cause the NullPointerException to highlight the + * programming error rather than miss a log message. + * <p/> + * The same is true for the remove. A NPE will occur if no set has been called + * highlighting the programming error. + */ +public class CurrentActor +{ + /** The ThreadLocal variable with initialiser */ + private static final ThreadLocal<Stack<LogActor>> _currentActor = new ThreadLocal<Stack<LogActor>>() + { + // Initialise the CurrentActor to be an empty List + protected Stack<LogActor> initialValue() + { + return new Stack<LogActor>(); + } + }; + + private static LogActor _defaultActor; + + /** + * Set a new {@link LogActor} to be the Current Actor + * <p/> + * This pushes the Actor in to the LIFO Queue + * + * @param actor The new LogActor + */ + public static void set(LogActor actor) + { + Stack<LogActor> stack = _currentActor.get(); + stack.push(actor); + } + + /** + * Remove all {@link LogActor}s + */ + public static void removeAll() + { + Stack<LogActor> stack = _currentActor.get(); + stack.clear(); + } + + /** + * Remove the current {@link LogActor}. + * <p/> + * Calling remove without calling set will result in an EmptyStackException. + */ + public static void remove() + { + Stack<LogActor> stack = _currentActor.get(); + stack.pop(); + } + + /** + * Return the current head of the list of {@link LogActor}s. + * + * @return Current LogActor + */ + public static LogActor get() + { + try + { + return _currentActor.get().peek(); + } + catch (EmptyStackException ese) + { + return _defaultActor; + } + } + + public static void setDefault(LogActor defaultActor) + { + _defaultActor = defaultActor; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/GenericActor.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/GenericActor.java new file mode 100644 index 0000000000..9afc76ce78 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/GenericActor.java @@ -0,0 +1,85 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging.actors; + +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.registry.ApplicationRegistry; + +public class GenericActor extends AbstractActor +{ + + private static RootMessageLogger _defaultMessageLogger; + + private LogSubject _logSubject; + + public static RootMessageLogger getDefaultMessageLogger() + { + return _defaultMessageLogger; + } + + public static void setDefaultMessageLogger(RootMessageLogger defaultMessageLogger) + { + _defaultMessageLogger = defaultMessageLogger; + } + + public GenericActor(LogSubject logSubject, RootMessageLogger rootLogger) + { + super(rootLogger); + _logSubject = logSubject; + } + + public String getLogMessage() + { + return _logSubject.toLogString(); + } + + public static LogActor getInstance(final String logMessage, RootMessageLogger rootLogger) + { + return new GenericActor(new LogSubject() + { + public String toLogString() + { + return logMessage; + } + + }, rootLogger); + } + + public static LogActor getInstance(final String subjectMessage) + { + return new GenericActor(new LogSubject() + { + public String toLogString() + { + return "[" + subjectMessage + "] "; + } + + }, _defaultMessageLogger); + } + + public static LogActor getInstance(LogSubject logSubject) + { + return new GenericActor(logSubject, _defaultMessageLogger); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/ManagementActor.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/ManagementActor.java new file mode 100644 index 0000000000..2825fa1b75 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/ManagementActor.java @@ -0,0 +1,114 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging.actors; + +import org.apache.qpid.server.logging.LogMessage; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.RootMessageLogger; + +import java.text.MessageFormat; + +/** + * NOTE: This actor is not thread safe. + * + * Sharing of a ManagementActor instance between threads may result in an + * incorrect actor value being logged. + * + * This is due to the fact that calls to message will dynamically query the + * thread name and use that to set the log format during each message() call. + * + * This is currently not an issue as each MBean operation creates a new Actor + * that is unique for each operation. + */ +public class ManagementActor extends AbstractActor +{ + String _lastThreadName = null; + + /** + * LOG FORMAT for the ManagementActor, + * Uses a MessageFormat call to insert the requried values according to + * these indicies: + * + * 0 - Connection ID + * 1 - User ID + * 2 - IP + */ + public static final String MANAGEMENT_FORMAT = "mng:{0}({1})"; + + /** + * The logString to be used for logging + */ + private String _logString; + + /** @param rootLogger The RootLogger to use for this Actor */ + public ManagementActor(RootMessageLogger rootLogger) + { + super(rootLogger); + } + + private void updateLogString() + { + String currentName = Thread.currentThread().getName(); + + String actor; + // Record the last thread name so we don't have to recreate the log string + if (!currentName.equals(_lastThreadName)) + { + _lastThreadName = currentName; + + // Management Thread names have this format. + //RMI TCP Connection(2)-169.24.29.116 + // This is true for both LocalAPI and JMX Connections + // However to be defensive lets test. + + String[] split = currentName.split("\\("); + if (split.length == 2) + { + String connectionID = split[1].split("\\)")[0]; + String ip = currentName.split("-")[1]; + + actor = MessageFormat.format(MANAGEMENT_FORMAT, + connectionID, + ip); + } + else + { + // This is a precautionary path as it is not expected to occur + // however rather than adjusting the thread name of the two + // tests that will use this it is safer all round to do this. + // it is also currently used by tests : + // AMQBrokerManagerMBeanTest + // ExchangeMBeanTest + actor = currentName; + } + + _logString = "[" + actor + "] "; + + } + } + + public String getLogMessage() + { + updateLogString(); + return _logString; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/QueueActor.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/QueueActor.java new file mode 100644 index 0000000000..3364365b61 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/QueueActor.java @@ -0,0 +1,55 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging.actors; + +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.subjects.QueueLogSubject; +import org.apache.qpid.server.queue.AMQQueue; + +import java.text.MessageFormat; + +/** + * This Actor is used when while the queue is performing an asynchronous process + * of its queue. + */ +public class QueueActor extends AbstractActor +{ + private QueueLogSubject _logSubject; + + /** + * Create an QueueLogSubject that Logs in the following format. + * + * @param queue The queue that this Actor is working for + * @param rootLogger the Root logger to use. + */ + public QueueActor(AMQQueue queue, RootMessageLogger rootLogger) + { + super(rootLogger); + + _logSubject = new QueueLogSubject(queue); + } + + public String getLogMessage() + { + return _logSubject.toLogString(); + } +} + diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/SubscriptionActor.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/SubscriptionActor.java new file mode 100644 index 0000000000..a2dbf2f6ee --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/actors/SubscriptionActor.java @@ -0,0 +1,46 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging.actors; + +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.subjects.SubscriptionLogSubject; +import org.apache.qpid.server.subscription.Subscription; + +/** + * The subscription actor provides formatted logging for actions that are + * performed by the subsciption. Such as STATE changes. + */ +public class SubscriptionActor extends AbstractActor +{ + private SubscriptionLogSubject _logSubject; + + public SubscriptionActor(RootMessageLogger logger, Subscription subscription) + { + super(logger); + + _logSubject = new SubscriptionLogSubject(subscription); + } + + public String getLogMessage() + { + return _logSubject.toLogString(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/management/LoggingManagementMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/management/LoggingManagementMBean.java new file mode 100644 index 0000000000..a823fb7cb1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/management/LoggingManagementMBean.java @@ -0,0 +1,825 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging.management; + +import static org.apache.log4j.xml.QpidLog4JConfigurator.LOCK; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import org.apache.qpid.management.common.mbeans.LoggingManagement; +import org.apache.qpid.management.common.mbeans.annotations.MBeanDescription; +import org.apache.qpid.server.management.AMQManagedObject; +import org.apache.qpid.util.FileUtils; + +import org.apache.log4j.Level; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.xml.Log4jEntityResolver; +import org.apache.log4j.xml.QpidLog4JConfigurator; +import org.apache.log4j.xml.QpidLog4JConfigurator.QpidLog4JSaxErrorHandler; +import org.apache.log4j.xml.QpidLog4JConfigurator.IllegalLoggerLevelException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +import javax.management.JMException; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + + +/** MBean class for BrokerLoggingManagerMBean. It implements all the management features exposed for managing logging. */ +@MBeanDescription("Logging Management Interface") +public class LoggingManagementMBean extends AMQManagedObject implements LoggingManagement +{ + + private static final Logger _logger = Logger.getLogger(LoggingManagementMBean.class); + private String _log4jConfigFileName; + private int _log4jLogWatchInterval; + private static final String INHERITED = "INHERITED"; + private static final String[] LEVELS = new String[]{Level.ALL.toString(), Level.TRACE.toString(), + Level.DEBUG.toString(), Level.INFO.toString(), + Level.WARN.toString(), Level.ERROR.toString(), + Level.FATAL.toString(),Level.OFF.toString(), + INHERITED}; + static TabularType _loggerLevelTabularType; + static CompositeType _loggerLevelCompositeType; + + static + { + try + { + OpenType[] loggerLevelItemTypes = new OpenType[]{SimpleType.STRING, SimpleType.STRING}; + + _loggerLevelCompositeType = new CompositeType("LoggerLevelList", "Logger Level Data", + COMPOSITE_ITEM_NAMES.toArray(new String[COMPOSITE_ITEM_NAMES.size()]), + COMPOSITE_ITEM_DESCRIPTIONS.toArray(new String[COMPOSITE_ITEM_DESCRIPTIONS.size()]), + loggerLevelItemTypes); + + _loggerLevelTabularType = new TabularType("LoggerLevel", "List of loggers with levels", + _loggerLevelCompositeType, + TABULAR_UNIQUE_INDEX.toArray(new String[TABULAR_UNIQUE_INDEX.size()])); + } + catch (OpenDataException e) + { + _logger.error("Tabular data setup for viewing logger levels was incorrect."); + _loggerLevelTabularType = null; + } + } + + public LoggingManagementMBean(String log4jConfigFileName, int log4jLogWatchInterval) throws JMException + { + super(LoggingManagement.class, LoggingManagement.TYPE); + _log4jConfigFileName = log4jConfigFileName; + _log4jLogWatchInterval = log4jLogWatchInterval; + } + + public String getObjectInstanceName() + { + return LoggingManagement.TYPE; + } + + public Integer getLog4jLogWatchInterval() + { + return _log4jLogWatchInterval; + } + + public String[] getAvailableLoggerLevels() + { + return LEVELS; + } + @SuppressWarnings("unchecked") + public synchronized boolean setRuntimeLoggerLevel(String logger, String level) + { + //check specified level is valid + Level newLevel; + try + { + newLevel = getLevel(level); + } + catch (Exception e) + { + return false; + } + + //check specified logger exists + Enumeration loggers = LogManager.getCurrentLoggers(); + Boolean loggerExists = false; + + while(loggers.hasMoreElements()) + { + Logger log = (Logger) loggers.nextElement(); + if (log.getName().equals(logger)) + { + loggerExists = true; + break; + } + } + + if(!loggerExists) + { + return false; + } + + //set the logger to the new level + _logger.info("Setting level to " + level + " for logger: " + logger); + + Logger log = Logger.getLogger(logger); + log.setLevel(newLevel); + + return true; + } + + @SuppressWarnings("unchecked") + public synchronized TabularData viewEffectiveRuntimeLoggerLevels() + { + if (_loggerLevelTabularType == null) + { + _logger.warn("TabluarData type not set up correctly"); + return null; + } + + _logger.info("Getting levels for currently active log4j loggers"); + + Enumeration loggers = LogManager.getCurrentLoggers(); + + TabularData loggerLevelList = new TabularDataSupport(_loggerLevelTabularType); + + Logger logger; + String loggerName; + String level; + + try + { + while(loggers.hasMoreElements()){ + logger = (Logger) loggers.nextElement(); + + loggerName = logger.getName(); + level = logger.getEffectiveLevel().toString(); + + Object[] itemData = {loggerName, level}; + CompositeData loggerData = new CompositeDataSupport(_loggerLevelCompositeType, + COMPOSITE_ITEM_NAMES.toArray(new String[COMPOSITE_ITEM_NAMES.size()]), itemData); + loggerLevelList.put(loggerData); + } + } + catch (OpenDataException e) + { + _logger.warn("Unable to create logger level list due to :" + e); + return null; + } + + return loggerLevelList; + + } + + public synchronized String getRuntimeRootLoggerLevel() + { + Logger rootLogger = Logger.getRootLogger(); + + return rootLogger.getLevel().toString(); + } + + public synchronized boolean setRuntimeRootLoggerLevel(String level) + { + Level newLevel; + try + { + newLevel = getLevel(level); + } + catch (Exception e) + { + return false; + } + + if(newLevel == null) + { + //A null Level reference implies inheritance. Setting the runtime RootLogger + //to null is catastrophic (and prevented by Log4J at startup and runtime anyway). + return false; + } + + _logger.info("Setting RootLogger level to " + level); + + Logger log = Logger.getRootLogger(); + log.setLevel(newLevel); + + return true; + } + + //method to convert from a string to a log4j Level, throws exception if the given value is invalid + private Level getLevel(String level) throws Exception + { + if("null".equalsIgnoreCase(level) || INHERITED.equalsIgnoreCase(level)) + { + //the string "null" or "inherited" signals to inherit from a parent logger, + //using a null Level reference for the logger. + return null; + } + + Level newLevel = Level.toLevel(level); + + //above Level.toLevel call returns a DEBUG Level if the request fails. Check the result. + if (newLevel.equals(Level.DEBUG) && !(level.equalsIgnoreCase("debug"))) + { + //received DEBUG but we did not ask for it, the Level request failed. + throw new Exception("Invalid level name"); + } + + return newLevel; + } + + //method to parse the XML configuration file, validating it in the process, and returning a DOM Document of the content. + private static synchronized Document parseConfigFile(String fileName) throws IOException + { + try + { + LOCK.lock(); + + //check file was specified, exists, and is readable + if(fileName == null) + { + _logger.warn("Provided log4j XML configuration filename is null"); + throw new IOException("Provided log4j XML configuration filename is null"); + } + + File configFile = new File(fileName); + + if (!configFile.exists()) + { + _logger.warn("The log4j XML configuration file could not be found: " + fileName); + throw new IOException("The log4j XML configuration file could not be found"); + } + else if (!configFile.canRead()) + { + _logger.warn("The log4j XML configuration file is not readable: " + fileName); + throw new IOException("The log4j XML configuration file is not readable"); + } + + //parse it + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder; + Document doc; + + ErrorHandler errHandler = new QpidLog4JSaxErrorHandler(); + try + { + docFactory.setValidating(true); + docBuilder = docFactory.newDocumentBuilder(); + docBuilder.setErrorHandler(errHandler); + docBuilder.setEntityResolver(new Log4jEntityResolver()); + doc = docBuilder.parse(fileName); + } + catch (ParserConfigurationException e) + { + _logger.warn("Unable to parse the log4j XML file due to possible configuration error: " + e); + //recommended that MBeans should use java.* and javax.* exceptions only + throw new IOException("Unable to parse the log4j XML file due to possible configuration error: " + e.getMessage()); + } + catch (SAXException e) + { + _logger.warn("The specified log4j XML file is invalid: " + e); + //recommended that MBeans should use standard java.* and javax.* exceptions only + throw new IOException("The specified log4j XML file is invalid: " + e.getMessage()); + } + catch (IOException e) + { + _logger.warn("Unable to parse the specified log4j XML file" + e); + throw new IOException("Unable to parse the specified log4j XML file: " + e.getMessage()); + } + + return doc; + } + finally + { + LOCK.unlock(); + } + } + + + private static synchronized boolean writeUpdatedConfigFile(String log4jConfigFileName, Document doc) throws IOException + { + try + { + LOCK.lock(); + + File log4jConfigFile = new File(log4jConfigFileName); + + if (!log4jConfigFile.canWrite()) + { + _logger.warn("Specified log4j XML configuration file is not writable: " + log4jConfigFile); + throw new IOException("Specified log4j XML configuration file is not writable"); + } + + Transformer transformer = null; + try + { + transformer = TransformerFactory.newInstance().newTransformer(); + } + catch (Exception e) + { + _logger.warn("Could not create an XML transformer: " +e); + return false; + } + + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "log4j.dtd"); + DOMSource source = new DOMSource(doc); + + File tmp; + Random r = new Random(); + do + { + tmp = new File(log4jConfigFile.getPath() + r.nextInt() + ".tmp"); + } + while(tmp.exists()); + + tmp.deleteOnExit(); + + try + { + StreamResult result = new StreamResult(tmp); + transformer.transform(source, result); + } + catch (TransformerException e) + { + _logger.warn("Could not transform the XML into new file: " +e); + throw new IOException("Could not transform the XML into new file: " +e); + } + + // Swap temp file in to replace existing configuration file. + File old = new File(log4jConfigFile.getAbsoluteFile() + ".old"); + if (old.exists()) + { + old.delete(); + } + + if(!log4jConfigFile.renameTo(old)) + { + //unable to rename the existing file to the backup name + _logger.error("Could not backup the existing log4j XML file"); + throw new IOException("Could not backup the existing log4j XML file"); + } + + if(!tmp.renameTo(log4jConfigFile)) + { + //failed to rename the new file to the required filename + + if(!old.renameTo(log4jConfigFile)) + { + //unable to return the backup to required filename + _logger.error("Could not rename the new log4j configuration file into place, and unable to restore original file"); + throw new IOException("Could not rename the new log4j configuration file into place, and unable to restore original file"); + } + + _logger.error("Could not rename the new log4j configuration file into place"); + throw new IOException("Could not rename the new log4j configuration file into place"); + } + + return true; + } + finally + { + LOCK.unlock(); + } + } + + + /* The log4j XML configuration file DTD defines three possible element + * combinations for specifying optional logger+level settings. + * Must account for the following: + * + * <category name="x"> <priority value="y"/> </category> OR + * <category name="x"> <level value="y"/> </category> OR + * <logger name="x"> <level value="y"/> </logger> + * + * Noting also that the level/priority child element is optional too, + * and not the only possible child element. + */ + + public static synchronized Map<String,String> retrieveConfigFileLoggersLevels(String fileName) throws IOException + { + try + { + LOCK.lock(); + + Document doc = parseConfigFile(fileName); + + HashMap<String,String> loggerLevelList = new HashMap<String,String>(); + + //retrieve the 'category' element nodes + NodeList categoryElements = doc.getElementsByTagName("category"); + + String categoryName; + String priority = null; + + for (int i = 0; i < categoryElements.getLength(); i++) + { + Element categoryElement = (Element) categoryElements.item(i); + categoryName = categoryElement.getAttribute("name"); + + //retrieve the category's mandatory 'priority' or 'level' element's value. + //It may not be the only child node, so request by tag name. + NodeList priorityElements = categoryElement.getElementsByTagName("priority"); + NodeList levelElements = categoryElement.getElementsByTagName("level"); + + if (priorityElements.getLength() != 0) + { + Element priorityElement = (Element) priorityElements.item(0); + priority = priorityElement.getAttribute("value"); + } + else if (levelElements.getLength() != 0) + { + Element levelElement = (Element) levelElements.item(0); + priority = levelElement.getAttribute("value"); + } + else + { + //there is no exiting priority or level to view, move onto next category/logger + continue; + } + + loggerLevelList.put(categoryName, priority); + } + + //retrieve the 'logger' element nodes + NodeList loggerElements = doc.getElementsByTagName("logger"); + + String loggerName; + String level; + + for (int i = 0; i < loggerElements.getLength(); i++) + { + Element loggerElement = (Element) loggerElements.item(i); + loggerName = loggerElement.getAttribute("name"); + + //retrieve the logger's mandatory 'level' element's value + //It may not be the only child node, so request by tag name. + NodeList levelElements = loggerElement.getElementsByTagName("level"); + + Element levelElement = (Element) levelElements.item(0); + level = levelElement.getAttribute("value"); + + loggerLevelList.put(loggerName, level); + } + + return loggerLevelList; + } + finally + { + LOCK.unlock(); + } + } + + public synchronized TabularData viewConfigFileLoggerLevels() throws IOException + { + try + { + LOCK.lock(); + + if (_loggerLevelTabularType == null) + { + _logger.warn("TabluarData type not set up correctly"); + return null; + } + + _logger.info("Getting logger levels from log4j configuration file"); + + TabularData loggerLevelList = new TabularDataSupport(_loggerLevelTabularType); + + Map<String,String> levels = retrieveConfigFileLoggersLevels(_log4jConfigFileName); + + for (Map.Entry<String,String> entry : levels.entrySet()) + { + String loggerName = entry.getKey(); + String level = entry.getValue(); + + try + { + Object[] itemData = {loggerName, level.toUpperCase()}; + CompositeData loggerData = new CompositeDataSupport(_loggerLevelCompositeType, + COMPOSITE_ITEM_NAMES.toArray(new String[COMPOSITE_ITEM_NAMES.size()]), itemData); + loggerLevelList.put(loggerData); + } + catch (OpenDataException e) + { + _logger.warn("Unable to create logger level list due to :" + e); + return null; + } + } + + return loggerLevelList; + } + finally + { + LOCK.unlock(); + } + } + + public synchronized boolean setConfigFileLoggerLevel(String logger, String level) throws IOException + { + try + { + LOCK.lock(); + + //check that the specified level is a valid log4j Level + try + { + getLevel(level); + } + catch (Exception e) + { + //it isnt a valid level + return false; + } + + _logger.info("Setting level to " + level + " for logger '" + logger + + "' in log4j xml configuration file: " + _log4jConfigFileName); + + Document doc = parseConfigFile(_log4jConfigFileName); + + //retrieve the 'category' and 'logger' element nodes + NodeList categoryElements = doc.getElementsByTagName("category"); + NodeList loggerElements = doc.getElementsByTagName("logger"); + + //collect them into a single elements list + List<Element> logElements = new ArrayList<Element>(); + + for (int i = 0; i < categoryElements.getLength(); i++) + { + logElements.add((Element) categoryElements.item(i)); + } + for (int i = 0; i < loggerElements.getLength(); i++) + { + logElements.add((Element) loggerElements.item(i)); + } + + //try to locate the specified logger/category in the elements retrieved + Element logElement = null; + for (Element e : logElements) + { + if (e.getAttribute("name").equals(logger)) + { + logElement = e; + break; + } + } + + if (logElement == null) + { + //no loggers/categories with given name found, does not exist to update + _logger.warn("Specified logger does not exist in the configuration file: " +logger); + return false; + } + + //retrieve the optional 'priority' or 'level' sub-element value. + //It may not be the only child node, so request by tag name. + NodeList priorityElements = logElement.getElementsByTagName("priority"); + NodeList levelElements = logElement.getElementsByTagName("level"); + + Element levelElement = null; + if (priorityElements.getLength() != 0) + { + levelElement = (Element) priorityElements.item(0); + } + else if (levelElements.getLength() != 0) + { + levelElement = (Element) levelElements.item(0); + } + else + { + //there is no exiting priority or level element to update + return false; + } + + //update the element with the new level/priority + levelElement.setAttribute("value", level.toLowerCase()); + + //output the new file + return writeUpdatedConfigFile(_log4jConfigFileName, doc); + } + finally + { + LOCK.unlock(); + } + } + + + /* The log4j XML configuration file DTD defines 2 possible element + * combinations for specifying the optional root logger level settings + * Must account for the following: + * + * <root> <priority value="y"/> </root> OR + * <root> <level value="y"/> </root> + * + * Noting also that the level/priority child element is optional too, + * and not the only possible child element. + */ + + public static synchronized String retrieveConfigFileRootLoggerLevel(String fileName) throws IOException + { + try + { + LOCK.lock(); + + Document doc = parseConfigFile(fileName); + + //retrieve the optional 'root' element node + NodeList rootElements = doc.getElementsByTagName("root"); + + if (rootElements.getLength() == 0) + { + //there is no root logger definition + return "N/A"; + } + + Element rootElement = (Element) rootElements.item(0); + + //retrieve the optional 'priority' or 'level' element value. + //It may not be the only child node, so request by tag name. + NodeList priorityElements = rootElement.getElementsByTagName("priority"); + NodeList levelElements = rootElement.getElementsByTagName("level"); + String priority = null; + + if (priorityElements.getLength() != 0) + { + Element priorityElement = (Element) priorityElements.item(0); + priority = priorityElement.getAttribute("value"); + } + else if(levelElements.getLength() != 0) + { + Element levelElement = (Element) levelElements.item(0); + priority = levelElement.getAttribute("value"); + } + + if(priority != null) + { + return priority; + } + else + { + return "N/A"; + } + } + finally + { + LOCK.unlock(); + } + } + + public synchronized String getConfigFileRootLoggerLevel() throws IOException + { + return retrieveConfigFileRootLoggerLevel(_log4jConfigFileName).toUpperCase(); + } + + public synchronized boolean setConfigFileRootLoggerLevel(String level) throws IOException + { + try + { + LOCK.lock(); + + //check that the specified level is a valid log4j Level + try + { + Level newLevel = getLevel(level); + if(newLevel == null) + { + //A null Level reference implies inheritance. Setting the config file RootLogger + //to "null" or "inherited" just ensures it defaults to DEBUG at startup as Log4J + //prevents this catastrophic situation at startup and runtime anyway. + return false; + } + } + catch (Exception e) + { + //it isnt a valid level + return false; + } + + _logger.info("Setting level to " + level + " for the Root logger in " + + "log4j xml configuration file: " + _log4jConfigFileName); + + Document doc = parseConfigFile(_log4jConfigFileName); + + //retrieve the optional 'root' element node + NodeList rootElements = doc.getElementsByTagName("root"); + + if (rootElements.getLength() == 0) + { + return false; + } + + Element rootElement = (Element) rootElements.item(0); + + //retrieve the optional 'priority' or 'level' sub-element value. + //It may not be the only child node, so request by tag name. + NodeList priorityElements = rootElement.getElementsByTagName("priority"); + NodeList levelElements = rootElement.getElementsByTagName("level"); + + Element levelElement = null; + if (priorityElements.getLength() != 0) + { + levelElement = (Element) priorityElements.item(0); + } + else if (levelElements.getLength() != 0) + { + levelElement = (Element) levelElements.item(0); + } + else + { + //there is no exiting priority/level to update + return false; + } + + //update the element with the new level/priority + levelElement.setAttribute("value", level); + + //output the new file + return writeUpdatedConfigFile(_log4jConfigFileName, doc); + } + finally + { + LOCK.unlock(); + } + } + + public synchronized void reloadConfigFile() throws IOException + { + try + { + LOCK.lock(); + + QpidLog4JConfigurator.configure(_log4jConfigFileName); + _logger.info("Applied log4j configuration from: " + _log4jConfigFileName); + } + catch (IllegalLoggerLevelException e) + { + _logger.warn("The log4j configuration reload request was aborted: " + e); + //recommended that MBeans should use standard java.* and javax.* exceptions only + throw new IOException("The log4j configuration reload request was aborted: " + e.getMessage()); + } + catch (ParserConfigurationException e) + { + _logger.warn("The log4j configuration reload request was aborted: " + e); + throw new IOException("The log4j configuration reload request was aborted: " + e.getMessage()); + } + catch (SAXException e) + { + _logger.warn("The log4j configuration reload request was aborted: " + e); + //recommended that MBeans should use standard java.* and javax.* exceptions only + throw new IOException("The log4j configuration reload request was aborted: " + e.getMessage()); + } + catch (IOException e) + { + _logger.warn("The log4j configuration reload request was aborted: " + e); + throw new IOException("The log4j configuration reload request was aborted: " + e.getMessage()); + } + finally + { + LOCK.unlock(); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Binding_logmessages.properties b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Binding_logmessages.properties new file mode 100644 index 0000000000..808ec7918f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Binding_logmessages.properties @@ -0,0 +1,22 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# +# Default File used for all non-defined locales. +# +CREATED = BND-1001 : Create[ : Arguments : {0}] +DELETED = BND-1002 : Deleted diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Broker_logmessages.properties b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Broker_logmessages.properties new file mode 100644 index 0000000000..5d1e85fe41 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Broker_logmessages.properties @@ -0,0 +1,38 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# +# Default File used for all non-defined locales. + +# 0 - Version +# 1 = Build +STARTUP = BRK-1001 : Startup : Version: {0} Build: {1} +# 0 - Transport +# 1 - Port +LISTENING = BRK-1002 : Starting : Listening on {0} port {1,number,#} +# 0 - Transport +# 1 - Port +SHUTTING_DOWN = BRK-1003 : Shuting down : {0} port {1,number,#} +READY = BRK-1004 : Qpid Broker Ready +STOPPED = BRK-1005 : Stopped +# 0 - path +CONFIG = BRK-1006 : Using configuration : {0} +# 0 - path +LOG_CONFIG = BRK-1007 : Using logging configuration : {0} + +STATS_DATA = BRK-1008 : {0,choice,0#delivered|1#received} : {1,number,#.###} kB/s peak : {2,number,#} bytes total +STATS_MSGS = BRK-1009 : {0,choice,0#delivered|1#received} : {1,number,#.###} msg/s peak : {2,number,#} msgs total
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Channel_logmessages.properties b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Channel_logmessages.properties new file mode 100644 index 0000000000..ed8c0d0ce9 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Channel_logmessages.properties @@ -0,0 +1,34 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# +# Default File used for all non-defined locales. + +CREATE = CHN-1001 : Create +# 0 - flow +FLOW = CHN-1002 : Flow {0} +CLOSE = CHN-1003 : Close +# 0 - bytes allowed in prefetch +# 1 - number of messagse. +PREFETCH_SIZE = CHN-1004 : Prefetch Size (bytes) {0,number} : Count {1,number} +# 0 - queue causing flow control +FLOW_ENFORCED = CHN-1005 : Flow Control Enforced (Queue {0}) +FLOW_REMOVED = CHN-1006 : Flow Control Removed +# Channel Transactions +# 0 - time in milliseconds +OPEN_TXN = CHN-1007 : Open Transaction : {0,number} ms +IDLE_TXN = CHN-1008 : Idle Transaction : {0,number} ms diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/ConfigStore_logmessages.properties b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/ConfigStore_logmessages.properties new file mode 100644 index 0000000000..3bc5074777 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/ConfigStore_logmessages.properties @@ -0,0 +1,27 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# +# Default File used for all non-defined locales. + +# 0 - name +CREATED = CFG-1001 : Created : {0} +# 0 - path +STORE_LOCATION = CFG-1002 : Store location : {0} +CLOSE = CFG-1003 : Closed +RECOVERY_START = CFG-1004 : Recovery Start +RECOVERY_COMPLETE = CFG-1005 : Recovery Complete diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Connection_logmessages.properties b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Connection_logmessages.properties new file mode 100644 index 0000000000..81ae6f3bd0 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Connection_logmessages.properties @@ -0,0 +1,24 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# +# Default File used for all non-defined locales. + +# 0 - Client id +# 1 - Protocol Version +OPEN = CON-1001 : Open[ : Client ID : {0}][ : Protocol Version : {1}] +CLOSE = CON-1002 : Close
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Exchange_logmessages.properties b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Exchange_logmessages.properties new file mode 100644 index 0000000000..b9890d9f27 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Exchange_logmessages.properties @@ -0,0 +1,24 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# +# Default File used for all non-defined locales. + +# 0 - type +# 1 - name +CREATED = EXH-1001 : Create :[ Durable] Type: {0} Name: {1} +DELETED = EXH-1002 : Deleted
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/ManagementConsole_logmessages.properties b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/ManagementConsole_logmessages.properties new file mode 100644 index 0000000000..ab77476da2 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/ManagementConsole_logmessages.properties @@ -0,0 +1,33 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# +# Default File used for all non-defined locales. +# +STARTUP = MNG-1001 : Startup +# 0 - Service +# 1 - Port +LISTENING = MNG-1002 : Starting : {0} : Listening on port {1,number,#} +# 0 - Service +# 1 - Port +SHUTTING_DOWN = MNG-1003 : Shutting down : {0} : port {1,number,#} +READY = MNG-1004 : Ready[ : Using the platform JMX Agent] +STOPPED = MNG-1005 : Stopped +# 0 - Path +SSL_KEYSTORE = MNG-1006 : Using SSL Keystore : {0} +OPEN = MNG-1007 : Open : User {0} +CLOSE = MNG-1008 : Close
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/MessageStore_logmessages.properties b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/MessageStore_logmessages.properties new file mode 100644 index 0000000000..a2cedeb22a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/MessageStore_logmessages.properties @@ -0,0 +1,28 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# +# Default File used for all non-defined locales. +# +# 0 - name +CREATED = MST-1001 : Created : {0} +# 0 - path +STORE_LOCATION = MST-1002 : Store location : {0} +CLOSED = MST-1003 : Closed +RECOVERY_START = MST-1004 : Recovery Start +RECOVERED = MST-1005 : Recovered {0,number} messages +RECOVERY_COMPLETE = MST-1006 : Recovery Complete
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Queue_logmessages.properties b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Queue_logmessages.properties new file mode 100644 index 0000000000..538bf994ea --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Queue_logmessages.properties @@ -0,0 +1,26 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# +# Default File used for all non-defined locales. +# +# 0 - owner +# 1 - priority +CREATED = QUE-1001 : Create :[ Owner: {0}][ AutoDelete][ Durable][ Transient][ Priority: {1,number,#}] +DELETED = QUE-1002 : Deleted +OVERFULL = QUE-1003 : Overfull : Size : {0,number} bytes, Capacity : {1,number} +UNDERFULL = QUE-1004 : Underfull : Size : {0,number} bytes, Resume Capacity : {1,number} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Subscription_logmessages.properties b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Subscription_logmessages.properties new file mode 100644 index 0000000000..ef5f885b50 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/Subscription_logmessages.properties @@ -0,0 +1,24 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# +# Default File used for all non-defined locales. +# +CREATE = SUB-1001 : Create[ : Durable][ : Arguments : {0}] +CLOSE = SUB-1002 : Close +# 0 - The current subscription state +STATE = SUB-1003 : State : {0}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/TransactionLog_logmessages.properties b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/TransactionLog_logmessages.properties new file mode 100644 index 0000000000..fadc2e2098 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/TransactionLog_logmessages.properties @@ -0,0 +1,33 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# +# Default File used for all non-defined locales. +# +# +# 0 - name +CREATED = TXN-1001 : Created : {0} +# 0 - path +STORE_LOCATION = TXN-1002 : Store location : {0} +CLOSED = TXN-1003 : Closed +# 0 - queue name +RECOVERY_START = TXN-1004 : Recovery Start[ : {0}] +# 0 - count +# 1 - queue count +RECOVERED = TXN-1005 : Recovered {0,number} messages for queue {1} +# 0 - queue name +RECOVERY_COMPLETE = TXN-1006 : Recovery Complete[ : {0}] diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/VirtualHost_logmessages.properties b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/VirtualHost_logmessages.properties new file mode 100644 index 0000000000..3e640c7929 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/messages/VirtualHost_logmessages.properties @@ -0,0 +1,26 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# +# Default File used for all non-defined locales. +# +# 0 - name +CREATED = VHT-1001 : Created : {0} +CLOSED = VHT-1002 : Closed + +STATS_DATA = VHT-1003 : {0} : {1,choice,0#delivered|1#received} : {2,number,#.###} kB/s peak : {3,number,#} bytes total +STATS_MSGS = VHT-1004 : {0} : {1,choice,0#delivered|1#received} : {2,number,#.###} msg/s peak : {3,number,#} msgs total`
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/AbstractLogSubject.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/AbstractLogSubject.java new file mode 100644 index 0000000000..779db01601 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/AbstractLogSubject.java @@ -0,0 +1,63 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging.subjects; + +import org.apache.qpid.server.logging.LogSubject; + +import java.text.MessageFormat; + +/** + * The LogSubjects all have a similar requriement to format their output and + * provide the String value. + * + * This Abstract LogSubject provides this basic functionality, allowing the + * actual LogSubjects to provide their formating and data. + */ +public abstract class AbstractLogSubject implements LogSubject +{ + /** + * The logString that will be returned via toLogString + */ + protected String _logString; + + /** + * Set the toString logging of this LogSubject. Based on a format provided + * by format and the var args. + * @param format The Message to format + * @param args The values to put in to the message. + */ + protected void setLogStringWithFormat(String format, Object... args) + { + _logString = "[" + MessageFormat.format(format, args) + "] "; + } + + /** + * toLogString is how the Logging infrastructure will get the text for this + * LogSubject + * + * @return String representing this LogSubject + */ + public String toLogString() + { + return _logString; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/BindingLogSubject.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/BindingLogSubject.java new file mode 100644 index 0000000000..088b59ae68 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/BindingLogSubject.java @@ -0,0 +1,49 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging.subjects; + +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.queue.AMQQueue; +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.BINDING_FORMAT; + +public class BindingLogSubject extends AbstractLogSubject +{ + + /** + * Create a BindingLogSubject that Logs in the following format. + * + * [ vh(/)/ex(amq.direct)/qu(testQueue)/bd(testQueue) ] + * + * @param routingKey + * @param exchange + * @param queue + */ + public BindingLogSubject(String routingKey, Exchange exchange, + AMQQueue queue) + { + setLogStringWithFormat(BINDING_FORMAT, queue.getVirtualHost().getName(), + exchange.getTypeShortString(), + exchange.getNameShortString(), + queue.getNameShortString(), + routingKey); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/ChannelLogSubject.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/ChannelLogSubject.java new file mode 100644 index 0000000000..f28873940b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/ChannelLogSubject.java @@ -0,0 +1,56 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging.subjects; + +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.CHANNEL_FORMAT; + +public class ChannelLogSubject extends AbstractLogSubject +{ + + public ChannelLogSubject(AMQChannel channel) + { + AMQProtocolSession session = channel.getProtocolSession(); + + /** + * LOG FORMAT used by the AMQPConnectorActor follows + * ChannelLogSubject.CHANNEL_FORMAT : + * con:{0}({1}@{2}/{3})/ch:{4} + * + * Uses a MessageFormat call to insert the required values according to + * these indices: + * + * 0 - Connection ID + * 1 - User ID + * 2 - IP + * 3 - Virtualhost + * 4 - Channel ID + */ + setLogStringWithFormat(CHANNEL_FORMAT, + session.getSessionID(), + session.getPrincipal().getName(), + session.getRemoteAddress(), + session.getVirtualHost().getName(), + channel.getChannelId()); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/ConnectionLogSubject.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/ConnectionLogSubject.java new file mode 100644 index 0000000000..a697029d24 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/ConnectionLogSubject.java @@ -0,0 +1,107 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging.subjects; + +import org.apache.qpid.server.protocol.AMQProtocolSession; + +import java.text.MessageFormat; + +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.SOCKET_FORMAT; +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.USER_FORMAT; +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.CONNECTION_FORMAT; + +/** The Connection LogSubject */ +public class ConnectionLogSubject extends AbstractLogSubject +{ + + public ConnectionLogSubject(AMQProtocolSession session) + { + _session = session; + } + + // The Session this Actor is representing + private AMQProtocolSession _session; + + // Used to stop re-creating the _logString when we reach our final format + private boolean _upToDate = false; + + /** + * Update the LogString as the Connection process proceeds. + * + * When the Session has an authorized ID add that to the string. + * + * When the Session then gains a Vhost add that to the string, at this point + * we can set upToDate = true as the _logString will not need to be updated + * from this point onwards. + */ + private void updateLogString() + { + if (!_upToDate) + { + if (_session.getPrincipal() != null) + { + if (_session.getVirtualHost() != null) + { + /** + * LOG FORMAT used by the AMQPConnectorActor follows + * ConnectionLogSubject.CONNECTION_FORMAT : + * con:{0}({1}@{2}/{3}) + * + * Uses a MessageFormat call to insert the required values + * according to these indices: + * + * 0 - Connection ID 1 - User ID 2 - IP 3 - Virtualhost + */ + _logString = "[" + MessageFormat.format(CONNECTION_FORMAT, + _session.getSessionID(), + _session.getPrincipal().getName(), + _session.getRemoteAddress(), + _session.getVirtualHost().getName()) + + "] "; + + _upToDate = true; + } + else + { + _logString = "[" + MessageFormat.format(USER_FORMAT, + _session.getSessionID(), + _session.getPrincipal().getName(), + _session.getRemoteAddress()) + + "] "; + + } + } + else + { + _logString = "[" + MessageFormat.format(SOCKET_FORMAT, + _session.getSessionID(), + _session.getRemoteAddress()) + + "] "; + } + } + } + + public String toLogString() + { + updateLogString(); + return super.toLogString(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/ExchangeLogSubject.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/ExchangeLogSubject.java new file mode 100644 index 0000000000..6ab44a92b9 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/ExchangeLogSubject.java @@ -0,0 +1,36 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging.subjects; + +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.virtualhost.VirtualHost; +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.EXCHANGE_FORMAT; + +public class ExchangeLogSubject extends AbstractLogSubject +{ + + /** Create an ExchangeLogSubject that Logs in the following format. */ + public ExchangeLogSubject(Exchange exchange, VirtualHost vhost) + { + setLogStringWithFormat(EXCHANGE_FORMAT, vhost.getName(), + exchange.getTypeShortString(), exchange.getNameShortString()); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/LogSubjectFormat.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/LogSubjectFormat.java new file mode 100644 index 0000000000..ff2bb90140 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/LogSubjectFormat.java @@ -0,0 +1,107 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging.subjects; + +/** + * LogSubjectFormat class contains a list of formatting string + * that can be statically imported where needed. + * The formatting strings are to be used via a MessageFormat call + * to insert the required values at the corresponding place holder + * indices. + * + */ + +public class LogSubjectFormat +{ + + /** + * LOG FORMAT for the Subscription Log Subject + * 0 - Subscription ID + */ + public static final String SUBSCRIPTION_FORMAT = "sub:{0}"; + + /** + * LOG FORMAT for Connection Log Subject - SOCKET format + * 0 - Connection ID + * 1 - Remote Address + */ + public static final String SOCKET_FORMAT = "con:{0}({1})"; + + /** + * LOG FORMAT for Connection Log Subject - USER format + * 0 - Connection ID + * 1 - User ID + * 2 - IP + */ + public static final String USER_FORMAT = "con:{0}({1}@{2})"; + + /** + * LOG FORMAT for the Connection Log Subject - CON format + * 0 - Connection ID + * 1 - User ID + * 2 - IP + * 3 - Virtualhost + */ + public static final String CONNECTION_FORMAT = "con:{0}({1}@{2}/{3})"; + + /** + * LOG FORMAT for the Channel LogSubject + * 0 - Connection ID + * 1 - User ID + * 2 - IP + * 3 - Virtualhost + * 4 - Channel ID + */ + public static final String CHANNEL_FORMAT = CONNECTION_FORMAT + "/ch:{4}"; + + /** + * LOG FORMAT for the Exchange LogSubject, + * 0 - Virtualhost Name + * 1 - Exchange Type + * 2 - Exchange Name + */ + public static final String EXCHANGE_FORMAT = "vh(/{0})/ex({1}/{2})"; + + /** + * LOG FORMAT for a Binding LogSubject + * 0 - Virtualhost Name + * 1 - Exchange Type + * 2 - Exchange Name + * 3 - Queue Name + * 4 - Binding RoutingKey + */ + public static final String BINDING_FORMAT = "vh(/{0})/ex({1}/{2})/qu({3})/rk({4})"; + + /** + * LOG FORMAT for the MessagesStore LogSubject + * 0 - Virtualhost Name + * 1 - Message Store Type + */ + public static final String STORE_FORMAT = "vh(/{0})/ms({1})"; + + /** + * LOG FORMAT for the Queue LogSubject, + * 0 - Virtualhost name + * 1 - queue name + */ + public static final String QUEUE_FORMAT = "vh(/{0})/qu({1})"; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/MessageStoreLogSubject.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/MessageStoreLogSubject.java new file mode 100644 index 0000000000..3fce13bcb5 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/MessageStoreLogSubject.java @@ -0,0 +1,36 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging.subjects; + +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.store.MessageStore; +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.STORE_FORMAT; + +public class MessageStoreLogSubject extends AbstractLogSubject +{ + + /** Create an ExchangeLogSubject that Logs in the following format. */ + public MessageStoreLogSubject(VirtualHost vhost, MessageStore store) + { + setLogStringWithFormat(STORE_FORMAT, vhost.getName(), + store.getClass().getSimpleName()); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/QueueLogSubject.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/QueueLogSubject.java new file mode 100644 index 0000000000..bfe12f1a60 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/QueueLogSubject.java @@ -0,0 +1,36 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging.subjects; + +import org.apache.qpid.server.queue.AMQQueue; +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.QUEUE_FORMAT; + +public class QueueLogSubject extends AbstractLogSubject +{ + + /** Create an QueueLogSubject that Logs in the following format. */ + public QueueLogSubject(AMQQueue queue) + { + setLogStringWithFormat(QUEUE_FORMAT, + queue.getVirtualHost().getName(), + queue.getName()); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/SubscriptionLogSubject.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/SubscriptionLogSubject.java new file mode 100644 index 0000000000..8b57647046 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/logging/subjects/SubscriptionLogSubject.java @@ -0,0 +1,55 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.logging.subjects; + +import org.apache.qpid.server.subscription.Subscription; + +import java.text.MessageFormat; + +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.SUBSCRIPTION_FORMAT; + +public class SubscriptionLogSubject extends AbstractLogSubject +{ + + /** + * Create an QueueLogSubject that Logs in the following format. + * + * @param subscription + */ + public SubscriptionLogSubject(Subscription subscription) + { + // Delegate the formating of the Queue to the QueueLogSubject. So final + // log string format is: + // [ sub:<id>(vh(<vhost>)/qu(<queue>)) ] + + String queueString = new QueueLogSubject(subscription.getQueue()).toLogString(); + + _logString = "[" + MessageFormat.format(SUBSCRIPTION_FORMAT, + subscription.getSubscriptionID()) + + "(" + // queueString is [vh(/{0})/qu({1}) ] so need to trim + // ^ ^^ + + queueString.substring(1,queueString.length() - 3) + + ")" + + "] "; + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/AMQManagedObject.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/AMQManagedObject.java new file mode 100644 index 0000000000..c4ffcd26bf --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/AMQManagedObject.java @@ -0,0 +1,106 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.management; + +import org.apache.qpid.server.logging.actors.ManagementActor; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.LogActor; + +import javax.management.ListenerNotFoundException; +import javax.management.MBeanInfo; +import javax.management.MBeanNotificationInfo; +import javax.management.NotCompliantMBeanException; +import javax.management.NotificationBroadcaster; +import javax.management.NotificationBroadcasterSupport; +import javax.management.NotificationFilter; +import javax.management.NotificationListener; + +/** + * This class provides additinal feature of Notification Broadcaster to the + * DefaultManagedObject. + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +public abstract class AMQManagedObject extends DefaultManagedObject + implements NotificationBroadcaster +{ + /** + * broadcaster support class + */ + protected NotificationBroadcasterSupport _broadcaster = new NotificationBroadcasterSupport(); + + /** + * sequence number for notifications + */ + protected long _notificationSequenceNumber = 0; + + protected MBeanInfo _mbeanInfo; + + protected LogActor _logActor; + + protected AMQManagedObject(Class<?> managementInterface, String typeName) + throws NotCompliantMBeanException + { + super(managementInterface, typeName); + // CurrentActor will be defined as these objects are created during + // broker startup. + _logActor = new ManagementActor(CurrentActor.get().getRootMessageLogger()); + buildMBeanInfo(); + } + + @Override + public MBeanInfo getMBeanInfo() + { + return _mbeanInfo; + } + + private void buildMBeanInfo() throws NotCompliantMBeanException + { + _mbeanInfo = new MBeanInfo(this.getClass().getName(), + MBeanIntrospector.getMBeanDescription(this.getClass()), + MBeanIntrospector.getMBeanAttributesInfo(getManagementInterface()), + MBeanIntrospector.getMBeanConstructorsInfo(this.getClass()), + MBeanIntrospector.getMBeanOperationsInfo(getManagementInterface()), + this.getNotificationInfo()); + } + + + + // notification broadcaster implementation + + public void addNotificationListener(NotificationListener listener, + NotificationFilter filter, + Object handback) + { + _broadcaster.addNotificationListener(listener, filter, handback); + } + + public void removeNotificationListener(NotificationListener listener) + throws ListenerNotFoundException + { + _broadcaster.removeNotificationListener(listener); + } + + public MBeanNotificationInfo[] getNotificationInfo() + { + return null; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/DefaultManagedObject.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/DefaultManagedObject.java new file mode 100644 index 0000000000..7924964fdf --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/DefaultManagedObject.java @@ -0,0 +1,156 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.management; + +import javax.management.JMException; +import javax.management.MalformedObjectNameException; +import javax.management.NotCompliantMBeanException; +import javax.management.ObjectName; +import javax.management.StandardMBean; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.registry.ApplicationRegistry; + +/** + * Provides implementation of the boilerplate ManagedObject interface. Most managed objects should find it useful + * to extend this class rather than implementing ManagedObject from scratch. + * + */ +public abstract class DefaultManagedObject extends StandardMBean implements ManagedObject +{ + private Class<?> _managementInterface; + + private String _typeName; + + protected DefaultManagedObject(Class<?> managementInterface, String typeName) + throws NotCompliantMBeanException + { + super(managementInterface); + _managementInterface = managementInterface; + _typeName = typeName; + } + + public String getType() + { + return _typeName; + } + + public Class<?> getManagementInterface() + { + return _managementInterface; + } + + public ManagedObject getParentObject() + { + return null; + } + + public void register() throws JMException + { + getManagedObjectRegistry().registerObject(this); + } + + protected ManagedObjectRegistry getManagedObjectRegistry() + { + return ApplicationRegistry.getInstance().getManagedObjectRegistry(); + } + + public void unregister() throws AMQException + { + try + { + getManagedObjectRegistry().unregisterObject(this); + } + catch (JMException e) + { + throw new AMQException("Error unregistering managed object: " + this + ": " + e, e); + } + } + + public String toString() + { + return getObjectInstanceName() + "[" + getType() + "]"; + } + + + /** + * Created the ObjectName as per the JMX Specs + * @return ObjectName + * @throws MalformedObjectNameException + */ + public ObjectName getObjectName() throws MalformedObjectNameException + { + String name = getObjectInstanceName(); + StringBuffer objectName = new StringBuffer(ManagedObject.DOMAIN); + + objectName.append(":type="); + objectName.append(getHierarchicalType(this)); + + objectName.append(","); + objectName.append(getHierarchicalName(this)); + objectName.append("name=").append(name); + + return new ObjectName(objectName.toString()); + } + + protected ObjectName getObjectNameForSingleInstanceMBean() throws MalformedObjectNameException + { + StringBuffer objectName = new StringBuffer(ManagedObject.DOMAIN); + + objectName.append(":type="); + objectName.append(getHierarchicalType(this)); + + String hierarchyName = getHierarchicalName(this); + if (hierarchyName != null) + { + objectName.append(","); + objectName.append(hierarchyName.substring(0, hierarchyName.lastIndexOf(","))); + } + + return new ObjectName(objectName.toString()); + } + + protected String getHierarchicalType(ManagedObject obj) + { + if (obj.getParentObject() != null) + { + String parentType = getHierarchicalType(obj.getParentObject()).toString(); + return parentType + "." + obj.getType(); + } + else + return obj.getType(); + } + + protected String getHierarchicalName(ManagedObject obj) + { + if (obj.getParentObject() != null) + { + String parentName = obj.getParentObject().getType() + "=" + + obj.getParentObject().getObjectInstanceName() + ","+ + getHierarchicalName(obj.getParentObject()); + + return parentName; + } + else + return ""; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java new file mode 100644 index 0000000000..0334a856c1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/JMXManagedObjectRegistry.java @@ -0,0 +1,447 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.management; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.security.auth.rmi.RMIPasswordAuthenticator; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.ManagementConsoleMessages; + +import javax.management.JMException; +import javax.management.MBeanServer; +import javax.management.MBeanServerFactory; +import javax.management.ObjectName; +import javax.management.NotificationListener; +import javax.management.NotificationFilterSupport; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXServiceURL; +import javax.management.remote.MBeanServerForwarder; +import javax.management.remote.JMXConnectionNotification; +import javax.management.remote.rmi.RMIConnectorServer; +import javax.management.remote.rmi.RMIJRMPServerImpl; +import javax.management.remote.rmi.RMIServerImpl; +import javax.rmi.ssl.SslRMIClientSocketFactory; +import javax.rmi.ssl.SslRMIServerSocketFactory; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.lang.reflect.Proxy; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; +import java.rmi.AlreadyBoundException; +import java.rmi.NoSuchObjectException; +import java.rmi.NotBoundException; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.rmi.server.RMIClientSocketFactory; +import java.rmi.server.RMIServerSocketFactory; +import java.rmi.server.UnicastRemoteObject; +import java.util.HashMap; +import java.util.Map; + +/** + * This class starts up an MBeanserver. If out of the box agent has been enabled then there are no + * security features implemented like user authentication and authorisation. + */ +public class JMXManagedObjectRegistry implements ManagedObjectRegistry +{ + private static final Logger _log = Logger.getLogger(JMXManagedObjectRegistry.class); + + public static final String MANAGEMENT_PORT_CONFIG_PATH = "management.jmxport"; + public static final int MANAGEMENT_PORT_DEFAULT = 8999; + public static final int PORT_EXPORT_OFFSET = 100; + + private final MBeanServer _mbeanServer; + private JMXConnectorServer _cs; + private Registry _rmiRegistry; + private boolean _useCustomSocketFactory; + + public JMXManagedObjectRegistry() throws AMQException + { + _log.info("Initialising managed object registry using platform MBean server"); + IApplicationRegistry appRegistry = ApplicationRegistry.getInstance(); + + // Retrieve the config parameters + _useCustomSocketFactory = appRegistry.getConfiguration().getUseCustomRMISocketFactory(); + boolean platformServer = appRegistry.getConfiguration().getPlatformMbeanserver(); + + _mbeanServer = + platformServer ? ManagementFactory.getPlatformMBeanServer() + : MBeanServerFactory.createMBeanServer(ManagedObject.DOMAIN); + } + + + public void start() throws IOException, ConfigurationException + { + + CurrentActor.get().message(ManagementConsoleMessages.STARTUP()); + + //check if system properties are set to use the JVM's out-of-the-box JMXAgent + if (areOutOfTheBoxJMXOptionsSet()) + { + CurrentActor.get().message(ManagementConsoleMessages.READY(true)); + return; + } + + IApplicationRegistry appRegistry = ApplicationRegistry.getInstance(); + int port = appRegistry.getConfiguration().getJMXManagementPort(); + + //retrieve the Principal Database assigned to JMX authentication duties + String jmxDatabaseName = appRegistry.getConfiguration().getJMXPrincipalDatabase(); + Map<String, PrincipalDatabase> map = appRegistry.getDatabaseManager().getDatabases(); + PrincipalDatabase db = map.get(jmxDatabaseName); + + HashMap<String,Object> env = new HashMap<String,Object>(); + + //Socket factories for the RMIConnectorServer, either default or SLL depending on configuration + RMIClientSocketFactory csf; + RMIServerSocketFactory ssf; + + //check ssl enabled option in config, default to true if option is not set + boolean sslEnabled = appRegistry.getConfiguration().getManagementSSLEnabled(); + + if (sslEnabled) + { + //set the SSL related system properties used by the SSL RMI socket factories to the values + //given in the configuration file, unless command line settings have already been specified + String keyStorePath; + + if(System.getProperty("javax.net.ssl.keyStore") != null) + { + keyStorePath = System.getProperty("javax.net.ssl.keyStore"); + } + else + { + keyStorePath = appRegistry.getConfiguration().getManagementKeyStorePath(); + } + + //check the keystore path value is valid + if (keyStorePath == null) + { + throw new ConfigurationException("JMX management SSL keystore path not defined, " + + "unable to start SSL protected JMX ConnectorServer"); + } + else + { + //ensure the system property is set + System.setProperty("javax.net.ssl.keyStore", keyStorePath); + + //check the file is usable + File ksf = new File(keyStorePath); + + if (!ksf.exists()) + { + throw new FileNotFoundException("Cannot find JMX management SSL keystore file " + ksf + "\n" + + "Check broker configuration, or see create-example-ssl-stores script" + + "in the bin/ directory if you need to generate an example store."); + } + if (!ksf.canRead()) + { + throw new FileNotFoundException("Cannot read JMX management SSL keystore file: " + + ksf + ". Check permissions."); + } + + CurrentActor.get().message(ManagementConsoleMessages.SSL_KEYSTORE(ksf.getAbsolutePath())); + } + + //check the key store password is set + if (System.getProperty("javax.net.ssl.keyStorePassword") == null) + { + + if (appRegistry.getConfiguration().getManagementKeyStorePassword() == null) + { + throw new ConfigurationException("JMX management SSL keystore password not defined, " + + "unable to start requested SSL protected JMX server"); + } + else + { + System.setProperty("javax.net.ssl.keyStorePassword", + appRegistry.getConfiguration().getManagementKeyStorePassword()); + } + } + + //create the SSL RMI socket factories + csf = new SslRMIClientSocketFactory(); + ssf = new SslRMIServerSocketFactory(); + } + else + { + //Do not specify any specific RMI socket factories, resulting in use of the defaults. + csf = null; + ssf = null; + } + + //add a JMXAuthenticator implementation the env map to authenticate the RMI based JMX connector server + RMIPasswordAuthenticator rmipa = new RMIPasswordAuthenticator(); + rmipa.setPrincipalDatabase(db); + env.put(JMXConnectorServer.AUTHENTICATOR, rmipa); + + /* + * Start a RMI registry on the management port, to hold the JMX RMI ConnectorServer stub. + * Using custom socket factory to prevent anyone (including us unfortunately) binding to the registry using RMI. + * As a result, only binds made using the object reference will succeed, thus securing it from external change. + */ + System.setProperty("java.rmi.server.randomIDs", "true"); + if(_useCustomSocketFactory) + { + _rmiRegistry = LocateRegistry.createRegistry(port, null, new CustomRMIServerSocketFactory()); + } + else + { + _rmiRegistry = LocateRegistry.createRegistry(port, null, null); + } + + CurrentActor.get().message(ManagementConsoleMessages.LISTENING("RMI Registry", port)); + + /* + * We must now create the RMI ConnectorServer manually, as the JMX Factory methods use RMI calls + * to bind the ConnectorServer to the registry, which will now fail as for security we have + * locked it from any RMI based modifications, including our own. Instead, we will manually bind + * the RMIConnectorServer stub to the registry using its object reference, which will still succeed. + * + * The registry is exported on the defined management port 'port'. We will export the RMIConnectorServer + * on 'port +1'. Use of these two well-defined ports will ease any navigation through firewall's. + */ + final RMIServerImpl rmiConnectorServerStub = new RMIJRMPServerImpl(port+PORT_EXPORT_OFFSET, csf, ssf, env); + String localHost; + try + { + localHost = InetAddress.getLocalHost().getHostName(); + } + catch(UnknownHostException ex) + { + localHost="127.0.0.1"; + } + final String hostname = localHost; + final JMXServiceURL externalUrl = new JMXServiceURL( + "service:jmx:rmi://"+hostname+":"+(port+PORT_EXPORT_OFFSET)+"/jndi/rmi://"+hostname+":"+port+"/jmxrmi"); + + final JMXServiceURL internalUrl = new JMXServiceURL("rmi", hostname, port+PORT_EXPORT_OFFSET); + _cs = new RMIConnectorServer(internalUrl, env, rmiConnectorServerStub, _mbeanServer) + { + @Override + public synchronized void start() throws IOException + { + try + { + //manually bind the connector server to the registry at key 'jmxrmi', like the out-of-the-box agent + _rmiRegistry.bind("jmxrmi", rmiConnectorServerStub); + } + catch (AlreadyBoundException abe) + { + //key was already in use. shouldnt happen here as its a new registry, unbindable by normal means. + + //IOExceptions are the only checked type throwable by the method, wrap and rethrow + IOException ioe = new IOException(abe.getMessage()); + ioe.initCause(abe); + throw ioe; + } + + //now do the normal tasks + super.start(); + } + + @Override + public synchronized void stop() throws IOException + { + try + { + if (_rmiRegistry != null) + { + _rmiRegistry.unbind("jmxrmi"); + } + } + catch (NotBoundException nbe) + { + //ignore + } + + //now do the normal tasks + super.stop(); + } + + @Override + public JMXServiceURL getAddress() + { + //must return our pre-crafted url that includes the full details, inc JNDI details + return externalUrl; + } + + }; + + + //Add the custom invoker as an MBeanServerForwarder, and start the RMIConnectorServer. + MBeanServerForwarder mbsf = MBeanInvocationHandlerImpl.newProxyInstance(); + _cs.setMBeanServerForwarder(mbsf); + + NotificationFilterSupport filter = new NotificationFilterSupport(); + filter.enableType(JMXConnectionNotification.OPENED); + filter.enableType(JMXConnectionNotification.CLOSED); + filter.enableType(JMXConnectionNotification.FAILED); + // Get the handler that is used by the above MBInvocationHandler Proxy. + // which is the MBeanInvocationHandlerImpl and so also a NotificationListener + _cs.addNotificationListener((NotificationListener) Proxy.getInvocationHandler(mbsf), filter, null); + + _cs.start(); + + String connectorServer = (sslEnabled ? "SSL " : "") + "JMX RMIConnectorServer"; + CurrentActor.get().message(ManagementConsoleMessages.LISTENING(connectorServer, port + PORT_EXPORT_OFFSET)); + + CurrentActor.get().message(ManagementConsoleMessages.READY(false)); + } + + /* + * Custom RMIServerSocketFactory class, used to prevent updates to the RMI registry. + * Supplied to the registry at creation, this will prevent RMI-based operations on the + * registry such as attempting to bind a new object, thereby securing it from tampering. + * This is accomplished by always returning null when attempting to determine the address + * of the caller, thus ensuring the registry will refuse the attempt. Calls to bind etc + * made using the object reference will not be affected and continue to operate normally. + */ + + private static class CustomRMIServerSocketFactory implements RMIServerSocketFactory + { + + public ServerSocket createServerSocket(int port) throws IOException + { + return new NoLocalAddressServerSocket(port); + } + + private static class NoLocalAddressServerSocket extends ServerSocket + { + NoLocalAddressServerSocket(int port) throws IOException + { + super(port); + } + + @Override + public Socket accept() throws IOException + { + Socket s = new NoLocalAddressSocket(); + super.implAccept(s); + return s; + } + } + + private static class NoLocalAddressSocket extends Socket + { + @Override + public InetAddress getInetAddress() + { + return null; + } + } + } + + + public void registerObject(ManagedObject managedObject) throws JMException + { + _mbeanServer.registerMBean(managedObject, managedObject.getObjectName()); + } + + public void unregisterObject(ManagedObject managedObject) throws JMException + { + _mbeanServer.unregisterMBean(managedObject.getObjectName()); + } + + // checks if the system properties are set which enable the JVM's out-of-the-box JMXAgent. + private boolean areOutOfTheBoxJMXOptionsSet() + { + if (System.getProperty("com.sun.management.jmxremote") != null) + { + return true; + } + + if (System.getProperty("com.sun.management.jmxremote.port") != null) + { + return true; + } + + return false; + } + + //Stops the JMXConnectorServer and RMIRegistry, then unregisters any remaining MBeans from the MBeanServer + public void close() + { + if (_cs != null) + { + // Stopping the JMX ConnectorServer + try + { + CurrentActor.get().message(ManagementConsoleMessages.SHUTTING_DOWN("JMX RMIConnectorServer", _cs.getAddress().getPort())); + _cs.stop(); + } + catch (IOException e) + { + _log.error("Exception while closing the JMX ConnectorServer: " + e.getMessage()); + } + } + + if (_rmiRegistry != null) + { + // Stopping the RMI registry + CurrentActor.get().message(ManagementConsoleMessages.SHUTTING_DOWN("RMI Registry", _cs.getAddress().getPort() - PORT_EXPORT_OFFSET)); + try + { + UnicastRemoteObject.unexportObject(_rmiRegistry, false); + } + catch (NoSuchObjectException e) + { + _log.error("Exception while closing the RMI Registry: " + e.getMessage()); + } + } + + //ObjectName query to gather all Qpid related MBeans + ObjectName mbeanNameQuery = null; + try + { + mbeanNameQuery = new ObjectName(ManagedObject.DOMAIN + ":*"); + } + catch (Exception e1) + { + _log.warn("Unable to generate MBean ObjectName query for close operation"); + } + + for (ObjectName name : _mbeanServer.queryNames(mbeanNameQuery, null)) + { + try + { + _mbeanServer.unregisterMBean(name); + } + catch (JMException e) + { + _log.error("Exception unregistering MBean '"+ name +"': " + e.getMessage()); + } + } + + CurrentActor.get().message(ManagementConsoleMessages.STOPPED()); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanIntrospector.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanIntrospector.java new file mode 100644 index 0000000000..17a6851abc --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanIntrospector.java @@ -0,0 +1,396 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.management; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanConstructorInfo; +import javax.management.MBeanOperationInfo; +import javax.management.MBeanParameterInfo; +import javax.management.NotCompliantMBeanException; + +import org.apache.qpid.management.common.mbeans.annotations.MBeanAttribute; +import org.apache.qpid.management.common.mbeans.annotations.MBeanConstructor; +import org.apache.qpid.management.common.mbeans.annotations.MBeanDescription; +import org.apache.qpid.management.common.mbeans.annotations.MBeanOperation; +import org.apache.qpid.management.common.mbeans.annotations.MBeanOperationParameter; + +/** + * This class is a utility class to introspect the MBean class and the management + * interface class for various purposes. + * @author Bhupendra Bhardwaj + * @version 0.1 + */ +class MBeanIntrospector { + + private static final String _defaultAttributeDescription = "Management attribute"; + private static final String _defaultOerationDescription = "Management operation"; + private static final String _defaultConstructorDescription = "MBean constructor"; + private static final String _defaultMbeanDescription = "Management interface of the MBean"; + + /** + * Introspects the management interface class for MBean attributes. + * @param interfaceClass + * @return MBeanAttributeInfo[] + * @throws NotCompliantMBeanException + */ + static MBeanAttributeInfo[] getMBeanAttributesInfo(Class interfaceClass) + throws NotCompliantMBeanException + { + List<MBeanAttributeInfo> attributesList = new ArrayList<MBeanAttributeInfo>(); + + /** + * Using reflection, all methods of the managemetn interface will be analysed, + * and MBeanInfo will be created. + */ + for (Method method : interfaceClass.getMethods()) + { + String name = method.getName(); + Class<?> resultType = method.getReturnType(); + MBeanAttributeInfo attributeInfo = null; + + if (isAttributeGetterMethod(method)) + { + String desc = getAttributeDescription(method); + attributeInfo = new MBeanAttributeInfo(name.substring(3), + resultType.getName(), + desc, + true, + false, + false); + int index = getIndexIfAlreadyExists(attributeInfo, attributesList); + if (index == -1) + { + attributesList.add(attributeInfo); + } + else + { + attributeInfo = new MBeanAttributeInfo(name.substring(3), + resultType.getName(), + desc, + true, + true, + false); + attributesList.set(index, attributeInfo); + } + } + else if (isAttributeSetterMethod(method)) + { + String desc = getAttributeDescription(method); + attributeInfo = new MBeanAttributeInfo(name.substring(3), + method.getParameterTypes()[0].getName(), + desc, + false, + true, + false); + int index = getIndexIfAlreadyExists(attributeInfo, attributesList); + if (index == -1) + { + attributesList.add(attributeInfo); + } + else + { + attributeInfo = new MBeanAttributeInfo(name.substring(3), + method.getParameterTypes()[0].getName(), + desc, + true, + true, + false); + attributesList.set(index, attributeInfo); + } + } + else if (isAttributeBoolean(method)) + { + attributeInfo = new MBeanAttributeInfo(name.substring(2), + resultType.getName(), + getAttributeDescription(method), + true, + false, + true); + attributesList.add(attributeInfo); + } + } + + return attributesList.toArray(new MBeanAttributeInfo[0]); + } + + /** + * Introspects the management interface class for management operations. + * @param interfaceClass + * @return MBeanOperationInfo[] + */ + static MBeanOperationInfo[] getMBeanOperationsInfo(Class interfaceClass) + { + List<MBeanOperationInfo> operationsList = new ArrayList<MBeanOperationInfo>(); + + for (Method method : interfaceClass.getMethods()) + { + if (!isAttributeGetterMethod(method) && + !isAttributeSetterMethod(method) && + !isAttributeBoolean(method)) + { + operationsList.add(getOperationInfo(method)); + } + } + + return operationsList.toArray(new MBeanOperationInfo[0]); + } + + /** + * Checks if the method is an attribute getter method. + * @param method + * @return true if the method is an attribute getter method. + */ + private static boolean isAttributeGetterMethod(Method method) + { + if (!(method.getName().equals("get")) && + method.getName().startsWith("get") && + method.getParameterTypes().length == 0 && + !method.getReturnType().equals(void.class)) + { + return true; + } + + return false; + } + + /** + * Checks if the method is an attribute setter method. + * @param method + * @return true if the method is an attribute setter method. + */ + private static boolean isAttributeSetterMethod(Method method) + { + if (!(method.getName().equals("set")) && + method.getName().startsWith("set") && + method.getParameterTypes().length == 1 && + method.getReturnType().equals(void.class)) + { + return true; + } + + return false; + } + + /** + * Checks if the attribute is a boolean and the method is a isX kind og method. + * @param method + * @return true if the method is an attribute isX type of method + */ + private static boolean isAttributeBoolean(Method method) + { + if (!(method.getName().equals("is")) && + method.getName().startsWith("is") && + method.getParameterTypes().length == 0 && + method.getReturnType().equals(boolean.class)) + { + return true; + } + + return false; + } + + /** + * Helper method to retrieve the attribute index from the list of attributes. + * @param attribute + * @param list + * @return attribute index no. -1 if attribtue doesn't exist + * @throws NotCompliantMBeanException + */ + private static int getIndexIfAlreadyExists(MBeanAttributeInfo attribute, + List<MBeanAttributeInfo> list) + throws NotCompliantMBeanException + { + String exceptionMsg = "Conflicting attribute methods for attribute " + attribute.getName(); + + for (MBeanAttributeInfo memberAttribute : list) + { + if (attribute.getName().equals(memberAttribute.getName())) + { + if (!attribute.getType().equals(memberAttribute.getType())) + { + throw new NotCompliantMBeanException(exceptionMsg); + } + if (attribute.isReadable() && memberAttribute.isReadable()) + { + if (attribute.isIs() != memberAttribute.isIs()) + { + throw new NotCompliantMBeanException(exceptionMsg); + } + } + + return list.indexOf(memberAttribute); + } + } + + return -1; + } + + /** + * Retrieves the attribute description from annotation + * @param attributeMethod + * @return attribute description + */ + private static String getAttributeDescription(Method attributeMethod) + { + MBeanAttribute anno = attributeMethod.getAnnotation(MBeanAttribute.class); + if (anno != null) + { + return anno.description(); + } + return _defaultAttributeDescription; + } + + /** + * Introspects the method to retrieve the operation information. + * @param operation + * @return MBeanOperationInfo + */ + private static MBeanOperationInfo getOperationInfo(Method operation) + { + MBeanOperationInfo operationInfo = null; + Class<?> returnType = operation.getReturnType(); + + MBeanParameterInfo[] paramsInfo = getParametersInfo(operation.getParameterAnnotations(), + operation.getParameterTypes()); + + String operationDesc = _defaultOerationDescription; + int impact = MBeanOperationInfo.UNKNOWN; + + if (operation.getAnnotation(MBeanOperation.class) != null) + { + operationDesc = operation.getAnnotation(MBeanOperation.class).description(); + impact = operation.getAnnotation(MBeanOperation.class).impact(); + } + operationInfo = new MBeanOperationInfo(operation.getName(), + operationDesc, + paramsInfo, + returnType.getName(), + impact); + + return operationInfo; + } + + /** + * Constructs the parameter info. + * @param paramsAnno + * @param paramTypes + * @return MBeanParameterInfo[] + */ + private static MBeanParameterInfo[] getParametersInfo(Annotation[][] paramsAnno, + Class<?>[] paramTypes) + { + int noOfParams = paramsAnno.length; + + MBeanParameterInfo[] paramsInfo = new MBeanParameterInfo[noOfParams]; + + for (int i = 0; i < noOfParams; i++) + { + MBeanParameterInfo paramInfo = null; + String type = paramTypes[i].getName(); + for (Annotation anno : paramsAnno[i]) + { + String name,desc; + if (MBeanOperationParameter.class.isInstance(anno)) + { + name = MBeanOperationParameter.class.cast(anno).name(); + desc = MBeanOperationParameter.class.cast(anno).description(); + paramInfo = new MBeanParameterInfo(name, type, desc); + } + } + + + if (paramInfo == null) + { + paramInfo = new MBeanParameterInfo("p " + (i + 1), type, "parameter " + (i + 1)); + } + if (paramInfo != null) + paramsInfo[i] = paramInfo; + } + + return paramsInfo; + } + + /** + * Introspects the MBean class for constructors + * @param implClass + * @return MBeanConstructorInfo[] + */ + static MBeanConstructorInfo[] getMBeanConstructorsInfo(Class implClass) + { + List<MBeanConstructorInfo> constructors = new ArrayList<MBeanConstructorInfo>(); + + for (Constructor cons : implClass.getConstructors()) + { + MBeanConstructorInfo constructorInfo = getMBeanConstructorInfo(cons); + //MBeanConstructorInfo constructorInfo = new MBeanConstructorInfo("desc", cons); + if (constructorInfo != null) + constructors.add(constructorInfo); + } + + return constructors.toArray(new MBeanConstructorInfo[0]); + } + + /** + * Retrieves the constructor info from given constructor. + * @param cons + * @return MBeanConstructorInfo + */ + private static MBeanConstructorInfo getMBeanConstructorInfo(Constructor cons) + { + String desc = _defaultConstructorDescription; + Annotation anno = cons.getAnnotation(MBeanConstructor.class); + if (anno != null && MBeanConstructor.class.isInstance(anno)) + { + desc = MBeanConstructor.class.cast(anno).value(); + if(desc == null) + { + desc = _defaultConstructorDescription; + } + } + + //MBeanParameterInfo[] paramsInfo = getParametersInfo(cons.getParameterAnnotations(), + // cons.getParameterTypes()); + + return new MBeanConstructorInfo(cons.getName(), desc, null); + } + + /** + * Retrieves the description from the annotations of given class + * @param annotatedClass + * @return class description + */ + static String getMBeanDescription(Class annotatedClass) + { + Annotation anno = annotatedClass.getAnnotation(MBeanDescription.class); + if (anno != null && MBeanDescription.class.isInstance(anno)) + { + return MBeanDescription.class.cast(anno).value(); + } + return _defaultMbeanDescription; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanInvocationHandlerImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanInvocationHandlerImpl.java new file mode 100644 index 0000000000..380f51e308 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/MBeanInvocationHandlerImpl.java @@ -0,0 +1,329 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.management; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.Principal; +import java.util.Properties; +import java.util.Set; + +import javax.management.Attribute; +import javax.management.JMException; +import javax.management.MBeanInfo; +import javax.management.MBeanOperationInfo; +import javax.management.MBeanServer; +import javax.management.Notification; +import javax.management.NotificationListener; +import javax.management.ObjectName; +import javax.management.remote.JMXConnectionNotification; +import javax.management.remote.JMXPrincipal; +import javax.management.remote.MBeanServerForwarder; +import javax.security.auth.Subject; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.ManagementActor; +import org.apache.qpid.server.logging.messages.ManagementConsoleMessages; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.security.access.Operation; + +/** + * This class can be used by the JMXConnectorServer as an InvocationHandler for the mbean operations. This implements + * the logic for allowing the users to invoke MBean operations and implements the restrictions for readOnly, readWrite + * and admin users. + */ +public class MBeanInvocationHandlerImpl implements InvocationHandler, NotificationListener +{ + private static final Logger _logger = Logger.getLogger(MBeanInvocationHandlerImpl.class); + + public final static String ADMIN = "admin"; + public final static String READWRITE = "readwrite"; + public final static String READONLY = "readonly"; + private final static String DELEGATE = "JMImplementation:type=MBeanServerDelegate"; + private MBeanServer _mbs; + private static Properties _userRoles = new Properties(); + private static ManagementActor _logActor; + + public static MBeanServerForwarder newProxyInstance() + { + final InvocationHandler handler = new MBeanInvocationHandlerImpl(); + final Class<?>[] interfaces = new Class[] { MBeanServerForwarder.class }; + + + _logActor = new ManagementActor(ApplicationRegistry.getInstance().getRootMessageLogger()); + + Object proxy = Proxy.newProxyInstance(MBeanServerForwarder.class.getClassLoader(), interfaces, handler); + return MBeanServerForwarder.class.cast(proxy); + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable + { + final String methodName = getMethodName(method, args); + + if (methodName.equals("getMBeanServer")) + { + return _mbs; + } + + if (methodName.equals("setMBeanServer")) + { + if (args[0] == null) + { + throw new IllegalArgumentException("Null MBeanServer"); + } + if (_mbs != null) + { + throw new IllegalArgumentException("MBeanServer object already initialized"); + } + _mbs = (MBeanServer) args[0]; + return null; + } + + // Retrieve Subject from current AccessControlContext + AccessControlContext acc = AccessController.getContext(); + Subject subject = Subject.getSubject(acc); + + try + { + // Allow operations performed locally on behalf of the connector server itself + if (subject == null) + { + return method.invoke(_mbs, args); + } + + if (args == null || DELEGATE.equals(args[0])) + { + return method.invoke(_mbs, args); + } + + // Restrict access to "createMBean" and "unregisterMBean" to any user + if (methodName.equals("createMBean") || methodName.equals("unregisterMBean")) + { + _logger.debug("User trying to create or unregister an MBean"); + throw new SecurityException("Access denied: " + methodName); + } + + // Allow querying available object names + if (methodName.equals("queryNames")) + { + return method.invoke(_mbs, args); + } + + // Retrieve JMXPrincipal from Subject + Set<JMXPrincipal> principals = subject.getPrincipals(JMXPrincipal.class); + if (principals == null || principals.isEmpty()) + { + throw new SecurityException("Access denied: no principal"); + } + + // Save the principal + Principal principal = principals.iterator().next(); + SecurityManager.setThreadPrincipal(principal); + + // Get the component, type and impact, which may be null + String type = getType(method, args); + String vhost = getVirtualHost(method, args); + int impact = getImpact(method, args); + + // Get the security manager for the virtual host (if set) + SecurityManager security; + if (vhost == null) + { + security = ApplicationRegistry.getInstance().getSecurityManager(); + } + else + { + security = ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHost(vhost).getSecurityManager(); + } + + if (isAccessMethod(methodName) || impact == MBeanOperationInfo.INFO) + { + // Check for read-only method invocation permission + if (!security.authoriseMethod(Operation.ACCESS, type, methodName)) + { + throw new SecurityException("Permission denied: Access " + methodName); + } + } + else if (isUpdateMethod(methodName)) + { + // Check for setting properties permission + if (!security.authoriseMethod(Operation.UPDATE, type, methodName)) + { + throw new SecurityException("Permission denied: Update " + methodName); + } + } + else + { + // Check for invoking/executing method action/operation permission + if (!security.authoriseMethod(Operation.EXECUTE, type, methodName)) + { + throw new SecurityException("Permission denied: Execute " + methodName); + } + } + + // Actually invoke the method + return method.invoke(_mbs, args); + } + catch (InvocationTargetException e) + { + throw e.getTargetException(); + } + } + + private String getType(Method method, Object[] args) + { + if (args[0] instanceof ObjectName) + { + ObjectName object = (ObjectName) args[0]; + String type = object.getKeyProperty("type"); + + return type; + } + return null; + } + + private String getVirtualHost(Method method, Object[] args) + { + if (args[0] instanceof ObjectName) + { + ObjectName object = (ObjectName) args[0]; + String vhost = object.getKeyProperty("VirtualHost"); + + if(vhost != null) + { + try + { + //if the name is quoted in the ObjectName, unquote it + vhost = ObjectName.unquote(vhost); + } + catch(IllegalArgumentException e) + { + //ignore, this just means the name is not quoted + //and can be left unchanged + } + } + + return vhost; + } + return null; + } + + private String getMethodName(Method method, Object[] args) + { + String methodName = method.getName(); + + // if arguments are set, try and work out real method name + if (args != null && args.length >= 1 && args[0] instanceof ObjectName) + { + if (methodName.equals("getAttribute")) + { + methodName = "get" + (String) args[1]; + } + else if (methodName.equals("setAttribute")) + { + methodName = "set" + ((Attribute) args[1]).getName(); + } + else if (methodName.equals("invoke")) + { + methodName = (String) args[1]; + } + } + + return methodName; + } + + private int getImpact(Method method, Object[] args) + { + //handle invocation of other methods on mbeans + if ((args[0] instanceof ObjectName) && (method.getName().equals("invoke"))) + { + //get invoked method name + String mbeanMethod = (args.length > 1) ? (String) args[1] : null; + if (mbeanMethod == null) + { + return -1; + } + + try + { + //Get the impact attribute + MBeanInfo mbeanInfo = _mbs.getMBeanInfo((ObjectName) args[0]); + if (mbeanInfo != null) + { + MBeanOperationInfo[] opInfos = mbeanInfo.getOperations(); + for (MBeanOperationInfo opInfo : opInfos) + { + if (opInfo.getName().equals(mbeanMethod)) + { + return opInfo.getImpact(); + } + } + } + } + catch (JMException ex) + { + ex.printStackTrace(); + } + } + + return -1; + } + + private boolean isAccessMethod(String methodName) + { + //handle standard get/query/is methods from MBeanServer + return (methodName.startsWith("query") || methodName.startsWith("get") || methodName.startsWith("is")); + } + + + private boolean isUpdateMethod(String methodName) + { + //handle standard set methods from MBeanServer + return methodName.startsWith("set"); + } + + public void handleNotification(Notification notification, Object handback) + { + assert notification instanceof JMXConnectionNotification; + + // only RMI Connections are serviced here, Local API atta + // rmi://169.24.29.116 guest 3 + String[] connectionData = ((JMXConnectionNotification) notification).getConnectionId().split(" "); + String user = connectionData[1]; + + if (notification.getType().equals(JMXConnectionNotification.OPENED)) + { + _logActor.message(ManagementConsoleMessages.OPEN(user)); + } + else if (notification.getType().equals(JMXConnectionNotification.CLOSED) || + notification.getType().equals(JMXConnectionNotification.FAILED)) + { + _logActor.message(ManagementConsoleMessages.CLOSE()); + } + } +} + diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/Managable.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/Managable.java new file mode 100644 index 0000000000..166a2a376d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/Managable.java @@ -0,0 +1,34 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.management; + +/** + * Any object that can return a related MBean should implement this interface. + * + * This enables other classes to get the managed object, which in turn is useful when + * constructing relationships between managed objects without having to maintain + * separate data structures containing MBeans. + * + */ +public interface Managable +{ + ManagedObject getManagedObject(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObject.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObject.java new file mode 100644 index 0000000000..de14785fb0 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObject.java @@ -0,0 +1,59 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.management; + +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; +import javax.management.JMException; + +import org.apache.qpid.AMQException; + +/** + * This should be implemented by all Managable objects. + */ +public interface ManagedObject +{ + static final String DOMAIN = "org.apache.qpid"; + + /** + * @return the name that uniquely identifies this object instance. It must be + * unique only among objects of this type at this level in the hierarchy so + * the uniqueness should not be too difficult to ensure. + */ + String getObjectInstanceName(); + + String getType(); + + Class<?> getManagementInterface(); + + ManagedObject getParentObject(); + + void register() throws AMQException, JMException; + + void unregister() throws AMQException; + + /** + * Returns the ObjectName required for the mbeanserver registration. + * @return ObjectName + * @throws MalformedObjectNameException + */ + ObjectName getObjectName() throws MalformedObjectNameException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java new file mode 100644 index 0000000000..fda80ad0dd --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/ManagedObjectRegistry.java @@ -0,0 +1,50 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.management; + +import javax.management.JMException; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.common.Closeable; + +import java.rmi.RemoteException; +import java.io.IOException; + +/** + * Handles the registration (and unregistration and so on) of managed objects. + * + * Managed objects are responsible for exposting attributes, operations and notifications. They will expose + * these outside the JVM therefore it is important not to use implementation objects directly as managed objects. + * Instead, creating inner classes and exposing those is an effective way of exposing internal state in a + * controlled way. + * + * Although we do not explictly use them while targetting Java 5, the enhanced MXBean approach in Java 6 will + * be the obvious choice for managed objects. + * + */ +public interface ManagedObjectRegistry extends Closeable +{ + void start() throws IOException, ConfigurationException; + + void registerObject(ManagedObject managedObject) throws JMException; + + void unregisterObject(ManagedObject managedObject) throws JMException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java new file mode 100644 index 0000000000..a048e75b2e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/management/NoopManagedObjectRegistry.java @@ -0,0 +1,60 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.management; + +import javax.management.JMException; + +import org.apache.log4j.Logger; + +import java.rmi.RemoteException; + +/** + * This managed object registry does not actually register MBeans. This can be used in tests when management is + * not required or when management has been disabled. + * + */ +public class NoopManagedObjectRegistry implements ManagedObjectRegistry +{ + private static final Logger _log = Logger.getLogger(NoopManagedObjectRegistry.class); + + public NoopManagedObjectRegistry() + { + _log.info("Management is disabled"); + } + + public void start() + { + //no-op + } + + public void registerObject(ManagedObject managedObject) throws JMException + { + } + + public void unregisterObject(ManagedObject managedObject) throws JMException + { + } + + public void close() + { + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/message/AMQMessage.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/AMQMessage.java new file mode 100644 index 0000000000..e0c181a5fc --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/AMQMessage.java @@ -0,0 +1,346 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.message; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.configuration.SessionConfig; +import org.apache.qpid.server.queue.AMQQueue; + + +import java.util.concurrent.atomic.AtomicInteger; +import java.lang.ref.WeakReference; +import java.nio.ByteBuffer; + +/** + * A deliverable message. + */ +public class AMQMessage implements ServerMessage +{ + /** Used for debugging purposes. */ + private static final Logger _log = Logger.getLogger(AMQMessage.class); + + private final AtomicInteger _referenceCount = new AtomicInteger(0); + + /** Flag to indicate that this message requires 'immediate' delivery. */ + + private static final byte IMMEDIATE = 0x01; + + /** + * Flag to indicate whether this message has been delivered to a consumer. Used in implementing return functionality + * for messages published with the 'immediate' flag. + */ + + private static final byte DELIVERED_TO_CONSUMER = 0x02; + + private byte _flags = 0; + + private long _expiration; + + private final long _size; + + private Object _sessionIdentifier; + private static final byte IMMEDIATE_AND_DELIVERED = (byte) (IMMEDIATE | DELIVERED_TO_CONSUMER); + + private final StoredMessage<MessageMetaData> _handle; + + WeakReference<AMQChannel> _channelRef; + + + public AMQMessage(StoredMessage<MessageMetaData> handle) + { + this(handle, null); + } + + public AMQMessage(StoredMessage<MessageMetaData> handle, WeakReference<AMQChannel> channelRef) + { + _handle = handle; + final MessageMetaData metaData = handle.getMetaData(); + _size = metaData.getContentSize(); + final MessagePublishInfo messagePublishInfo = metaData.getMessagePublishInfo(); + + if(messagePublishInfo.isImmediate()) + { + _flags |= IMMEDIATE; + } + + _channelRef = channelRef; + } + + + public String debugIdentity() + { + return "(HC:" + System.identityHashCode(this) + " ID:" + getMessageId() + " Ref:" + _referenceCount.get() + ")"; + } + + public void setExpiration(final long expiration) + { + + _expiration = expiration; + + } + + public boolean isReferenced() + { + return _referenceCount.get() > 0; + } + + public MessageMetaData getMessageMetaData() + { + return _handle.getMetaData(); + } + + public ContentHeaderBody getContentHeaderBody() throws AMQException + { + return getMessageMetaData().getContentHeaderBody(); + } + + + + public Long getMessageId() + { + return _handle.getMessageNumber(); + } + + /** + * Creates a long-lived reference to this message, and increments the count of such references, as an atomic + * operation. + */ + public AMQMessage takeReference() + { + incrementReference(); // _referenceCount.incrementAndGet(); + + return this; + } + + public boolean incrementReference() + { + return incrementReference(1); + } + + /* Threadsafe. Increment the reference count on the message. */ + public boolean incrementReference(int count) + { + + if(_referenceCount.addAndGet(count) <= 0) + { + _referenceCount.addAndGet(-count); + return false; + } + else + { + return true; + } + + } + + /** + * Threadsafe. This will decrement the reference count and when it reaches zero will remove the message from the + * message store. + * + * + * @throws org.apache.qpid.server.queue.MessageCleanupException when an attempt was made to remove the message from the message store and that + * failed + */ + public void decrementReference() + { + int count = _referenceCount.decrementAndGet(); + + // note that the operation of decrementing the reference count and then removing the message does not + // have to be atomic since the ref count starts at 1 and the exchange itself decrements that after + // the message has been passed to all queues. i.e. we are + // not relying on the all the increments having taken place before the delivery manager decrements. + if (count == 0) + { + // set the reference count way below 0 so that we can detect that the message has been deleted + // this is to guard against the message being spontaneously recreated (from the mgmt console) + // by copying from other queues at the same time as it is being removed. + _referenceCount.set(Integer.MIN_VALUE/2); + + // must check if the handle is null since there may be cases where we decide to throw away a message + // and the handle has not yet been constructed + if (_handle != null) + { + _handle.remove(); + + } + } + else + { + if (count < 0) + { + throw new RuntimeException("Reference count for message id " + debugIdentity() + + " has gone below 0."); + } + } + } + + + /** + * Called selectors to determin if the message has already been sent + * + * @return _deliveredToConsumer + */ + public boolean getDeliveredToConsumer() + { + return (_flags & DELIVERED_TO_CONSUMER) != 0; + } + + public String getRoutingKey() + { + // TODO + return null; + } + + public AMQMessageHeader getMessageHeader() + { + return getMessageMetaData().getMessageHeader(); + } + + public boolean isPersistent() + { + return getMessageMetaData().isPersistent(); + } + + /** + * Called to enforce the 'immediate' flag. + * + * @returns true if the message is marked for immediate delivery but has not been marked as delivered + * to a consumer + */ + public boolean immediateAndNotDelivered() + { + + return (_flags & IMMEDIATE_AND_DELIVERED) == IMMEDIATE; + + } + + public MessagePublishInfo getMessagePublishInfo() throws AMQException + { + return getMessageMetaData().getMessagePublishInfo(); + } + + public long getArrivalTime() + { + return getMessageMetaData().getArrivalTime(); + } + + /** + * Checks to see if the message has expired. If it has the message is dequeued. + * + * @param queue The queue to check the expiration against. (Currently not used) + * + * @return true if the message has expire + * + * @throws AMQException + */ + public boolean expired(AMQQueue queue) throws AMQException + { + + if (_expiration != 0L) + { + long now = System.currentTimeMillis(); + + return (now > _expiration); + } + + return false; + } + + /** + * Called when this message is delivered to a consumer. (used to implement the 'immediate' flag functionality). + * And for selector efficiency. + */ + public void setDeliveredToConsumer() + { + _flags |= DELIVERED_TO_CONSUMER; + } + + public long getSize() + { + return _size; + + } + + public boolean isImmediate() + { + return (_flags & IMMEDIATE) == IMMEDIATE; + } + + public long getExpiration() + { + return _expiration; + } + + public MessageReference newReference() + { + return new AMQMessageReference(this); + } + + public Long getMessageNumber() + { + return getMessageId(); + } + + + public Object getPublisherIdentifier() + { + //todo store sessionIdentifier/client id with message in store + //Currently the _sessionIdentifier will be null if the message has been + // restored from a message Store + + return _sessionIdentifier; + + } + + public void setClientIdentifier(final Object sessionIdentifier) + { + _sessionIdentifier = sessionIdentifier; + } + + + public String toString() + { + // return "Message[" + debugIdentity() + "]: " + _messageId + "; ref count: " + _referenceCount + "; taken : " + + // _taken + " by :" + _takenBySubcription; + + return "Message[" + debugIdentity() + "]: " + getMessageId() + "; ref count: " + _referenceCount; + } + + public int getContent(ByteBuffer buf, int offset) + { + return _handle.getContent(offset, buf); + } + + public StoredMessage<MessageMetaData> getStoredMessage() + { + return _handle; + } + + public SessionConfig getSessionConfig() + { + return _channelRef == null ? null : ((SessionConfig) _channelRef.get()); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/message/AMQMessageHeader.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/AMQMessageHeader.java new file mode 100644 index 0000000000..faac14f8a7 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/AMQMessageHeader.java @@ -0,0 +1,55 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.message; + +import java.util.Set; + +public interface AMQMessageHeader +{ + String getCorrelationId(); + + long getExpiration(); + + String getMessageId(); + + String getMimeType(); + + String getEncoding(); + + byte getPriority(); + + long getTimestamp(); + + String getType(); + + String getReplyTo(); + + String getReplyToExchange(); + String getReplyToRoutingKey(); + + + Object getHeader(String name); + + boolean containsHeaders(Set<String> names); + + boolean containsHeader(String name); + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/message/AMQMessageReference.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/AMQMessageReference.java new file mode 100644 index 0000000000..940caaefe4 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/AMQMessageReference.java @@ -0,0 +1,44 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.message; + +import org.apache.qpid.server.message.AMQMessage; +import org.apache.qpid.server.queue.MessageCleanupException; + +public class AMQMessageReference extends MessageReference<AMQMessage> +{ + + + public AMQMessageReference(AMQMessage message) + { + super(message); + } + + protected void onReference(AMQMessage message) + { + message.incrementReference(); + } + + protected void onRelease(AMQMessage message) + { + message.decrementReference(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/message/ContentHeaderBodyAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/ContentHeaderBodyAdapter.java new file mode 100644 index 0000000000..84a1642578 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/ContentHeaderBodyAdapter.java @@ -0,0 +1,127 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.message; + +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.FieldTable; + +import java.util.Set; + +public class ContentHeaderBodyAdapter implements AMQMessageHeader +{ + private final ContentHeaderBody _contentHeaderBody; + + public ContentHeaderBodyAdapter(ContentHeaderBody contentHeaderBody) + { + _contentHeaderBody = contentHeaderBody; + } + + private BasicContentHeaderProperties getProperties() + { + return (BasicContentHeaderProperties) _contentHeaderBody.getProperties(); + } + + public String getCorrelationId() + { + return getProperties().getCorrelationIdAsString(); + } + + public long getExpiration() + { + return getProperties().getExpiration(); + } + + public String getMessageId() + { + return getProperties().getMessageIdAsString(); + } + + public String getMimeType() + { + return getProperties().getContentTypeAsString(); + } + + public String getEncoding() + { + return getProperties().getEncodingAsString(); + } + + public byte getPriority() + { + return getProperties().getPriority(); + } + + public long getTimestamp() + { + return getProperties().getTimestamp(); + } + + public String getType() + { + return getProperties().getTypeAsString(); + } + + public String getReplyTo() + { + return getProperties().getReplyToAsString(); + } + + public String getReplyToExchange() + { + // TODO + return getReplyTo(); + } + + public String getReplyToRoutingKey() + { + // TODO + return getReplyTo(); + + } + + public Object getHeader(String name) + { + FieldTable ft = getProperties().getHeaders(); + return ft.get(name); + } + + public boolean containsHeaders(Set<String> names) + { + FieldTable ft = getProperties().getHeaders(); + for(String name : names) + { + if(!ft.containsKey(name)) + { + return false; + } + } + return true; + } + + public boolean containsHeader(String name) + { + FieldTable ft = getProperties().getHeaders(); + return ft.containsKey(name); + } + + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/message/EnqueableMessage.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/EnqueableMessage.java new file mode 100755 index 0000000000..c32f80fc5b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/EnqueableMessage.java @@ -0,0 +1,27 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.message; + +public interface EnqueableMessage +{ + Long getMessageNumber(); + boolean isPersistent(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/message/InboundMessage.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/InboundMessage.java new file mode 100644 index 0000000000..1b3fdb1870 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/InboundMessage.java @@ -0,0 +1,37 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.message; + + +import org.apache.qpid.server.queue.Filterable; + +public interface InboundMessage extends Filterable +{ + String getRoutingKey(); + + AMQMessageHeader getMessageHeader(); + + boolean isPersistent(); + + boolean isRedelivered(); + + long getSize(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/message/MessageContentSource.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/MessageContentSource.java new file mode 100755 index 0000000000..08a09c4a85 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/MessageContentSource.java @@ -0,0 +1,31 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.message; + +import java.nio.ByteBuffer; + +public interface MessageContentSource +{ + public int getContent(ByteBuffer buf, int offset); + + long getSize(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/message/MessageMetaData.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/MessageMetaData.java new file mode 100644 index 0000000000..66cb7ed83b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/MessageMetaData.java @@ -0,0 +1,320 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.message; + +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.EncodingUtils; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.store.StorableMessageMetaData; +import org.apache.qpid.server.store.MessageMetaDataType; +import org.apache.qpid.AMQException; + +import java.nio.ByteBuffer; +import java.util.Set; + +/** + * Encapsulates a publish body and a content header. In the context of the message store these are treated as a + * single unit. + */ +public class MessageMetaData implements StorableMessageMetaData +{ + private MessagePublishInfo _messagePublishInfo; + + private ContentHeaderBody _contentHeaderBody; + + private int _contentChunkCount; + + private long _arrivalTime; + private static final byte MANDATORY_FLAG = 1; + private static final byte IMMEDIATE_FLAG = 2; + public static final MessageMetaDataType.Factory<MessageMetaData> FACTORY = new MetaDataFactory(); + + public MessageMetaData(MessagePublishInfo publishBody, ContentHeaderBody contentHeaderBody, int contentChunkCount) + { + this(publishBody,contentHeaderBody, contentChunkCount, System.currentTimeMillis()); + } + + public MessageMetaData(MessagePublishInfo publishBody, ContentHeaderBody contentHeaderBody, int contentChunkCount, long arrivalTime) + { + _contentHeaderBody = contentHeaderBody; + _messagePublishInfo = publishBody; + _contentChunkCount = contentChunkCount; + _arrivalTime = arrivalTime; + } + + public int getContentChunkCount() + { + return _contentChunkCount; + } + + public void setContentChunkCount(int contentChunkCount) + { + _contentChunkCount = contentChunkCount; + } + + public ContentHeaderBody getContentHeaderBody() + { + return _contentHeaderBody; + } + + public void setContentHeaderBody(ContentHeaderBody contentHeaderBody) + { + _contentHeaderBody = contentHeaderBody; + } + + public MessagePublishInfo getMessagePublishInfo() + { + return _messagePublishInfo; + } + + public void setMessagePublishInfo(MessagePublishInfo messagePublishInfo) + { + _messagePublishInfo = messagePublishInfo; + } + + public long getArrivalTime() + { + return _arrivalTime; + } + + public void setArrivalTime(long arrivalTime) + { + _arrivalTime = arrivalTime; + } + + public MessageMetaDataType getType() + { + return MessageMetaDataType.META_DATA_0_8; + } + + public int getStorableSize() + { + int size = _contentHeaderBody.getSize(); + size += 4; + size += EncodingUtils.encodedShortStringLength(_messagePublishInfo.getExchange()); + size += EncodingUtils.encodedShortStringLength(_messagePublishInfo.getRoutingKey()); + size += 1; // flags for immediate/mandatory + size += EncodingUtils.encodedLongLength(); + + return size; + } + + public int writeToBuffer(int offset, ByteBuffer dest) + { + ByteBuffer src = ByteBuffer.allocate((int)getStorableSize()); + + org.apache.mina.common.ByteBuffer minaSrc = org.apache.mina.common.ByteBuffer.wrap(src); + EncodingUtils.writeInteger(minaSrc, _contentHeaderBody.getSize()); + _contentHeaderBody.writePayload(minaSrc); + EncodingUtils.writeShortStringBytes(minaSrc, _messagePublishInfo.getExchange()); + EncodingUtils.writeShortStringBytes(minaSrc, _messagePublishInfo.getRoutingKey()); + byte flags = 0; + if(_messagePublishInfo.isMandatory()) + { + flags |= MANDATORY_FLAG; + } + if(_messagePublishInfo.isImmediate()) + { + flags |= IMMEDIATE_FLAG; + } + EncodingUtils.writeByte(minaSrc, flags); + EncodingUtils.writeLong(minaSrc,_arrivalTime); + src.position(minaSrc.position()); + src.flip(); + src.position(offset); + src = src.slice(); + if(dest.remaining() < src.limit()) + { + src.limit(dest.remaining()); + } + dest.put(src); + + + return src.limit(); + } + + public int getContentSize() + { + return (int) _contentHeaderBody.bodySize; + } + + public boolean isPersistent() + { + BasicContentHeaderProperties properties = (BasicContentHeaderProperties) (_contentHeaderBody.getProperties()); + return properties.getDeliveryMode() == BasicContentHeaderProperties.PERSISTENT; + } + + private static class MetaDataFactory implements MessageMetaDataType.Factory + { + + + public MessageMetaData createMetaData(ByteBuffer buf) + { + try + { + org.apache.mina.common.ByteBuffer minaSrc = org.apache.mina.common.ByteBuffer.wrap(buf); + int size = EncodingUtils.readInteger(minaSrc); + ContentHeaderBody chb = ContentHeaderBody.createFromBuffer(minaSrc, size); + final AMQShortString exchange = EncodingUtils.readAMQShortString(minaSrc); + final AMQShortString routingKey = EncodingUtils.readAMQShortString(minaSrc); + + final byte flags = EncodingUtils.readByte(minaSrc); + long arrivalTime = EncodingUtils.readLong(minaSrc); + + MessagePublishInfo publishBody = + new MessagePublishInfo() + { + + public AMQShortString getExchange() + { + return exchange; + } + + public void setExchange(AMQShortString exchange) + { + } + + public boolean isImmediate() + { + return (flags & IMMEDIATE_FLAG) != 0; + } + + public boolean isMandatory() + { + return (flags & MANDATORY_FLAG) != 0; + } + + public AMQShortString getRoutingKey() + { + return routingKey; + } + }; + return new MessageMetaData(publishBody, chb, 0, arrivalTime); + } + catch (AMQException e) + { + throw new RuntimeException(e); + } + + } + }; + + public AMQMessageHeader getMessageHeader() + { + return new MessageHeaderAdapter(); + } + + private final class MessageHeaderAdapter implements AMQMessageHeader + { + private BasicContentHeaderProperties getProperties() + { + return (BasicContentHeaderProperties) getContentHeaderBody().getProperties(); + } + + public String getCorrelationId() + { + return getProperties().getCorrelationIdAsString(); + } + + public long getExpiration() + { + return getProperties().getExpiration(); + } + + public String getMessageId() + { + return getProperties().getMessageIdAsString(); + } + + public String getMimeType() + { + return getProperties().getContentTypeAsString(); + } + + public String getEncoding() + { + return getProperties().getEncodingAsString(); + } + + public byte getPriority() + { + return getProperties().getPriority(); + } + + public long getTimestamp() + { + return getProperties().getTimestamp(); + } + + public String getType() + { + return getProperties().getTypeAsString(); + } + + public String getReplyTo() + { + return getProperties().getReplyToAsString(); + } + + public String getReplyToExchange() + { + // TODO + return getReplyTo(); + } + + public String getReplyToRoutingKey() + { + // TODO + return getReplyTo(); + } + + public Object getHeader(String name) + { + FieldTable ft = getProperties().getHeaders(); + return ft.get(name); + } + + public boolean containsHeaders(Set<String> names) + { + FieldTable ft = getProperties().getHeaders(); + for(String name : names) + { + if(!ft.containsKey(name)) + { + return false; + } + } + return true; + } + + public boolean containsHeader(String name) + { + FieldTable ft = getProperties().getHeaders(); + return ft.containsKey(name); + } + + + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/message/MessageMetaData_0_10.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/MessageMetaData_0_10.java new file mode 100755 index 0000000000..cf8ae2166c --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/MessageMetaData_0_10.java @@ -0,0 +1,242 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.message; + +import org.apache.qpid.server.store.StorableMessageMetaData; +import org.apache.qpid.server.store.MessageMetaDataType; +import org.apache.qpid.transport.MessageTransfer; +import org.apache.qpid.transport.DeliveryProperties; +import org.apache.qpid.transport.MessageProperties; +import org.apache.qpid.transport.Header; +import org.apache.qpid.transport.MessageDeliveryMode; +import org.apache.qpid.transport.Struct; +import org.apache.qpid.transport.codec.BBEncoder; +import org.apache.qpid.transport.codec.BBDecoder; + +import java.nio.ByteBuffer; +import java.lang.ref.SoftReference; + +public class MessageMetaData_0_10 implements StorableMessageMetaData +{ + private Header _header; + private DeliveryProperties _deliveryProps; + private MessageProperties _messageProps; + private MessageTransferHeader _messageHeader; + private long _arrivalTime; + private int _bodySize; + private volatile SoftReference<ByteBuffer> _body; + + private static final int ENCODER_SIZE = 1 << 16; + + public static final MessageMetaDataType.Factory<MessageMetaData_0_10> FACTORY = new MetaDataFactory(); + + private volatile ByteBuffer _encoded; + + + public MessageMetaData_0_10(MessageTransfer xfr) + { + this(xfr.getHeader(), xfr.getBodySize(), xfr.getBody(), System.currentTimeMillis()); + } + + private MessageMetaData_0_10(Header header, int bodySize, long arrivalTime) + { + this(header, bodySize, null, arrivalTime); + } + + private MessageMetaData_0_10(Header header, int bodySize, ByteBuffer xfrBody, long arrivalTime) + { + _header = header; + if(_header != null) + { + _deliveryProps = _header.get(DeliveryProperties.class); + _messageProps = _header.get(MessageProperties.class); + } + else + { + _deliveryProps = null; + _messageProps = null; + } + _messageHeader = new MessageTransferHeader(_deliveryProps, _messageProps); + _arrivalTime = arrivalTime; + _bodySize = bodySize; + + + + if(xfrBody == null) + { + _body = null; + } + else + { + ByteBuffer body = ByteBuffer.allocate(_bodySize); + body.put(xfrBody); + body.flip(); + _body = new SoftReference<ByteBuffer>(body); + } + + + } + + + + public MessageMetaDataType getType() + { + return MessageMetaDataType.META_DATA_0_10; + } + + public int getStorableSize() + { + ByteBuffer buf = _encoded; + + if(buf == null) + { + buf = encodeAsBuffer(); + _encoded = buf; + } + + //TODO -- need to add stuff + return buf.limit(); + } + + private ByteBuffer encodeAsBuffer() + { + BBEncoder encoder = new BBEncoder(ENCODER_SIZE); + + encoder.writeInt64(_arrivalTime); + encoder.writeInt32(_bodySize); + Struct[] headers = _header == null ? new Struct[0] : _header.getStructs(); + encoder.writeInt32(headers.length); + + + for(Struct header : headers) + { + encoder.writeStruct32(header); + + } + + ByteBuffer buf = encoder.buffer(); + return buf; + } + + public int writeToBuffer(int offsetInMetaData, ByteBuffer dest) + { + ByteBuffer buf = _encoded; + + if(buf == null) + { + buf = encodeAsBuffer(); + _encoded = buf; + } + + buf = buf.duplicate(); + + buf.position(offsetInMetaData); + + if(dest.remaining() < buf.limit()) + { + buf.limit(dest.remaining()); + } + dest.put(buf); + return buf.limit(); + } + + public int getContentSize() + { + return _bodySize; + } + + public boolean isPersistent() + { + return _deliveryProps == null ? false : _deliveryProps.getDeliveryMode() == MessageDeliveryMode.PERSISTENT; + } + + public String getRoutingKey() + { + return _deliveryProps == null ? null : _deliveryProps.getRoutingKey(); + } + + public AMQMessageHeader getMessageHeader() + { + return _messageHeader; + } + + public long getSize() + { + + return _bodySize; + } + + public boolean isImmediate() + { + return _deliveryProps != null && _deliveryProps.getImmediate(); + } + + public long getExpiration() + { + return _deliveryProps == null ? 0L : _deliveryProps.getExpiration(); + } + + public long getArrivalTime() + { + return _arrivalTime; + } + + public Header getHeader() + { + return _header; + } + + public ByteBuffer getBody() + { + ByteBuffer body = _body == null ? null : _body.get(); + return body; + } + + public void setBody(ByteBuffer body) + { + _body = new SoftReference<ByteBuffer>(body); + } + + private static class MetaDataFactory implements MessageMetaDataType.Factory<MessageMetaData_0_10> + { + public MessageMetaData_0_10 createMetaData(ByteBuffer buf) + { + BBDecoder decoder = new BBDecoder(); + decoder.init(buf); + + long arrivalTime = decoder.readInt64(); + int bodySize = decoder.readInt32(); + int headerCount = decoder.readInt32(); + + Struct[] headers = new Struct[headerCount]; + + for(int i = 0 ; i < headerCount; i++) + { + headers[i] = decoder.readStruct32(); + } + + Header header = new Header(headers); + + return new MessageMetaData_0_10(header, bodySize, arrivalTime); + + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/message/MessageReference.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/MessageReference.java new file mode 100644 index 0000000000..399f8f9327 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/MessageReference.java @@ -0,0 +1,58 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.message; + +import java.util.concurrent.atomic.AtomicBoolean; + +public abstract class MessageReference<M extends ServerMessage> +{ + + private final AtomicBoolean _released = new AtomicBoolean(false); + + private volatile M _message; + + public MessageReference(M message) + { + _message = message; + onReference(message); + } + + abstract protected void onReference(M message); + + abstract protected void onRelease(M message); + + public M getMessage() + { + return _message; + } + + public void release() + { + if(!_released.getAndSet(true)) + { + if(_message != null) + { + onRelease(_message); + } + } + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/message/MessageTransferHeader.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/MessageTransferHeader.java new file mode 100644 index 0000000000..31cf223428 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/MessageTransferHeader.java @@ -0,0 +1,153 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.message; + +import org.apache.qpid.transport.DeliveryProperties; +import org.apache.qpid.transport.MessageProperties; +import org.apache.qpid.transport.MessageDeliveryPriority; + +import java.util.Set; +import java.util.Map; +import java.util.UUID; + +class MessageTransferHeader implements AMQMessageHeader +{ + + + public static final String JMS_TYPE = "x-jms-type"; + + private final DeliveryProperties _deliveryProps; + private final MessageProperties _messageProps; + + public MessageTransferHeader(DeliveryProperties deliveryProps, MessageProperties messageProps) + { + _deliveryProps = deliveryProps; + _messageProps = messageProps; + } + + public String getCorrelationId() + { + if (_messageProps != null && _messageProps.getCorrelationId() != null) + { + return new String(_messageProps.getCorrelationId()); + } + else + { + return null; + } + } + + public long getExpiration() + { + return _deliveryProps == null ? 0L : _deliveryProps.getExpiration(); + } + + public String getMessageId() + { + UUID id = _messageProps == null ? null : _messageProps.getMessageId(); + + return id == null ? null : String.valueOf(id); + } + + public String getMimeType() + { + return _messageProps == null ? null : _messageProps.getContentType(); + } + + public String getEncoding() + { + return _messageProps == null ? null : _messageProps.getContentEncoding(); + } + + public byte getPriority() + { + MessageDeliveryPriority priority = _deliveryProps == null + ? MessageDeliveryPriority.MEDIUM + : _deliveryProps.getPriority(); + return (byte) priority.getValue(); + } + + public long getTimestamp() + { + return _deliveryProps == null ? 0L : _deliveryProps.getTimestamp(); + } + + public String getType() + { + Object type = getHeader(JMS_TYPE); + return type instanceof String ? (String) type : null; + } + + public String getReplyTo() + { + if (_messageProps != null && _messageProps.getReplyTo() != null) + { + return _messageProps.getReplyTo().toString(); + } + else + { + return null; + } + } + + public String getReplyToExchange() + { + if (_messageProps != null && _messageProps.getReplyTo() != null) + { + return _messageProps.getReplyTo().getExchange(); + } + else + { + return null; + } + } + + public String getReplyToRoutingKey() + { + if (_messageProps != null && _messageProps.getReplyTo() != null) + { + return _messageProps.getReplyTo().getRoutingKey(); + } + else + { + return null; + } + } + + public Object getHeader(String name) + { + Map<String, Object> appHeaders = _messageProps == null ? null : _messageProps.getApplicationHeaders(); + return appHeaders == null ? null : appHeaders.get(name); + } + + public boolean containsHeaders(Set<String> names) + { + Map<String, Object> appHeaders = _messageProps == null ? null : _messageProps.getApplicationHeaders(); + return appHeaders != null && appHeaders.keySet().containsAll(names); + + } + + public boolean containsHeader(String name) + { + Map<String, Object> appHeaders = _messageProps == null ? null : _messageProps.getApplicationHeaders(); + return appHeaders != null && appHeaders.containsKey(name); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/message/MessageTransferMessage.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/MessageTransferMessage.java new file mode 100644 index 0000000000..08006435f8 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/MessageTransferMessage.java @@ -0,0 +1,149 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.message; + +import org.apache.qpid.transport.*; +import org.apache.qpid.server.configuration.SessionConfig; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.transport.ServerSession; + +import java.nio.ByteBuffer; +import java.lang.ref.WeakReference; + + +public class MessageTransferMessage implements InboundMessage, ServerMessage +{ + + + private StoredMessage<MessageMetaData_0_10> _storeMessage; + + + private WeakReference<Session> _sessionRef; + + public MessageTransferMessage(StoredMessage<MessageMetaData_0_10> storeMessage, WeakReference<Session> sessionRef) + { + + _storeMessage = storeMessage; + _sessionRef = sessionRef; + } + + private MessageMetaData_0_10 getMetaData() + { + return _storeMessage.getMetaData(); + } + + public String getRoutingKey() + { + return getMetaData().getRoutingKey(); + + } + + public AMQMessageHeader getMessageHeader() + { + return getMetaData().getMessageHeader(); + } + + public boolean isPersistent() + { + return getMetaData().isPersistent(); + } + + + public boolean isRedelivered() + { + // The *Message* is never redelivered, only queue entries are... this is here so that filters + // can run against the message on entry to an exchange + return false; + } + + public long getSize() + { + + return getMetaData().getSize(); + } + + public boolean isImmediate() + { + return getMetaData().isImmediate(); + } + + public long getExpiration() + { + return getMetaData().getExpiration(); + } + + public MessageReference newReference() + { + return new TransferMessageReference(this); + } + + public Long getMessageNumber() + { + return _storeMessage.getMessageNumber(); + } + + public long getArrivalTime() + { + return getMetaData().getArrivalTime(); + } + + public int getContent(ByteBuffer buf, int offset) + { + return _storeMessage.getContent(offset, buf); + } + + public Header getHeader() + { + return getMetaData().getHeader(); + } + + public ByteBuffer getBody() + { + ByteBuffer body = getMetaData().getBody(); + if(body == null && getSize() != 0l) + { + final int size = (int) getSize(); + int pos = 0; + body = ByteBuffer.allocate(size); + + while(pos < size) + { + pos += getContent(body, pos); + } + + body.flip(); + + getMetaData().setBody(body.duplicate()); + } + return body; + } + + public Session getSession() + { + return _sessionRef == null ? null : _sessionRef.get(); + } + + public SessionConfig getSessionConfig() + { + return _sessionRef == null ? null : (ServerSession) _sessionRef.get(); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/message/ServerMessage.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/ServerMessage.java new file mode 100644 index 0000000000..2f2d39115f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/ServerMessage.java @@ -0,0 +1,50 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.message; + +import java.nio.ByteBuffer; + +import org.apache.qpid.server.configuration.SessionConfig; + +public interface ServerMessage extends EnqueableMessage, MessageContentSource +{ + String getRoutingKey(); + + AMQMessageHeader getMessageHeader(); + + boolean isPersistent(); + + long getSize(); + + boolean isImmediate(); + + long getExpiration(); + + MessageReference newReference(); + + Long getMessageNumber(); + + long getArrivalTime(); + + public int getContent(ByteBuffer buf, int offset); + + SessionConfig getSessionConfig(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/message/TransferMessageReference.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/TransferMessageReference.java new file mode 100644 index 0000000000..ed189c49c4 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/message/TransferMessageReference.java @@ -0,0 +1,39 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.message; + +public class TransferMessageReference extends MessageReference<MessageTransferMessage> +{ + public TransferMessageReference(MessageTransferMessage message) + { + super(message); + } + + protected void onReference(MessageTransferMessage message) + { + + } + + protected void onRelease(MessageTransferMessage message) + { + + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/output/HeaderPropertiesConverter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/HeaderPropertiesConverter.java new file mode 100755 index 0000000000..aded3f3d2a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/HeaderPropertiesConverter.java @@ -0,0 +1,124 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.output; + +import org.apache.qpid.server.message.MessageTransferMessage; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.transport.Header; +import org.apache.qpid.transport.DeliveryProperties; +import org.apache.qpid.transport.MessageProperties; +import org.apache.qpid.transport.MessageDeliveryMode; +import org.apache.qpid.AMQPInvalidClassException; + +import java.util.Map; + +public class HeaderPropertiesConverter +{ + + public static BasicContentHeaderProperties convert(MessageTransferMessage messageTransferMessage) + { + BasicContentHeaderProperties props = new BasicContentHeaderProperties(); + + Header header = messageTransferMessage.getHeader(); + DeliveryProperties deliveryProps = header.get(DeliveryProperties.class); + MessageProperties messageProps = header.get(MessageProperties.class); + + if(deliveryProps != null) + { + if(deliveryProps.hasDeliveryMode()) + { + props.setDeliveryMode((byte)(deliveryProps.getDeliveryMode() == MessageDeliveryMode.PERSISTENT ? BasicContentHeaderProperties.PERSISTENT : BasicContentHeaderProperties.NON_PERSISTENT)); + } + if(deliveryProps.hasExpiration()) + { + props.setExpiration(deliveryProps.getExpiration()); + } + if(deliveryProps.hasPriority()) + { + props.setPriority((byte)deliveryProps.getPriority().getValue()); + } + if(deliveryProps.hasTimestamp()) + { + props.setTimestamp(deliveryProps.getTimestamp()); + } + } + if(messageProps != null) + { + if(messageProps.hasAppId()) + { + props.setAppId(new AMQShortString(messageProps.getAppId())); + } + if(messageProps.hasContentType()) + { + props.setContentType(messageProps.getContentType()); + } + if(messageProps.hasCorrelationId()) + { + props.setCorrelationId(new AMQShortString(messageProps.getCorrelationId())); + } + if(messageProps.hasContentEncoding()) + { + props.setEncoding(messageProps.getContentEncoding()); + } + if(messageProps.hasMessageId()) + { + props.setMessageId(messageProps.getMessageId().toString()); + } + + // TODO Reply-to + + if(messageProps.hasUserId()) + { + props.setUserId(new AMQShortString(messageProps.getUserId())); + } + + if(messageProps.hasApplicationHeaders()) + { + Map<String, Object> appHeaders = messageProps.getApplicationHeaders(); + FieldTable ft = new FieldTable(); + for(Map.Entry<String, Object> entry : appHeaders.entrySet()) + { + try + { + ft.put(new AMQShortString(entry.getKey()), entry.getValue()); + } + catch(AMQPInvalidClassException e) + { + // TODO + // log here, but ignore - just can;t convert + } + } + props.setHeaders(ft); + + } + } + + + + + + + + return props; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/output/ProtocolOutputConverter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/ProtocolOutputConverter.java new file mode 100644 index 0000000000..5300bad613 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/ProtocolOutputConverter.java @@ -0,0 +1,60 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ *
+ */
+
+/*
+ * This file is auto-generated by Qpid Gentools v.0.1 - do not modify.
+ * Supported AMQP versions:
+ * 8-0
+ */
+package org.apache.qpid.server.output;
+
+import org.apache.qpid.server.queue.QueueEntry;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.message.MessageContentSource;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.AMQDataBlock;
+import org.apache.qpid.framing.ContentHeaderBody;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.AMQException;
+
+public interface ProtocolOutputConverter
+{
+ void confirmConsumerAutoClose(int channelId, AMQShortString consumerTag);
+
+ interface Factory
+ {
+ ProtocolOutputConverter newInstance(AMQProtocolSession session);
+ }
+
+ void writeDeliver(QueueEntry entry, int channelId, long deliveryTag, AMQShortString consumerTag)
+ throws AMQException;
+
+ void writeGetOk(QueueEntry message, int channelId, long deliveryTag, int queueSize) throws AMQException;
+
+ byte getProtocolMinorVersion();
+
+ byte getProtocolMajorVersion();
+
+ void writeReturn(MessagePublishInfo messagePublishInfo, ContentHeaderBody header, MessageContentSource msgContent, int channelId, int replyCode, AMQShortString replyText)
+ throws AMQException;
+
+ void writeFrame(AMQDataBlock block);
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/output/ProtocolOutputConverterRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/ProtocolOutputConverterRegistry.java new file mode 100644 index 0000000000..dbefeb61f2 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/ProtocolOutputConverterRegistry.java @@ -0,0 +1,61 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ *
+ */
+
+/*
+ * This file is auto-generated by Qpid Gentools v.0.1 - do not modify.
+ * Supported AMQP versions:
+ * 8-0
+ */
+package org.apache.qpid.server.output;
+
+import org.apache.qpid.server.output.ProtocolOutputConverter.Factory;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.framing.ProtocolVersion;
+
+import java.util.Map;
+import java.util.HashMap;
+
+public class ProtocolOutputConverterRegistry
+{
+
+ private static final Map<ProtocolVersion, Factory> _registry =
+ new HashMap<ProtocolVersion, Factory>();
+
+
+ static
+ {
+ register(ProtocolVersion.v8_0, org.apache.qpid.server.output.amqp0_8.ProtocolOutputConverterImpl.getInstanceFactory());
+ register(ProtocolVersion.v0_9, org.apache.qpid.server.output.amqp0_9.ProtocolOutputConverterImpl.getInstanceFactory());
+ register(ProtocolVersion.v0_91, org.apache.qpid.server.output.amqp0_9_1.ProtocolOutputConverterImpl.getInstanceFactory());
+ }
+
+ private static void register(ProtocolVersion version, Factory converter)
+ {
+
+ _registry.put(version,converter);
+ }
+
+
+ public static ProtocolOutputConverter getConverter(AMQProtocolSession session)
+ {
+ return _registry.get(session.getProtocolVersion()).newInstance(session);
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_8/ProtocolOutputConverterImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_8/ProtocolOutputConverterImpl.java new file mode 100644 index 0000000000..2cebec373e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_8/ProtocolOutputConverterImpl.java @@ -0,0 +1,273 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ *
+ */
+
+/*
+ * This file is auto-generated by Qpid Gentools v.0.1 - do not modify.
+ * Supported AMQP versions:
+ * 8-0
+ */
+package org.apache.qpid.server.output.amqp0_8;
+
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.message.AMQMessage;
+import org.apache.qpid.server.queue.QueueEntry;
+import org.apache.qpid.server.output.ProtocolOutputConverter;
+import org.apache.qpid.server.output.HeaderPropertiesConverter;
+import org.apache.qpid.server.message.MessageContentSource;
+import org.apache.qpid.server.message.MessageTransferMessage;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.framing.amqp_8_0.BasicGetBodyImpl;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.framing.abstraction.ProtocolVersionMethodConverter;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.transport.DeliveryProperties;
+
+import java.nio.ByteBuffer;
+
+public class ProtocolOutputConverterImpl implements ProtocolOutputConverter
+{
+
+ private static final MethodRegistry METHOD_REGISTRY = MethodRegistry.getMethodRegistry(ProtocolVersion.v8_0);
+
+ private static final ProtocolVersionMethodConverter PROTOCOL_CONVERTER =
+ METHOD_REGISTRY.getProtocolVersionMethodConverter();
+
+ public static Factory getInstanceFactory()
+ {
+ return new Factory()
+ {
+
+ public ProtocolOutputConverter newInstance(AMQProtocolSession session)
+ {
+ return new ProtocolOutputConverterImpl(session);
+ }
+ };
+ }
+
+ private final AMQProtocolSession _protocolSession;
+
+ private ProtocolOutputConverterImpl(AMQProtocolSession session)
+ {
+ _protocolSession = session;
+ }
+
+
+ public AMQProtocolSession getProtocolSession()
+ {
+ return _protocolSession;
+ }
+
+ public void writeDeliver(QueueEntry entry, int channelId, long deliveryTag, AMQShortString consumerTag)
+ throws AMQException
+ {
+ AMQDataBlock deliver = createEncodedDeliverFrame(entry, channelId, deliveryTag, consumerTag);
+ writeMessageDelivery(entry.getMessage(), getContentHeaderBody(entry), channelId, deliver);
+ }
+
+ private ContentHeaderBody getContentHeaderBody(QueueEntry entry)
+ throws AMQException
+ {
+ if(entry.getMessage() instanceof AMQMessage)
+ {
+ return ((AMQMessage)entry.getMessage()).getContentHeaderBody();
+ }
+ else
+ {
+ final MessageTransferMessage message = (MessageTransferMessage) entry.getMessage();
+ BasicContentHeaderProperties props = HeaderPropertiesConverter.convert(message);
+ ContentHeaderBody chb = new ContentHeaderBody(props, BasicGetBodyImpl.CLASS_ID);
+ chb.bodySize = message.getSize();
+ return chb;
+ }
+ }
+
+
+ public void writeGetOk(QueueEntry entry, int channelId, long deliveryTag, int queueSize) throws AMQException
+ {
+ AMQDataBlock deliver = createEncodedGetOkFrame(entry, channelId, deliveryTag, queueSize);
+ writeMessageDelivery(entry.getMessage(), getContentHeaderBody(entry), channelId, deliver);
+ }
+
+ private void writeMessageDelivery(MessageContentSource message, ContentHeaderBody chb, int channelId, AMQDataBlock deliver)
+ throws AMQException
+ {
+
+
+ AMQDataBlock contentHeader = ContentHeaderBody.createAMQFrame(channelId, chb);
+
+
+ final int bodySize = (int) message.getSize();
+ if(bodySize == 0)
+ {
+ SmallCompositeAMQDataBlock compositeBlock = new SmallCompositeAMQDataBlock(deliver,
+ contentHeader);
+ writeFrame(compositeBlock);
+ }
+ else
+ {
+ int maxBodySize = (int) getProtocolSession().getMaxFrameSize() - AMQFrame.getFrameOverhead();
+
+ final int capacity = bodySize > maxBodySize ? maxBodySize : bodySize;
+ ByteBuffer buf = ByteBuffer.allocate(capacity);
+
+ int writtenSize = 0;
+
+ writtenSize += message.getContent(buf, writtenSize);
+ buf.flip();
+ AMQDataBlock firstContentBody = new AMQFrame(channelId, PROTOCOL_CONVERTER.convertToBody(buf));
+ AMQDataBlock[] blocks = new AMQDataBlock[]{deliver, contentHeader, firstContentBody};
+ CompositeAMQDataBlock compositeBlock = new CompositeAMQDataBlock(blocks);
+ writeFrame(compositeBlock);
+
+ while(writtenSize < bodySize)
+ {
+ buf = java.nio.ByteBuffer.allocate(capacity);
+ writtenSize += message.getContent(buf, writtenSize);
+ buf.flip();
+ writeFrame(new AMQFrame(channelId, PROTOCOL_CONVERTER.convertToBody(buf)));
+ }
+
+ }
+ }
+
+
+ private AMQDataBlock createEncodedDeliverFrame(QueueEntry entry, int channelId, long deliveryTag, AMQShortString consumerTag)
+ throws AMQException
+ {
+ final AMQShortString exchangeName;
+ final AMQShortString routingKey;
+
+ if(entry.getMessage() instanceof AMQMessage)
+ {
+ final AMQMessage message = (AMQMessage) entry.getMessage();
+ final MessagePublishInfo pb = message.getMessagePublishInfo();
+ exchangeName = pb.getExchange();
+ routingKey = pb.getRoutingKey();
+ }
+ else
+ {
+ MessageTransferMessage message = (MessageTransferMessage) entry.getMessage();
+ DeliveryProperties delvProps = message.getHeader().get(DeliveryProperties.class);
+ exchangeName = (delvProps == null || delvProps.getExchange() == null) ? null : new AMQShortString(delvProps.getExchange());
+ routingKey = (delvProps == null || delvProps.getRoutingKey() == null) ? null : new AMQShortString(delvProps.getRoutingKey());
+ }
+
+ final boolean isRedelivered = entry.isRedelivered();
+
+
+ BasicDeliverBody deliverBody =
+ METHOD_REGISTRY.createBasicDeliverBody(consumerTag,
+ deliveryTag,
+ isRedelivered,
+ exchangeName,
+ routingKey);
+
+ AMQFrame deliverFrame = deliverBody.generateFrame(channelId);
+
+
+ return deliverFrame;
+ }
+
+ private AMQDataBlock createEncodedGetOkFrame(QueueEntry entry, int channelId, long deliveryTag, int queueSize)
+ throws AMQException
+ {
+ final AMQShortString exchangeName;
+ final AMQShortString routingKey;
+
+ if(entry.getMessage() instanceof AMQMessage)
+ {
+ final AMQMessage message = (AMQMessage) entry.getMessage();
+ final MessagePublishInfo pb = message.getMessagePublishInfo();
+ exchangeName = pb.getExchange();
+ routingKey = pb.getRoutingKey();
+ }
+ else
+ {
+ MessageTransferMessage message = (MessageTransferMessage) entry.getMessage();
+ DeliveryProperties delvProps = message.getHeader().get(DeliveryProperties.class);
+ exchangeName = (delvProps == null || delvProps.getExchange() == null) ? null : new AMQShortString(delvProps.getExchange());
+ routingKey = (delvProps == null || delvProps.getRoutingKey() == null) ? null : new AMQShortString(delvProps.getRoutingKey());
+ }
+
+ final boolean isRedelivered = entry.isRedelivered();
+
+ BasicGetOkBody getOkBody =
+ METHOD_REGISTRY.createBasicGetOkBody(deliveryTag,
+ isRedelivered,
+ exchangeName,
+ routingKey,
+ queueSize);
+ AMQFrame getOkFrame = getOkBody.generateFrame(channelId);
+
+ return getOkFrame;
+ }
+
+ public byte getProtocolMinorVersion()
+ {
+ return getProtocolSession().getProtocolMinorVersion();
+ }
+
+ public byte getProtocolMajorVersion()
+ {
+ return getProtocolSession().getProtocolMajorVersion();
+ }
+
+ private AMQDataBlock createEncodedReturnFrame(MessagePublishInfo messagePublishInfo, int channelId, int replyCode, AMQShortString replyText) throws AMQException
+ {
+ BasicReturnBody basicReturnBody =
+ METHOD_REGISTRY.createBasicReturnBody(replyCode,
+ replyText,
+ messagePublishInfo.getExchange(),
+ messagePublishInfo.getRoutingKey());
+ AMQFrame returnFrame = basicReturnBody.generateFrame(channelId);
+
+ return returnFrame;
+ }
+
+ public void writeReturn(MessagePublishInfo messagePublishInfo,
+ ContentHeaderBody header,
+ MessageContentSource content,
+ int channelId,
+ int replyCode,
+ AMQShortString replyText)
+ throws AMQException
+ {
+
+ AMQDataBlock returnFrame = createEncodedReturnFrame(messagePublishInfo, channelId, replyCode, replyText);
+
+ writeMessageDelivery(content, header, channelId, returnFrame);
+
+ }
+
+
+ public void writeFrame(AMQDataBlock block)
+ {
+ getProtocolSession().writeFrame(block);
+ }
+
+
+ public void confirmConsumerAutoClose(int channelId, AMQShortString consumerTag)
+ {
+ BasicCancelOkBody basicCancelOkBody = METHOD_REGISTRY.createBasicCancelOkBody(consumerTag);
+ writeFrame(basicCancelOkBody.generateFrame(channelId));
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_9/ProtocolOutputConverterImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_9/ProtocolOutputConverterImpl.java new file mode 100644 index 0000000000..319b5cc7bd --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_9/ProtocolOutputConverterImpl.java @@ -0,0 +1,383 @@ +package org.apache.qpid.server.output.amqp0_9;
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ *
+ */
+
+
+import org.apache.mina.common.ByteBuffer;
+
+import org.apache.qpid.server.output.ProtocolOutputConverter;
+import org.apache.qpid.server.output.HeaderPropertiesConverter;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.message.AMQMessage;
+import org.apache.qpid.server.queue.QueueEntry;
+import org.apache.qpid.server.message.MessageContentSource;
+import org.apache.qpid.server.message.MessageTransferMessage;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.framing.amqp_0_9.BasicGetBodyImpl;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.framing.abstraction.ProtocolVersionMethodConverter;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.transport.DeliveryProperties;
+import org.apache.qpid.protocol.AMQVersionAwareProtocolSession;
+
+public class ProtocolOutputConverterImpl implements ProtocolOutputConverter
+{
+ private static final MethodRegistry METHOD_REGISTRY = MethodRegistry.getMethodRegistry(ProtocolVersion.v0_9);
+ private static final ProtocolVersionMethodConverter
+ PROTOCOL_CONVERTER = METHOD_REGISTRY.getProtocolVersionMethodConverter();
+
+
+ public static Factory getInstanceFactory()
+ {
+ return new Factory()
+ {
+
+ public ProtocolOutputConverter newInstance(AMQProtocolSession session)
+ {
+ return new ProtocolOutputConverterImpl(session);
+ }
+ };
+ }
+
+ private final AMQProtocolSession _protocolSession;
+
+ private ProtocolOutputConverterImpl(AMQProtocolSession session)
+ {
+ _protocolSession = session;
+ }
+
+
+ public AMQProtocolSession getProtocolSession()
+ {
+ return _protocolSession;
+ }
+
+ public void writeDeliver(QueueEntry entry, int channelId, long deliveryTag, AMQShortString consumerTag)
+ throws AMQException
+ {
+ AMQBody deliverBody = createEncodedDeliverBody(entry, deliveryTag, consumerTag);
+ writeMessageDelivery(entry, channelId, deliverBody);
+ }
+
+
+ private ContentHeaderBody getContentHeaderBody(QueueEntry entry)
+ throws AMQException
+ {
+ if(entry.getMessage() instanceof AMQMessage)
+ {
+ return ((AMQMessage)entry.getMessage()).getContentHeaderBody();
+ }
+ else
+ {
+ final MessageTransferMessage message = (MessageTransferMessage) entry.getMessage();
+ BasicContentHeaderProperties props = HeaderPropertiesConverter.convert(message);
+ ContentHeaderBody chb = new ContentHeaderBody(props, BasicGetBodyImpl.CLASS_ID);
+ chb.bodySize = message.getSize();
+ return chb;
+ }
+ }
+
+
+ private void writeMessageDelivery(QueueEntry entry, int channelId, AMQBody deliverBody)
+ throws AMQException
+ {
+ writeMessageDelivery(entry.getMessage(), getContentHeaderBody(entry), channelId, deliverBody);
+ }
+
+ private void writeMessageDelivery(MessageContentSource message, ContentHeaderBody contentHeaderBody, int channelId, AMQBody deliverBody)
+ throws AMQException
+ {
+
+
+ int bodySize = (int) message.getSize();
+
+ if(bodySize == 0)
+ {
+ SmallCompositeAMQBodyBlock compositeBlock = new SmallCompositeAMQBodyBlock(channelId, deliverBody,
+ contentHeaderBody);
+
+ writeFrame(compositeBlock);
+ }
+ else
+ {
+ int maxBodySize = (int) getProtocolSession().getMaxFrameSize() - AMQFrame.getFrameOverhead();
+
+
+ final int capacity = bodySize > maxBodySize ? maxBodySize : bodySize;
+ java.nio.ByteBuffer buf = java.nio.ByteBuffer.allocate(capacity);
+
+ int writtenSize = 0;
+
+
+ writtenSize += message.getContent(buf, writtenSize);
+ buf.flip();
+ AMQBody firstContentBody = PROTOCOL_CONVERTER.convertToBody(buf);
+
+ CompositeAMQBodyBlock
+ compositeBlock = new CompositeAMQBodyBlock(channelId, deliverBody, contentHeaderBody, firstContentBody);
+ writeFrame(compositeBlock);
+
+ while(writtenSize < bodySize)
+ {
+ buf = java.nio.ByteBuffer.allocate(capacity);
+
+ writtenSize += message.getContent(buf, writtenSize);
+ buf.flip();
+ writeFrame(new AMQFrame(channelId, PROTOCOL_CONVERTER.convertToBody(buf)));
+ }
+ }
+ }
+
+ private AMQDataBlock createContentHeaderBlock(final int channelId, final ContentHeaderBody contentHeaderBody)
+ {
+
+ AMQDataBlock contentHeader = ContentHeaderBody.createAMQFrame(channelId,
+ contentHeaderBody);
+ return contentHeader;
+ }
+
+
+ public void writeGetOk(QueueEntry entry, int channelId, long deliveryTag, int queueSize) throws AMQException
+ {
+ AMQBody deliver = createEncodedGetOkBody(entry, deliveryTag, queueSize);
+ writeMessageDelivery(entry, channelId, deliver);
+ }
+
+
+ private AMQBody createEncodedDeliverBody(QueueEntry entry,
+ final long deliveryTag,
+ final AMQShortString consumerTag)
+ throws AMQException
+ {
+
+ final AMQShortString exchangeName;
+ final AMQShortString routingKey;
+
+ if(entry.getMessage() instanceof AMQMessage)
+ {
+ final AMQMessage message = (AMQMessage) entry.getMessage();
+ final MessagePublishInfo pb = message.getMessagePublishInfo();
+ exchangeName = pb.getExchange();
+ routingKey = pb.getRoutingKey();
+ }
+ else
+ {
+ MessageTransferMessage message = (MessageTransferMessage) entry.getMessage();
+ DeliveryProperties delvProps = message.getHeader().get(DeliveryProperties.class);
+ exchangeName = (delvProps == null || delvProps.getExchange() == null) ? null : new AMQShortString(delvProps.getExchange());
+ routingKey = (delvProps == null || delvProps.getRoutingKey() == null) ? null : new AMQShortString(delvProps.getRoutingKey());
+ }
+
+ final boolean isRedelivered = entry.isRedelivered();
+
+ final AMQBody returnBlock = new AMQBody()
+ {
+
+ public AMQBody _underlyingBody;
+
+ public AMQBody createAMQBody()
+ {
+ return METHOD_REGISTRY.createBasicDeliverBody(consumerTag,
+ deliveryTag,
+ isRedelivered,
+ exchangeName,
+ routingKey);
+
+
+
+
+
+ }
+
+ public byte getFrameType()
+ {
+ return AMQMethodBody.TYPE;
+ }
+
+ public int getSize()
+ {
+ if(_underlyingBody == null)
+ {
+ _underlyingBody = createAMQBody();
+ }
+ return _underlyingBody.getSize();
+ }
+
+ public void writePayload(ByteBuffer buffer)
+ {
+ if(_underlyingBody == null)
+ {
+ _underlyingBody = createAMQBody();
+ }
+ _underlyingBody.writePayload(buffer);
+ }
+
+ public void handle(final int channelId, final AMQVersionAwareProtocolSession amqMinaProtocolSession)
+ throws AMQException
+ {
+ throw new AMQException("This block should never be dispatched!");
+ }
+ };
+ return returnBlock;
+ }
+
+ private AMQBody createEncodedGetOkBody(QueueEntry entry, long deliveryTag, int queueSize)
+ throws AMQException
+ {
+ final AMQShortString exchangeName;
+ final AMQShortString routingKey;
+
+ if(entry.getMessage() instanceof AMQMessage)
+ {
+ final AMQMessage message = (AMQMessage) entry.getMessage();
+ final MessagePublishInfo pb = message.getMessagePublishInfo();
+ exchangeName = pb.getExchange();
+ routingKey = pb.getRoutingKey();
+ }
+ else
+ {
+ MessageTransferMessage message = (MessageTransferMessage) entry.getMessage();
+ DeliveryProperties delvProps = message.getHeader().get(DeliveryProperties.class);
+ exchangeName = (delvProps == null || delvProps.getExchange() == null) ? null : new AMQShortString(delvProps.getExchange());
+ routingKey = (delvProps == null || delvProps.getRoutingKey() == null) ? null : new AMQShortString(delvProps.getRoutingKey());
+ }
+
+ final boolean isRedelivered = entry.isRedelivered();
+
+ BasicGetOkBody getOkBody =
+ METHOD_REGISTRY.createBasicGetOkBody(deliveryTag,
+ isRedelivered,
+ exchangeName,
+ routingKey,
+ queueSize);
+
+ return getOkBody;
+ }
+
+ public byte getProtocolMinorVersion()
+ {
+ return getProtocolSession().getProtocolMinorVersion();
+ }
+
+ public byte getProtocolMajorVersion()
+ {
+ return getProtocolSession().getProtocolMajorVersion();
+ }
+
+ private AMQBody createEncodedReturnFrame(MessagePublishInfo messagePublishInfo,
+ int replyCode,
+ AMQShortString replyText) throws AMQException
+ {
+
+ BasicReturnBody basicReturnBody =
+ METHOD_REGISTRY.createBasicReturnBody(replyCode,
+ replyText,
+ messagePublishInfo.getExchange(),
+ messagePublishInfo.getRoutingKey());
+
+
+ return basicReturnBody;
+ }
+
+ public void writeReturn(MessagePublishInfo messagePublishInfo, ContentHeaderBody header, MessageContentSource message, int channelId, int replyCode, AMQShortString replyText)
+ throws AMQException
+ {
+
+ AMQBody returnFrame = createEncodedReturnFrame(messagePublishInfo, replyCode, replyText);
+
+ writeMessageDelivery(message, header, channelId, returnFrame);
+ }
+
+
+ public void writeFrame(AMQDataBlock block)
+ {
+ getProtocolSession().writeFrame(block);
+ }
+
+
+ public void confirmConsumerAutoClose(int channelId, AMQShortString consumerTag)
+ {
+
+ BasicCancelOkBody basicCancelOkBody = METHOD_REGISTRY.createBasicCancelOkBody(consumerTag);
+ writeFrame(basicCancelOkBody.generateFrame(channelId));
+
+ }
+
+
+ public static final class CompositeAMQBodyBlock extends AMQDataBlock
+ {
+ public static final int OVERHEAD = 3 * AMQFrame.getFrameOverhead();
+
+ private final AMQBody _methodBody;
+ private final AMQBody _headerBody;
+ private final AMQBody _contentBody;
+ private final int _channel;
+
+
+ public CompositeAMQBodyBlock(int channel, AMQBody methodBody, AMQBody headerBody, AMQBody contentBody)
+ {
+ _channel = channel;
+ _methodBody = methodBody;
+ _headerBody = headerBody;
+ _contentBody = contentBody;
+
+ }
+
+ public long getSize()
+ {
+ return OVERHEAD + _methodBody.getSize() + _headerBody.getSize() + _contentBody.getSize();
+ }
+
+ public void writePayload(ByteBuffer buffer)
+ {
+ AMQFrame.writeFrames(buffer, _channel, _methodBody, _headerBody, _contentBody);
+ }
+ }
+
+ public static final class SmallCompositeAMQBodyBlock extends AMQDataBlock
+ {
+ public static final int OVERHEAD = 2 * AMQFrame.getFrameOverhead();
+
+ private final AMQBody _methodBody;
+ private final AMQBody _headerBody;
+ private final int _channel;
+
+
+ public SmallCompositeAMQBodyBlock(int channel, AMQBody methodBody, AMQBody headerBody)
+ {
+ _channel = channel;
+ _methodBody = methodBody;
+ _headerBody = headerBody;
+
+ }
+
+ public long getSize()
+ {
+ return OVERHEAD + _methodBody.getSize() + _headerBody.getSize() ;
+ }
+
+ public void writePayload(ByteBuffer buffer)
+ {
+ AMQFrame.writeFrames(buffer, _channel, _methodBody, _headerBody);
+ }
+ }
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_9_1/ProtocolOutputConverterImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_9_1/ProtocolOutputConverterImpl.java new file mode 100644 index 0000000000..cffbe445ee --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/output/amqp0_9_1/ProtocolOutputConverterImpl.java @@ -0,0 +1,383 @@ +package org.apache.qpid.server.output.amqp0_9_1;
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ *
+ */
+
+
+import org.apache.mina.common.ByteBuffer;
+
+import org.apache.qpid.server.output.ProtocolOutputConverter;
+import org.apache.qpid.server.output.HeaderPropertiesConverter;
+import org.apache.qpid.server.protocol.AMQProtocolSession;
+import org.apache.qpid.server.message.AMQMessage;
+import org.apache.qpid.server.queue.QueueEntry;
+import org.apache.qpid.server.message.MessageContentSource;
+import org.apache.qpid.server.message.MessageTransferMessage;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.framing.amqp_0_91.BasicGetBodyImpl;
+import org.apache.qpid.framing.abstraction.MessagePublishInfo;
+import org.apache.qpid.framing.abstraction.ProtocolVersionMethodConverter;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.transport.DeliveryProperties;
+import org.apache.qpid.protocol.AMQVersionAwareProtocolSession;
+
+public class ProtocolOutputConverterImpl implements ProtocolOutputConverter
+{
+ private static final MethodRegistry METHOD_REGISTRY = MethodRegistry.getMethodRegistry(ProtocolVersion.v0_91);
+ private static final ProtocolVersionMethodConverter
+ PROTOCOL_CONVERTER = METHOD_REGISTRY.getProtocolVersionMethodConverter();
+
+
+ public static Factory getInstanceFactory()
+ {
+ return new Factory()
+ {
+
+ public ProtocolOutputConverter newInstance(AMQProtocolSession session)
+ {
+ return new ProtocolOutputConverterImpl(session);
+ }
+ };
+ }
+
+ private final AMQProtocolSession _protocolSession;
+
+ private ProtocolOutputConverterImpl(AMQProtocolSession session)
+ {
+ _protocolSession = session;
+ }
+
+
+ public AMQProtocolSession getProtocolSession()
+ {
+ return _protocolSession;
+ }
+
+ public void writeDeliver(QueueEntry entry, int channelId, long deliveryTag, AMQShortString consumerTag)
+ throws AMQException
+ {
+ AMQBody deliverBody = createEncodedDeliverBody(entry, deliveryTag, consumerTag);
+ writeMessageDelivery(entry, channelId, deliverBody);
+ }
+
+
+ private ContentHeaderBody getContentHeaderBody(QueueEntry entry)
+ throws AMQException
+ {
+ if(entry.getMessage() instanceof AMQMessage)
+ {
+ return ((AMQMessage)entry.getMessage()).getContentHeaderBody();
+ }
+ else
+ {
+ final MessageTransferMessage message = (MessageTransferMessage) entry.getMessage();
+ BasicContentHeaderProperties props = HeaderPropertiesConverter.convert(message);
+ ContentHeaderBody chb = new ContentHeaderBody(props, BasicGetBodyImpl.CLASS_ID);
+ chb.bodySize = message.getSize();
+ return chb;
+ }
+ }
+
+
+ private void writeMessageDelivery(QueueEntry entry, int channelId, AMQBody deliverBody)
+ throws AMQException
+ {
+ writeMessageDelivery(entry.getMessage(), getContentHeaderBody(entry), channelId, deliverBody);
+ }
+
+ private void writeMessageDelivery(MessageContentSource message, ContentHeaderBody contentHeaderBody, int channelId, AMQBody deliverBody)
+ throws AMQException
+ {
+
+
+ int bodySize = (int) message.getSize();
+
+ if(bodySize == 0)
+ {
+ SmallCompositeAMQBodyBlock compositeBlock = new SmallCompositeAMQBodyBlock(channelId, deliverBody,
+ contentHeaderBody);
+
+ writeFrame(compositeBlock);
+ }
+ else
+ {
+ int maxBodySize = (int) getProtocolSession().getMaxFrameSize() - AMQFrame.getFrameOverhead();
+
+
+ final int capacity = bodySize > maxBodySize ? maxBodySize : bodySize;
+ java.nio.ByteBuffer buf = java.nio.ByteBuffer.allocate(capacity);
+
+ int writtenSize = 0;
+
+
+ writtenSize += message.getContent(buf, writtenSize);
+ buf.flip();
+ AMQBody firstContentBody = PROTOCOL_CONVERTER.convertToBody(buf);
+
+ CompositeAMQBodyBlock
+ compositeBlock = new CompositeAMQBodyBlock(channelId, deliverBody, contentHeaderBody, firstContentBody);
+ writeFrame(compositeBlock);
+
+ while(writtenSize < bodySize)
+ {
+ buf = java.nio.ByteBuffer.allocate(capacity);
+
+ writtenSize += message.getContent(buf, writtenSize);
+ buf.flip();
+ writeFrame(new AMQFrame(channelId, PROTOCOL_CONVERTER.convertToBody(buf)));
+ }
+ }
+ }
+
+ private AMQDataBlock createContentHeaderBlock(final int channelId, final ContentHeaderBody contentHeaderBody)
+ {
+
+ AMQDataBlock contentHeader = ContentHeaderBody.createAMQFrame(channelId,
+ contentHeaderBody);
+ return contentHeader;
+ }
+
+
+ public void writeGetOk(QueueEntry entry, int channelId, long deliveryTag, int queueSize) throws AMQException
+ {
+ AMQBody deliver = createEncodedGetOkBody(entry, deliveryTag, queueSize);
+ writeMessageDelivery(entry, channelId, deliver);
+ }
+
+
+ private AMQBody createEncodedDeliverBody(QueueEntry entry,
+ final long deliveryTag,
+ final AMQShortString consumerTag)
+ throws AMQException
+ {
+
+ final AMQShortString exchangeName;
+ final AMQShortString routingKey;
+
+ if(entry.getMessage() instanceof AMQMessage)
+ {
+ final AMQMessage message = (AMQMessage) entry.getMessage();
+ final MessagePublishInfo pb = message.getMessagePublishInfo();
+ exchangeName = pb.getExchange();
+ routingKey = pb.getRoutingKey();
+ }
+ else
+ {
+ MessageTransferMessage message = (MessageTransferMessage) entry.getMessage();
+ DeliveryProperties delvProps = message.getHeader().get(DeliveryProperties.class);
+ exchangeName = (delvProps == null || delvProps.getExchange() == null) ? null : new AMQShortString(delvProps.getExchange());
+ routingKey = (delvProps == null || delvProps.getRoutingKey() == null) ? null : new AMQShortString(delvProps.getRoutingKey());
+ }
+
+ final boolean isRedelivered = entry.isRedelivered();
+
+ final AMQBody returnBlock = new AMQBody()
+ {
+
+ public AMQBody _underlyingBody;
+
+ public AMQBody createAMQBody()
+ {
+ return METHOD_REGISTRY.createBasicDeliverBody(consumerTag,
+ deliveryTag,
+ isRedelivered,
+ exchangeName,
+ routingKey);
+
+
+
+
+
+ }
+
+ public byte getFrameType()
+ {
+ return AMQMethodBody.TYPE;
+ }
+
+ public int getSize()
+ {
+ if(_underlyingBody == null)
+ {
+ _underlyingBody = createAMQBody();
+ }
+ return _underlyingBody.getSize();
+ }
+
+ public void writePayload(ByteBuffer buffer)
+ {
+ if(_underlyingBody == null)
+ {
+ _underlyingBody = createAMQBody();
+ }
+ _underlyingBody.writePayload(buffer);
+ }
+
+ public void handle(final int channelId, final AMQVersionAwareProtocolSession amqMinaProtocolSession)
+ throws AMQException
+ {
+ throw new AMQException("This block should never be dispatched!");
+ }
+ };
+ return returnBlock;
+ }
+
+ private AMQBody createEncodedGetOkBody(QueueEntry entry, long deliveryTag, int queueSize)
+ throws AMQException
+ {
+ final AMQShortString exchangeName;
+ final AMQShortString routingKey;
+
+ if(entry.getMessage() instanceof AMQMessage)
+ {
+ final AMQMessage message = (AMQMessage) entry.getMessage();
+ final MessagePublishInfo pb = message.getMessagePublishInfo();
+ exchangeName = pb.getExchange();
+ routingKey = pb.getRoutingKey();
+ }
+ else
+ {
+ MessageTransferMessage message = (MessageTransferMessage) entry.getMessage();
+ DeliveryProperties delvProps = message.getHeader().get(DeliveryProperties.class);
+ exchangeName = (delvProps == null || delvProps.getExchange() == null) ? null : new AMQShortString(delvProps.getExchange());
+ routingKey = (delvProps == null || delvProps.getRoutingKey() == null) ? null : new AMQShortString(delvProps.getRoutingKey());
+ }
+
+ final boolean isRedelivered = entry.isRedelivered();
+
+ BasicGetOkBody getOkBody =
+ METHOD_REGISTRY.createBasicGetOkBody(deliveryTag,
+ isRedelivered,
+ exchangeName,
+ routingKey,
+ queueSize);
+
+ return getOkBody;
+ }
+
+ public byte getProtocolMinorVersion()
+ {
+ return getProtocolSession().getProtocolMinorVersion();
+ }
+
+ public byte getProtocolMajorVersion()
+ {
+ return getProtocolSession().getProtocolMajorVersion();
+ }
+
+ private AMQBody createEncodedReturnFrame(MessagePublishInfo messagePublishInfo,
+ int replyCode,
+ AMQShortString replyText) throws AMQException
+ {
+
+ BasicReturnBody basicReturnBody =
+ METHOD_REGISTRY.createBasicReturnBody(replyCode,
+ replyText,
+ messagePublishInfo.getExchange(),
+ messagePublishInfo.getRoutingKey());
+
+
+ return basicReturnBody;
+ }
+
+ public void writeReturn(MessagePublishInfo messagePublishInfo, ContentHeaderBody header, MessageContentSource message, int channelId, int replyCode, AMQShortString replyText)
+ throws AMQException
+ {
+
+ AMQBody returnFrame = createEncodedReturnFrame(messagePublishInfo, replyCode, replyText);
+
+ writeMessageDelivery(message, header, channelId, returnFrame);
+ }
+
+
+ public void writeFrame(AMQDataBlock block)
+ {
+ getProtocolSession().writeFrame(block);
+ }
+
+
+ public void confirmConsumerAutoClose(int channelId, AMQShortString consumerTag)
+ {
+
+ BasicCancelOkBody basicCancelOkBody = METHOD_REGISTRY.createBasicCancelOkBody(consumerTag);
+ writeFrame(basicCancelOkBody.generateFrame(channelId));
+
+ }
+
+
+ public static final class CompositeAMQBodyBlock extends AMQDataBlock
+ {
+ public static final int OVERHEAD = 3 * AMQFrame.getFrameOverhead();
+
+ private final AMQBody _methodBody;
+ private final AMQBody _headerBody;
+ private final AMQBody _contentBody;
+ private final int _channel;
+
+
+ public CompositeAMQBodyBlock(int channel, AMQBody methodBody, AMQBody headerBody, AMQBody contentBody)
+ {
+ _channel = channel;
+ _methodBody = methodBody;
+ _headerBody = headerBody;
+ _contentBody = contentBody;
+
+ }
+
+ public long getSize()
+ {
+ return OVERHEAD + _methodBody.getSize() + _headerBody.getSize() + _contentBody.getSize();
+ }
+
+ public void writePayload(ByteBuffer buffer)
+ {
+ AMQFrame.writeFrames(buffer, _channel, _methodBody, _headerBody, _contentBody);
+ }
+ }
+
+ public static final class SmallCompositeAMQBodyBlock extends AMQDataBlock
+ {
+ public static final int OVERHEAD = 2 * AMQFrame.getFrameOverhead();
+
+ private final AMQBody _methodBody;
+ private final AMQBody _headerBody;
+ private final int _channel;
+
+
+ public SmallCompositeAMQBodyBlock(int channel, AMQBody methodBody, AMQBody headerBody)
+ {
+ _channel = channel;
+ _methodBody = methodBody;
+ _headerBody = headerBody;
+
+ }
+
+ public long getSize()
+ {
+ return OVERHEAD + _methodBody.getSize() + _headerBody.getSize() ;
+ }
+
+ public void writePayload(ByteBuffer buffer)
+ {
+ AMQFrame.writeFrames(buffer, _channel, _methodBody, _headerBody);
+ }
+ }
+
+}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/Activator.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/Activator.java new file mode 100644 index 0000000000..df72e87fd8 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/Activator.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.plugins; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +public class Activator implements BundleActivator +{ + private static final Logger _logger = Logger.getLogger(Activator.class); + + private BundleContext _context = null; + + public void start(BundleContext ctx) throws Exception + { + _context = ctx; + _logger.info("Registering bundle: " + _context.getBundle().getSymbolicName()); + ctx.registerService(ServerConfiguration.class.getName(), ApplicationRegistry.getInstance().getConfiguration(), null); + } + + public void stop(BundleContext ctx) throws Exception + { + _logger.info("Stopping bundle: " + _context.getBundle().getSymbolicName()); + _context = null; + } + + public BundleContext getContext() + { + return _context; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/Plugin.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/Plugin.java new file mode 100644 index 0000000000..e7f9983fff --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/Plugin.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.plugins; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; + +public interface Plugin +{ + + /** + * Provide Configuration to this plugin + */ + public void configure(ConfigurationPlugin config); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/PluginFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/PluginFactory.java new file mode 100644 index 0000000000..bbf3e74a30 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/PluginFactory.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.plugins; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; + +public interface PluginFactory<P extends Plugin> +{ + public Class<P> getPluginClass(); + + public String getPluginName(); + + public P newInstance(ConfigurationPlugin config) throws ConfigurationException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/PluginManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/PluginManager.java new file mode 100644 index 0000000000..4e8d64a136 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/plugins/PluginManager.java @@ -0,0 +1,342 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.plugins; + +import static org.apache.felix.framework.util.FelixConstants.*; +import static org.apache.felix.main.AutoProcessor.*; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.felix.framework.Felix; +import org.apache.felix.framework.util.StringMap; +import org.apache.log4j.Logger; +import org.apache.qpid.common.Closeable; +import org.apache.qpid.server.configuration.TopicConfiguration; +import org.apache.qpid.server.configuration.plugins.SlowConsumerDetectionConfiguration.SlowConsumerDetectionConfigurationFactory; +import org.apache.qpid.server.configuration.plugins.SlowConsumerDetectionPolicyConfiguration.SlowConsumerDetectionPolicyConfigurationFactory; +import org.apache.qpid.server.configuration.plugins.SlowConsumerDetectionQueueConfiguration.SlowConsumerDetectionQueueConfigurationFactory; +import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory; +import org.apache.qpid.server.exchange.ExchangeType; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.security.SecurityPluginFactory; +import org.apache.qpid.server.security.access.plugins.AllowAll; +import org.apache.qpid.server.security.access.plugins.DenyAll; +import org.apache.qpid.server.security.access.plugins.LegacyAccess; +import org.apache.qpid.server.virtualhost.plugins.VirtualHostPluginFactory; +import org.apache.qpid.server.virtualhost.plugins.SlowConsumerDetection; +import org.apache.qpid.server.virtualhost.plugins.policies.TopicDeletePolicy; +import org.apache.qpid.slowconsumerdetection.policies.SlowConsumerPolicyPluginFactory; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleException; +import org.osgi.framework.launch.Framework; +import org.osgi.util.tracker.ServiceTracker; + +/** + * Provides access to pluggable elements, such as exchanges + */ +@SuppressWarnings("unchecked") +public class PluginManager implements Closeable +{ + private static final Logger _logger = Logger.getLogger(PluginManager.class); + + private static final int FELIX_STOP_TIMEOUT = 30000; + private static final String QPID_VER_SUFFIX = "version=0.11,"; + + private Framework _felix; + + private ServiceTracker _exchangeTracker = null; + private ServiceTracker _securityTracker = null; + private ServiceTracker _configTracker = null; + private ServiceTracker _virtualHostTracker = null; + private ServiceTracker _policyTracker = null; + + private Activator _activator; + + private Map<String, SecurityPluginFactory> _securityPlugins = new HashMap<String, SecurityPluginFactory>(); + private Map<List<String>, ConfigurationPluginFactory> _configPlugins = new IdentityHashMap<List<String>, ConfigurationPluginFactory>(); + private Map<String, VirtualHostPluginFactory> _vhostPlugins = new HashMap<String, VirtualHostPluginFactory>(); + private Map<String, SlowConsumerPolicyPluginFactory> _policyPlugins = new HashMap<String, SlowConsumerPolicyPluginFactory>(); + + public PluginManager(String pluginPath, String cachePath) throws Exception + { + // Store all non-OSGi plugins + // A little gross that we have to add them here, but not all the plugins are OSGIfied + for (SecurityPluginFactory<?> pluginFactory : Arrays.asList( + AllowAll.FACTORY, DenyAll.FACTORY, LegacyAccess.FACTORY)) + { + _securityPlugins.put(pluginFactory.getPluginName(), pluginFactory); + } + for (ConfigurationPluginFactory configFactory : Arrays.asList( + TopicConfiguration.FACTORY, + SecurityManager.SecurityConfiguration.FACTORY, + AllowAll.AllowAllConfiguration.FACTORY, + DenyAll.DenyAllConfiguration.FACTORY, + LegacyAccess.LegacyAccessConfiguration.FACTORY, + new SlowConsumerDetectionConfigurationFactory(), + new SlowConsumerDetectionPolicyConfigurationFactory(), + new SlowConsumerDetectionQueueConfigurationFactory())) + { + _configPlugins.put(configFactory.getParentPaths(), configFactory); + } + for (SlowConsumerPolicyPluginFactory pluginFactory : Arrays.asList( + new TopicDeletePolicy.TopicDeletePolicyFactory())) + { + _policyPlugins.put(pluginFactory.getPluginName(), pluginFactory); + } + for (VirtualHostPluginFactory pluginFactory : Arrays.asList( + new SlowConsumerDetection.SlowConsumerFactory())) + { + _vhostPlugins.put(pluginFactory.getClass().getName(), pluginFactory); + } + + // Check the plugin directory path is set and exist + if (pluginPath == null) + { + return; + } + File pluginDir = new File(pluginPath); + if (!pluginDir.exists()) + { + return; + } + + // Setup OSGi configuration propery map + StringMap configMap = new StringMap(false); + + // Add the bundle provided service interface package and the core OSGi + // packages to be exported from the class path via the system bundle. + configMap.put(FRAMEWORK_SYSTEMPACKAGES, + "org.osgi.framework; version=1.3.0," + + "org.osgi.service.packageadmin; version=1.2.0," + + "org.osgi.service.startlevel; version=1.0.0," + + "org.osgi.service.url; version=1.0.0," + + "org.osgi.util.tracker; version=1.0.0," + + "org.apache.qpid.junit.extensions.util; " + QPID_VER_SUFFIX + + "org.apache.qpid; " + QPID_VER_SUFFIX + + "org.apache.qpid.common; " + QPID_VER_SUFFIX + + "org.apache.qpid.exchange; " + QPID_VER_SUFFIX + + "org.apache.qpid.framing; " + QPID_VER_SUFFIX + + "org.apache.qpid.management.common.mbeans.annotations; " + QPID_VER_SUFFIX + + "org.apache.qpid.protocol; " + QPID_VER_SUFFIX + + "org.apache.qpid.server.binding; " + QPID_VER_SUFFIX + + "org.apache.qpid.server.configuration; " + QPID_VER_SUFFIX + + "org.apache.qpid.server.configuration.plugins; " + QPID_VER_SUFFIX + + "org.apache.qpid.server.configuration.management; " + QPID_VER_SUFFIX + + "org.apache.qpid.server.exchange; " + QPID_VER_SUFFIX + + "org.apache.qpid.server.logging; " + QPID_VER_SUFFIX + + "org.apache.qpid.server.logging.actors; " + QPID_VER_SUFFIX + + "org.apache.qpid.server.logging.subjects; " + QPID_VER_SUFFIX + + "org.apache.qpid.server.management; " + QPID_VER_SUFFIX + + "org.apache.qpid.server.persistent; " + QPID_VER_SUFFIX + + "org.apache.qpid.server.plugins; " + QPID_VER_SUFFIX + + "org.apache.qpid.server.protocol; " + QPID_VER_SUFFIX + + "org.apache.qpid.server.queue; " + QPID_VER_SUFFIX + + "org.apache.qpid.server.registry; " + QPID_VER_SUFFIX + + "org.apache.qpid.server.security; " + QPID_VER_SUFFIX + + "org.apache.qpid.server.security.access; " + QPID_VER_SUFFIX + + "org.apache.qpid.server.security.access.plugins; " + QPID_VER_SUFFIX + + "org.apache.qpid.server.virtualhost; " + QPID_VER_SUFFIX + + "org.apache.qpid.server.virtualhost.plugins; " + QPID_VER_SUFFIX + + "org.apache.qpid.util; " + QPID_VER_SUFFIX + + "org.apache.commons.configuration; version=1.0.0," + + "org.apache.commons.lang; version=1.0.0," + + "org.apache.commons.lang.builder; version=1.0.0," + + "org.apache.commons.logging; version=1.0.0," + + "org.apache.log4j; version=1.2.12," + + "javax.management.openmbean; version=1.0.0," + + "javax.management; version=1.0.0" + ); + + // No automatic shutdown hook + configMap.put("felix.shutdown.hook", "false"); + + // Add system activator + List<BundleActivator> activators = new ArrayList<BundleActivator>(); + _activator = new Activator(); + activators.add(_activator); + configMap.put(SYSTEMBUNDLE_ACTIVATORS_PROP, activators); + + if (cachePath != null) + { + File cacheDir = new File(cachePath); + if (!cacheDir.exists() && cacheDir.canWrite()) + { + _logger.info("Creating plugin cache directory: " + cachePath); + cacheDir.mkdir(); + } + + // Set plugin cache directory and empty it + _logger.info("Cache bundles in directory " + cachePath); + configMap.put(FRAMEWORK_STORAGE, cachePath); + } + configMap.put(FRAMEWORK_STORAGE_CLEAN, FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT); + + // Set directory with plugins to auto-deploy + _logger.info("Auto deploying bundles from directory " + pluginPath); + configMap.put(AUTO_DEPLOY_DIR_PROPERY, pluginPath); + configMap.put(AUTO_DEPLOY_ACTION_PROPERY, AUTO_DEPLOY_INSTALL_VALUE + "," + AUTO_DEPLOY_START_VALUE); + + // Start plugin manager and trackers + _felix = new Felix(configMap); + try + { + _logger.info("Starting plugin manager..."); + _felix.init(); + process(configMap, _felix.getBundleContext()); + _felix.start(); + _logger.info("Started plugin manager"); + } + catch (BundleException e) + { + throw new ConfigurationException("Could not start plugin manager: " + e.getMessage(), e); + } + + // TODO save trackers in a map, keyed by class name + + _exchangeTracker = new ServiceTracker(_activator.getContext(), ExchangeType.class.getName(), null); + _exchangeTracker.open(); + + _securityTracker = new ServiceTracker(_activator.getContext(), SecurityPluginFactory.class.getName(), null); + _securityTracker.open(); + + _configTracker = new ServiceTracker(_activator.getContext(), ConfigurationPluginFactory.class.getName(), null); + _configTracker.open(); + + _virtualHostTracker = new ServiceTracker(_activator.getContext(), VirtualHostPluginFactory.class.getName(), null); + _virtualHostTracker.open(); + + _policyTracker = new ServiceTracker(_activator.getContext(), SlowConsumerPolicyPluginFactory.class.getName(), null); + _policyTracker.open(); + + _logger.info("Opened service trackers"); + } + + private static <T> Map<String, T> getServices(ServiceTracker tracker) + { + Map<String, T> services = new HashMap<String, T>(); + + if ((tracker != null) && (tracker.getServices() != null)) + { + for (Object service : tracker.getServices()) + { + if (service instanceof PluginFactory<?>) + { + services.put(((PluginFactory<?>) service).getPluginName(), (T) service); + } + else + { + services.put(service.getClass().getName(), (T) service); + } + } + } + + return services; + } + + public static <T> Map<String, T> getServices(ServiceTracker tracker, Map<String, T> plugins) + { + Map<String, T> services = getServices(tracker); + services.putAll(plugins); + return services; + } + + public Map<List<String>, ConfigurationPluginFactory> getConfigurationPlugins() + { + Map<List<String>, ConfigurationPluginFactory> services = new IdentityHashMap<List<String>, ConfigurationPluginFactory>(); + + if (_configTracker != null && _configTracker.getServices() != null) + { + for (Object service : _configTracker.getServices()) + { + ConfigurationPluginFactory factory = (ConfigurationPluginFactory) service; + services.put(factory.getParentPaths(), factory); + } + } + + services.putAll(_configPlugins); + + return services; + } + + public Map<String, VirtualHostPluginFactory> getVirtualHostPlugins() + { + return getServices(_virtualHostTracker, _vhostPlugins); + } + + public Map<String, SlowConsumerPolicyPluginFactory> getSlowConsumerPlugins() + { + return getServices(_policyTracker, _policyPlugins); + } + + public Map<String, ExchangeType<?>> getExchanges() + { + return getServices(_exchangeTracker); + } + + public Map<String, SecurityPluginFactory> getSecurityPlugins() + { + return getServices(_securityTracker, _securityPlugins); + } + + public void close() + { + if (_felix != null) + { + try + { + // Close all bundle trackers + _exchangeTracker.close(); + _securityTracker.close(); + _configTracker.close(); + _virtualHostTracker.close(); + _policyTracker.close(); + } + finally + { + _logger.info("Stopping plugin manager"); + try + { + // FIXME should be stopAndWait() but hangs VM, need upgrade in felix + _felix.stop(); + } + catch (BundleException e) + { + // Ignore + } + + try + { + _felix.waitForStop(FELIX_STOP_TIMEOUT); + } + catch (InterruptedException e) + { + // Ignore + } + _logger.info("Stopped plugin manager"); + } + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQConnectionModel.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQConnectionModel.java new file mode 100644 index 0000000000..061ebf50cd --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQConnectionModel.java @@ -0,0 +1,72 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.protocol; + +import java.util.List; +import java.util.UUID; + +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.stats.StatisticsGatherer; + +public interface AMQConnectionModel extends StatisticsGatherer +{ + /** + * get a unique id for this connection. + * + * @return a {@link UUID} representing the connection + */ + public UUID getId(); + + /** + * Close the underlying Connection + * + * @param cause + * @param message + * @throws org.apache.qpid.AMQException + */ + public void close(AMQConstant cause, String message) throws AMQException; + + /** + * Close the given requested Session + * + * @param session + * @param cause + * @param message + * @throws org.apache.qpid.AMQException + */ + public void closeSession(AMQSessionModel session, AMQConstant cause, String message) throws AMQException; + + public long getConnectionId(); + + /** + * Get a list of all sessions using this connection. + * + * @return a list of {@link AMQSessionModel}s + */ + public List<AMQSessionModel> getSessionModels(); + + /** + * Return a {@link LogSubject} for the connection. + */ + public LogSubject getLogSubject(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQNoMethodHandlerException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQNoMethodHandlerException.java new file mode 100644 index 0000000000..a7599a3e0d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQNoMethodHandlerException.java @@ -0,0 +1,46 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.qpid.server.protocol;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQMethodBody;
+import org.apache.qpid.protocol.AMQMethodEvent;
+
+/**
+ * AMQNoMethodHandlerException represents the case where no method handler exists to handle an AQMP method.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Represents failure to handle an AMQP method.
+ * </table>
+ *
+ * @todo Not an AMQP exception as no status code.
+ *
+ * @todo Missing method handler. Unlikely to ever happen, and if it does its a coding error. Consider replacing with a
+ * Runtime.
+ */
+public class AMQNoMethodHandlerException extends AMQException
+{
+ public AMQNoMethodHandlerException(AMQMethodEvent<AMQMethodBody> evt)
+ {
+ super("AMQMethodEvent " + evt + " was not processed by any listener on Broker.");
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolEngine.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolEngine.java new file mode 100644 index 0000000000..449f698c48 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolEngine.java @@ -0,0 +1,1361 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.protocol; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.security.Principal; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import javax.management.JMException; +import javax.security.sasl.SaslServer; + +import org.apache.log4j.Logger; +import org.apache.mina.transport.vmpipe.VmPipeAddress; +import org.apache.qpid.AMQChannelException; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.codec.AMQCodecFactory; +import org.apache.qpid.codec.AMQDecoder; +import org.apache.qpid.common.ClientProperties; +import org.apache.qpid.framing.AMQBody; +import org.apache.qpid.framing.AMQDataBlock; +import org.apache.qpid.framing.AMQFrame; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.framing.AMQProtocolHeaderException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ChannelCloseBody; +import org.apache.qpid.framing.ChannelCloseOkBody; +import org.apache.qpid.framing.ConnectionCloseBody; +import org.apache.qpid.framing.ContentBody; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.HeartbeatBody; +import org.apache.qpid.framing.MethodDispatcher; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.framing.ProtocolInitiation; +import org.apache.qpid.framing.ProtocolVersion; +import org.apache.qpid.pool.Job; +import org.apache.qpid.pool.ReferenceCountingExecutorService; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.protocol.AMQMethodListener; +import org.apache.qpid.protocol.ProtocolEngine; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.configuration.ConfigStore; +import org.apache.qpid.server.configuration.ConfiguredObject; +import org.apache.qpid.server.configuration.ConnectionConfig; +import org.apache.qpid.server.configuration.ConnectionConfigType; +import org.apache.qpid.server.handler.ServerMethodDispatcherImpl; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.AMQPConnectionActor; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.ManagementActor; +import org.apache.qpid.server.logging.messages.ConnectionMessages; +import org.apache.qpid.server.logging.subjects.ConnectionLogSubject; +import org.apache.qpid.server.management.Managable; +import org.apache.qpid.server.management.ManagedObject; +import org.apache.qpid.server.output.ProtocolOutputConverter; +import org.apache.qpid.server.output.ProtocolOutputConverterRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.state.AMQState; +import org.apache.qpid.server.state.AMQStateManager; +import org.apache.qpid.server.stats.StatisticsCounter; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; +import org.apache.qpid.transport.NetworkDriver; +import org.apache.qpid.transport.Sender; + +public class AMQProtocolEngine implements ProtocolEngine, Managable, AMQProtocolSession, ConnectionConfig +{ + private static final Logger _logger = Logger.getLogger(AMQProtocolEngine.class); + + private static final String CLIENT_PROPERTIES_INSTANCE = ClientProperties.instance.toString(); + + private static final AtomicLong idGenerator = new AtomicLong(0); + + // to save boxing the channelId and looking up in a map... cache in an array the low numbered + // channels. This value must be of the form 2^x - 1. + private static final int CHANNEL_CACHE_SIZE = 0xff; + + private AMQShortString _contextKey; + + private AMQShortString _clientVersion = null; + + private VirtualHost _virtualHost; + + private final Map<Integer, AMQChannel> _channelMap = new HashMap<Integer, AMQChannel>(); + + private final AMQChannel[] _cachedChannels = new AMQChannel[CHANNEL_CACHE_SIZE + 1]; + + private final CopyOnWriteArraySet<AMQMethodListener> _frameListeners = new CopyOnWriteArraySet<AMQMethodListener>(); + + private final AMQStateManager _stateManager; + + private AMQCodecFactory _codecFactory; + + private AMQProtocolSessionMBean _managedObject; + + private SaslServer _saslServer; + + private Object _lastReceived; + + private Object _lastSent; + + protected volatile boolean _closed; + + // maximum number of channels this session should have + private long _maxNoOfChannels = ApplicationRegistry.getInstance().getConfiguration().getMaxChannelCount(); + + /* AMQP Version for this session */ + private ProtocolVersion _protocolVersion = ProtocolVersion.getLatestSupportedVersion(); + + private FieldTable _clientProperties; + private final List<Task> _taskList = new CopyOnWriteArrayList<Task>(); + + private Map<Integer, Long> _closingChannelsList = new ConcurrentHashMap<Integer, Long>(); + private ProtocolOutputConverter _protocolOutputConverter; + private Principal _authorizedID; + private MethodDispatcher _dispatcher; + private ProtocolSessionIdentifier _sessionIdentifier; + + // Create a simple ID that increments for ever new Session + private final long _sessionID = idGenerator.getAndIncrement(); + + private AMQPConnectionActor _actor; + private LogSubject _logSubject; + + private NetworkDriver _networkDriver; + + private long _lastIoTime; + + private long _writtenBytes; + private long _readBytes; + + private Job _readJob; + private Job _writeJob; + + private ReferenceCountingExecutorService _poolReference = ReferenceCountingExecutorService.getInstance(); + private long _maxFrameSize; + private final AtomicBoolean _closing = new AtomicBoolean(false); + private final UUID _id; + private final ConfigStore _configStore; + private long _createTime = System.currentTimeMillis(); + + private ApplicationRegistry _registry; + private boolean _statisticsEnabled = false; + private StatisticsCounter _messagesDelivered, _dataDelivered, _messagesReceived, _dataReceived; + + public ManagedObject getManagedObject() + { + return _managedObject; + } + + public AMQProtocolEngine(VirtualHostRegistry virtualHostRegistry, NetworkDriver driver) + { + _stateManager = new AMQStateManager(virtualHostRegistry, this); + _networkDriver = driver; + + _codecFactory = new AMQCodecFactory(true, this); + _poolReference.acquireExecutorService(); + _readJob = new Job(_poolReference, Job.MAX_JOB_EVENTS, true); + _writeJob = new Job(_poolReference, Job.MAX_JOB_EVENTS, false); + + _actor = new AMQPConnectionActor(this, virtualHostRegistry.getApplicationRegistry().getRootMessageLogger()); + + _logSubject = new ConnectionLogSubject(this); + + _configStore = virtualHostRegistry.getConfigStore(); + _id = _configStore.createId(); + + _actor.message(ConnectionMessages.OPEN(null, null, false, false)); + + _registry = virtualHostRegistry.getApplicationRegistry(); + initialiseStatistics(); + } + + private AMQProtocolSessionMBean createMBean() throws JMException + { + return new AMQProtocolSessionMBean(this); + } + + public long getSessionID() + { + return _sessionID; + } + + public LogActor getLogActor() + { + return _actor; + } + + public void setMaxFrameSize(long frameMax) + { + _maxFrameSize = frameMax; + } + + public long getMaxFrameSize() + { + return _maxFrameSize; + } + + public boolean isClosing() + { + return _closing.get(); + } + + public void received(final ByteBuffer msg) + { + _lastIoTime = System.currentTimeMillis(); + try + { + final ArrayList<AMQDataBlock> dataBlocks = _codecFactory.getDecoder().decodeBuffer(msg); + Job.fireAsynchEvent(_poolReference.getPool(), _readJob, new Runnable() + { + public void run() + { + // Decode buffer + + for (AMQDataBlock dataBlock : dataBlocks) + { + try + { + dataBlockReceived(dataBlock); + } + catch (Exception e) + { + _logger.error("Unexpected exception when processing datablock", e); + closeProtocolSession(); + } + } + } + }); + } + catch (Exception e) + { + _logger.error("Unexpected exception when processing datablock", e); + closeProtocolSession(); + } + } + + public void dataBlockReceived(AMQDataBlock message) throws Exception + { + _lastReceived = message; + if (message instanceof ProtocolInitiation) + { + protocolInitiationReceived((ProtocolInitiation) message); + + } + else if (message instanceof AMQFrame) + { + AMQFrame frame = (AMQFrame) message; + frameReceived(frame); + + } + else + { + throw new AMQException("Unknown message type: " + message.getClass().getName() + ": " + message); + } + } + + private void frameReceived(AMQFrame frame) throws AMQException + { + int channelId = frame.getChannel(); + AMQBody body = frame.getBodyFrame(); + + //Look up the Channel's Actor and set that as the current actor + // If that is not available then we can use the ConnectionActor + // that is associated with this AMQMPSession. + LogActor channelActor = null; + if (_channelMap.get(channelId) != null) + { + channelActor = _channelMap.get(channelId).getLogActor(); + } + CurrentActor.set(channelActor == null ? _actor : channelActor); + + try + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Frame Received: " + frame); + } + + // Check that this channel is not closing + if (channelAwaitingClosure(channelId)) + { + if ((frame.getBodyFrame() instanceof ChannelCloseOkBody)) + { + if (_logger.isInfoEnabled()) + { + _logger.info("Channel[" + channelId + "] awaiting closure - processing close-ok"); + } + } + else + { + // The channel has been told to close, we don't process any more frames until + // it's closed. + return; + } + } + + try + { + body.handle(channelId, this); + } + catch (AMQException e) + { + closeChannel(channelId); + throw e; + } + } + finally + { + CurrentActor.remove(); + } + } + + private void protocolInitiationReceived(ProtocolInitiation pi) + { + // this ensures the codec never checks for a PI message again + ((AMQDecoder) _codecFactory.getDecoder()).setExpectProtocolInitiation(false); + try + { + // Log incomming protocol negotiation request + _actor.message(ConnectionMessages.OPEN(null, pi._protocolMajor + "-" + pi._protocolMinor, false, true)); + + ProtocolVersion pv = pi.checkVersion(); // Fails if not correct + + // This sets the protocol version (and hence framing classes) for this session. + setProtocolVersion(pv); + + String mechanisms = ApplicationRegistry.getInstance().getAuthenticationManager().getMechanisms(); + + String locales = "en_US"; + + AMQMethodBody responseBody = getMethodRegistry().createConnectionStartBody((short) getProtocolMajorVersion(), + (short) pv.getActualMinorVersion(), + null, + mechanisms.getBytes(), + locales.getBytes()); + _networkDriver.send(responseBody.generateFrame(0).toNioByteBuffer()); + + } + catch (AMQException e) + { + _logger.info("Received unsupported protocol initiation for protocol version: " + getProtocolVersion()); + + _networkDriver.send(new ProtocolInitiation(ProtocolVersion.getLatestSupportedVersion()).toNioByteBuffer()); + } + } + + public void methodFrameReceived(int channelId, AMQMethodBody methodBody) + { + final AMQMethodEvent<AMQMethodBody> evt = new AMQMethodEvent<AMQMethodBody>(channelId, methodBody); + + try + { + try + { + boolean wasAnyoneInterested = _stateManager.methodReceived(evt); + + if (!_frameListeners.isEmpty()) + { + for (AMQMethodListener listener : _frameListeners) + { + wasAnyoneInterested = listener.methodReceived(evt) || wasAnyoneInterested; + } + } + + if (!wasAnyoneInterested) + { + throw new AMQNoMethodHandlerException(evt); + } + } + catch (AMQChannelException e) + { + if (getChannel(channelId) != null) + { + if (_logger.isInfoEnabled()) + { + _logger.info("Closing channel due to: " + e.getMessage()); + } + + writeFrame(e.getCloseFrame(channelId)); + closeChannel(channelId); + } + else + { + if (_logger.isDebugEnabled()) + { + _logger.debug("ChannelException occured on non-existent channel:" + e.getMessage()); + } + + if (_logger.isInfoEnabled()) + { + _logger.info("Closing connection due to: " + e.getMessage()); + } + + AMQConnectionException ce = + evt.getMethod().getConnectionException(AMQConstant.CHANNEL_ERROR, + AMQConstant.CHANNEL_ERROR.getName().toString()); + + _logger.info(e.getMessage() + " whilst processing:" + methodBody); + closeConnection(channelId, ce, false); + } + } + catch (AMQConnectionException e) + { + _logger.info(e.getMessage() + " whilst processing:" + methodBody); + closeConnection(channelId, e, false); + } + catch (AMQSecurityException e) + { + AMQConnectionException ce = evt.getMethod().getConnectionException(AMQConstant.ACCESS_REFUSED, e.getMessage()); + _logger.info(e.getMessage() + " whilst processing:" + methodBody); + closeConnection(channelId, ce, false); + } + } + catch (Exception e) + { + for (AMQMethodListener listener : _frameListeners) + { + listener.error(e); + } + + _logger.error("Unexpected exception while processing frame. Closing connection.", e); + + closeProtocolSession(); + } + } + + public void contentHeaderReceived(int channelId, ContentHeaderBody body) throws AMQException + { + + AMQChannel channel = getAndAssertChannel(channelId); + + channel.publishContentHeader(body); + + } + + public void contentBodyReceived(int channelId, ContentBody body) throws AMQException + { + AMQChannel channel = getAndAssertChannel(channelId); + + channel.publishContentBody(body); + } + + public void heartbeatBodyReceived(int channelId, HeartbeatBody body) + { + // NO - OP + } + + /** + * Convenience method that writes a frame to the protocol session. Equivalent to calling + * getProtocolSession().write(). + * + * @param frame the frame to write + */ + public void writeFrame(AMQDataBlock frame) + { + _lastSent = frame; + final ByteBuffer buf = frame.toNioByteBuffer(); + _lastIoTime = System.currentTimeMillis(); + _writtenBytes += buf.remaining(); + Job.fireAsynchEvent(_poolReference.getPool(), _writeJob, new Runnable() + { + public void run() + { + _networkDriver.send(buf); + } + }); + } + + public AMQShortString getContextKey() + { + return _contextKey; + } + + public void setContextKey(AMQShortString contextKey) + { + _contextKey = contextKey; + } + + public List<AMQChannel> getChannels() + { + return new ArrayList<AMQChannel>(_channelMap.values()); + } + + public AMQChannel getAndAssertChannel(int channelId) throws AMQException + { + AMQChannel channel = getChannel(channelId); + if (channel == null) + { + throw new AMQException(AMQConstant.NOT_FOUND, "Channel not found with id:" + channelId); + } + + return channel; + } + + public AMQChannel getChannel(int channelId) + { + final AMQChannel channel = + ((channelId & CHANNEL_CACHE_SIZE) == channelId) ? _cachedChannels[channelId] : _channelMap.get(channelId); + if ((channel == null) || channel.isClosing()) + { + return null; + } + else + { + return channel; + } + } + + public boolean channelAwaitingClosure(int channelId) + { + return !_closingChannelsList.isEmpty() && _closingChannelsList.containsKey(channelId); + } + + public void addChannel(AMQChannel channel) throws AMQException + { + if (_closed) + { + throw new AMQException("Session is closed"); + } + + final int channelId = channel.getChannelId(); + + if (_closingChannelsList.containsKey(channelId)) + { + throw new AMQException("Session is marked awaiting channel close"); + } + + if (_channelMap.size() == _maxNoOfChannels) + { + String errorMessage = + toString() + ": maximum number of channels has been reached (" + _maxNoOfChannels + + "); can't create channel"; + _logger.error(errorMessage); + throw new AMQException(AMQConstant.NOT_ALLOWED, errorMessage); + } + else + { + _channelMap.put(channel.getChannelId(), channel); + } + + if (((channelId & CHANNEL_CACHE_SIZE) == channelId)) + { + _cachedChannels[channelId] = channel; + } + + checkForNotification(); + } + + private void checkForNotification() + { + int channelsCount = _channelMap.size(); + if (_managedObject != null && channelsCount >= _maxNoOfChannels) + { + _managedObject.notifyClients("Channel count (" + channelsCount + ") has reached the threshold value"); + } + } + + public Long getMaximumNumberOfChannels() + { + return _maxNoOfChannels; + } + + public void setMaximumNumberOfChannels(Long value) + { + _maxNoOfChannels = value; + } + + public void commitTransactions(AMQChannel channel) throws AMQException + { + if ((channel != null) && channel.isTransactional()) + { + channel.commit(); + } + } + + public void rollbackTransactions(AMQChannel channel) throws AMQException + { + if ((channel != null) && channel.isTransactional()) + { + channel.rollback(); + } + } + + /** + * Close a specific channel. This will remove any resources used by the channel, including: <ul><li>any queue + * subscriptions (this may in turn remove queues if they are auto delete</li> </ul> + * + * @param channelId id of the channel to close + * + * @throws AMQException if an error occurs closing the channel + * @throws IllegalArgumentException if the channel id is not valid + */ + public void closeChannel(int channelId) throws AMQException + { + final AMQChannel channel = getChannel(channelId); + if (channel == null) + { + throw new IllegalArgumentException("Unknown channel id"); + } + else + { + try + { + channel.close(); + markChannelAwaitingCloseOk(channelId); + } + finally + { + removeChannel(channelId); + } + } + } + + public void closeChannelOk(int channelId) + { + // todo QPID-847 - This is called from two lcoations ChannelCloseHandler and ChannelCloseOkHandler. + // When it is the CC_OK_Handler then it makes sence to remove the channel else we will leak memory. + // We do it from the Close Handler as we are sending the OK back to the client. + // While this is AMQP spec compliant. The Java client in the event of an IllegalArgumentException + // will send a close-ok.. Where we should call removeChannel. + // However, due to the poor exception handling on the client. The client-user will be notified of the + // InvalidArgument and if they then decide to close the session/connection then the there will be time + // for that to occur i.e. a new close method be sent before the exeption handling can mark the session closed. + //removeChannel(channelId); + _closingChannelsList.remove(channelId); + } + + private void markChannelAwaitingCloseOk(int channelId) + { + _closingChannelsList.put(channelId, System.currentTimeMillis()); + } + + /** + * In our current implementation this is used by the clustering code. + * + * @param channelId The channel to remove + */ + public void removeChannel(int channelId) + { + _channelMap.remove(channelId); + if ((channelId & CHANNEL_CACHE_SIZE) == channelId) + { + _cachedChannels[channelId] = null; + } + } + + /** + * Initialise heartbeats on the session. + * + * @param delay delay in seconds (not ms) + */ + public void initHeartbeats(int delay) + { + if (delay > 0) + { + _networkDriver.setMaxWriteIdle(delay); + _networkDriver.setMaxReadIdle((int) (ApplicationRegistry.getInstance().getConfiguration().getHeartBeatTimeout() * delay)); + } + } + + /** + * Closes all channels that were opened by this protocol session. This frees up all resources used by the channel. + * + * @throws AMQException if an error occurs while closing any channel + */ + private void closeAllChannels() throws AMQException + { + for (AMQChannel channel : _channelMap.values()) + { + channel.close(); + } + + _channelMap.clear(); + for (int i = 0; i <= CHANNEL_CACHE_SIZE; i++) + { + _cachedChannels[i] = null; + } + } + + /** This must be called when the session is _closed in order to free up any resources managed by the session. */ + public void closeSession() throws AMQException + { + if(_closing.compareAndSet(false,true)) + { + // REMOVE THIS SHOULD NOT BE HERE. + if (CurrentActor.get() == null) + { + CurrentActor.set(_actor); + } + if (!_closed) + { + if (_virtualHost != null) + { + _virtualHost.getConnectionRegistry().deregisterConnection(this); + } + + closeAllChannels(); + + getConfigStore().removeConfiguredObject(this); + + if (_managedObject != null) + { + _managedObject.unregister(); + // Ensure we only do this once. + _managedObject = null; + } + + for (Task task : _taskList) + { + task.doTask(this); + } + + synchronized(this) + { + _closed = true; + notifyAll(); + } + _poolReference.releaseExecutorService(); + CurrentActor.get().message(_logSubject, ConnectionMessages.CLOSE()); + } + } + else + { + synchronized(this) + { + while(!_closed) + { + try + { + wait(1000); + } + catch (InterruptedException e) + { + + } + } + } + } + } + + public void closeConnection(int channelId, AMQConnectionException e, boolean closeProtocolSession) throws AMQException + { + if (_logger.isInfoEnabled()) + { + _logger.info("Closing connection due to: " + e); + } + + markChannelAwaitingCloseOk(channelId); + closeSession(); + _stateManager.changeState(AMQState.CONNECTION_CLOSING); + writeFrame(e.getCloseFrame(channelId)); + + if (closeProtocolSession) + { + closeProtocolSession(); + } + } + + public void closeProtocolSession() + { + _networkDriver.close(); + try + { + _stateManager.changeState(AMQState.CONNECTION_CLOSED); + } + catch (AMQException e) + { + _logger.info(e.getMessage()); + } + } + + public String toString() + { + return getRemoteAddress() + "(" + (getAuthorizedID() == null ? "?" : getAuthorizedID().getName() + ")"); + } + + public String dump() + { + return this + " last_sent=" + _lastSent + " last_received=" + _lastReceived; + } + + /** @return an object that can be used to identity */ + public Object getKey() + { + return getRemoteAddress(); + } + + /** + * Get the fully qualified domain name of the local address to which this session is bound. Since some servers may + * be bound to multiple addresses this could vary depending on the acceptor this session was created from. + * + * @return a String FQDN + */ + public String getLocalFQDN() + { + SocketAddress address = _networkDriver.getLocalAddress(); + // we use the vmpipe address in some tests hence the need for this rather ugly test. The host + // information is used by SASL primary. + if (address instanceof InetSocketAddress) + { + return ((InetSocketAddress) address).getHostName(); + } + else if (address instanceof VmPipeAddress) + { + return "vmpipe:" + ((VmPipeAddress) address).getPort(); + } + else + { + throw new IllegalArgumentException("Unsupported socket address class: " + address); + } + } + + public SaslServer getSaslServer() + { + return _saslServer; + } + + public void setSaslServer(SaslServer saslServer) + { + _saslServer = saslServer; + } + + public FieldTable getClientProperties() + { + return _clientProperties; + } + + public void setClientProperties(FieldTable clientProperties) + { + _clientProperties = clientProperties; + if (_clientProperties != null) + { + if (_clientProperties.getString(CLIENT_PROPERTIES_INSTANCE) != null) + { + String clientID = _clientProperties.getString(CLIENT_PROPERTIES_INSTANCE); + setContextKey(new AMQShortString(clientID)); + + // Log the Opening of the connection for this client + _actor.message(ConnectionMessages.OPEN(clientID, _protocolVersion.toString(), true, true)); + } + + if (_clientProperties.getString(ClientProperties.version.toString()) != null) + { + _clientVersion = new AMQShortString(_clientProperties.getString(ClientProperties.version.toString())); + } + } + _sessionIdentifier = new ProtocolSessionIdentifier(this); + } + + private void setProtocolVersion(ProtocolVersion pv) + { + _protocolVersion = pv; + + _protocolOutputConverter = ProtocolOutputConverterRegistry.getConverter(this); + _dispatcher = ServerMethodDispatcherImpl.createMethodDispatcher(_stateManager, _protocolVersion); + } + + public byte getProtocolMajorVersion() + { + return _protocolVersion.getMajorVersion(); + } + + public ProtocolVersion getProtocolVersion() + { + return _protocolVersion; + } + + public byte getProtocolMinorVersion() + { + return _protocolVersion.getMinorVersion(); + } + + public boolean isProtocolVersion(byte major, byte minor) + { + return (getProtocolMajorVersion() == major) && (getProtocolMinorVersion() == minor); + } + + public MethodRegistry getRegistry() + { + return getMethodRegistry(); + } + + public Object getClientIdentifier() + { + return (_networkDriver != null) ? _networkDriver.getRemoteAddress() : null; + } + + public VirtualHost getVirtualHost() + { + return _virtualHost; + } + + public void setVirtualHost(VirtualHost virtualHost) throws AMQException + { + _virtualHost = virtualHost; + + _virtualHost.getConnectionRegistry().registerConnection(this); + + _configStore.addConfiguredObject(this); + + try + { + _managedObject = createMBean(); + _managedObject.register(); + } + catch (JMException e) + { + _logger.error(e); + } + } + + public void addSessionCloseTask(Task task) + { + _taskList.add(task); + } + + public void removeSessionCloseTask(Task task) + { + _taskList.remove(task); + } + + public ProtocolOutputConverter getProtocolOutputConverter() + { + return _protocolOutputConverter; + } + + public void setAuthorizedID(Principal authorizedID) + { + _authorizedID = authorizedID; + } + + public Principal getAuthorizedID() + { + return _authorizedID; + } + + public Principal getPrincipal() + { + return _authorizedID; + } + + public SocketAddress getRemoteAddress() + { + return _networkDriver.getRemoteAddress(); + } + + public SocketAddress getLocalAddress() + { + return _networkDriver.getLocalAddress(); + } + + public MethodRegistry getMethodRegistry() + { + return MethodRegistry.getMethodRegistry(getProtocolVersion()); + } + + public MethodDispatcher getMethodDispatcher() + { + return _dispatcher; + } + + public void closed() + { + try + { + closeSession(); + } + catch (AMQException e) + { + _logger.error("Could not close protocol engine", e); + } + } + + public void readerIdle() + { + // Nothing + } + + public void setNetworkDriver(NetworkDriver driver) + { + _networkDriver = driver; + } + + public void writerIdle() + { + _networkDriver.send(HeartbeatBody.FRAME.toNioByteBuffer()); + } + + public void exception(Throwable throwable) + { + if (throwable instanceof AMQProtocolHeaderException) + { + writeFrame(new ProtocolInitiation(ProtocolVersion.getLatestSupportedVersion())); + _networkDriver.close(); + + _logger.error("Error in protocol initiation " + this + ":" + getRemoteAddress() + " :" + throwable.getMessage(), throwable); + } + else if (throwable instanceof IOException) + { + _logger.error("IOException caught in" + this + ", session closed implictly: " + throwable); + } + else + { + _logger.error("Exception caught in" + this + ", closing session explictly: " + throwable, throwable); + + + MethodRegistry methodRegistry = MethodRegistry.getMethodRegistry(getProtocolVersion()); + ConnectionCloseBody closeBody = methodRegistry.createConnectionCloseBody(200,new AMQShortString(throwable.getMessage()),0,0); + + writeFrame(closeBody.generateFrame(0)); + + _networkDriver.close(); + } + } + + public void init() + { + // Do nothing + } + + public void setSender(Sender<ByteBuffer> sender) + { + // Do nothing + } + + public long getReadBytes() + { + return _readBytes; + } + + public long getWrittenBytes() + { + return _writtenBytes; + } + + public long getLastIoTime() + { + return _lastIoTime; + } + + public ProtocolSessionIdentifier getSessionIdentifier() + { + return _sessionIdentifier; + } + + public String getClientVersion() + { + return (_clientVersion == null) ? null : _clientVersion.toString(); + } + + public Boolean isIncoming() + { + return true; + } + + public Boolean isSystemConnection() + { + return false; + } + + public Boolean isFederationLink() + { + return false; + } + + public String getAuthId() + { + return getAuthorizedID().getName(); + } + + public Integer getRemotePID() + { + return null; + } + + public String getRemoteProcessName() + { + return null; + } + + public Integer getRemoteParentPID() + { + return null; + } + + public ConfigStore getConfigStore() + { + return _configStore; + } + + public ConnectionConfigType getConfigType() + { + return ConnectionConfigType.getInstance(); + } + + public ConfiguredObject getParent() + { + return getVirtualHost(); + } + + public boolean isDurable() + { + return false; + } + + public UUID getId() + { + return _id; + } + + public long getConnectionId() + { + return getSessionID(); + } + + public String getAddress() + { + return String.valueOf(getRemoteAddress()); + } + + public long getCreateTime() + { + return _createTime; + } + + public Boolean isShadow() + { + return false; + } + + public void mgmtClose() + { + MethodRegistry methodRegistry = getMethodRegistry(); + ConnectionCloseBody responseBody = + methodRegistry.createConnectionCloseBody( + AMQConstant.REPLY_SUCCESS.getCode(), + new AMQShortString("The connection was closed using the broker's management interface."), + 0,0); + + // This seems ugly but because we use closeConnection in both normal + // broker operation and as part of the management interface it cannot + // be avoided. The Current Actor will be null when this method is + // called via the QMF management interface. As such we need to set one. + boolean removeActor = false; + if (CurrentActor.get() == null) + { + removeActor = true; + CurrentActor.set(new ManagementActor(_actor.getRootMessageLogger())); + } + + try + { + writeFrame(responseBody.generateFrame(0)); + + try + { + + closeSession(); + } + catch (AMQException ex) + { + throw new RuntimeException(ex); + } + } + finally + { + if (removeActor) + { + CurrentActor.remove(); + } + } + } + + public void mgmtCloseChannel(int channelId) + { + MethodRegistry methodRegistry = getMethodRegistry(); + ChannelCloseBody responseBody = + methodRegistry.createChannelCloseBody( + AMQConstant.REPLY_SUCCESS.getCode(), + new AMQShortString("The channel was closed using the broker's management interface."), + 0,0); + + // This seems ugly but because we use AMQChannel.close() in both normal + // broker operation and as part of the management interface it cannot + // be avoided. The Current Actor will be null when this method is + // called via the QMF management interface. As such we need to set one. + boolean removeActor = false; + if (CurrentActor.get() == null) + { + removeActor = true; + CurrentActor.set(new ManagementActor(_actor.getRootMessageLogger())); + } + + try + { + writeFrame(responseBody.generateFrame(channelId)); + + try + { + closeChannel(channelId); + } + catch (AMQException ex) + { + throw new RuntimeException(ex); + } + } + finally + { + if (removeActor) + { + CurrentActor.remove(); + } + } + } + + public String getClientID() + { + return getContextKey().toString(); + } + + public void closeSession(AMQSessionModel session, AMQConstant cause, String message) throws AMQException + { + closeChannel((Integer)session.getID()); + + MethodRegistry methodRegistry = getMethodRegistry(); + ChannelCloseBody responseBody = + methodRegistry.createChannelCloseBody( + cause.getCode(), + new AMQShortString(message), + 0,0); + + writeFrame(responseBody.generateFrame((Integer)session.getID())); + } + + public void close(AMQConstant cause, String message) throws AMQException + { + closeConnection(0, new AMQConnectionException(cause, message, 0, 0, + getProtocolOutputConverter().getProtocolMajorVersion(), + getProtocolOutputConverter().getProtocolMinorVersion(), + (Throwable) null), true); + } + + public List<AMQSessionModel> getSessionModels() + { + List<AMQSessionModel> sessions = new ArrayList<AMQSessionModel>(); + for (AMQChannel channel : getChannels()) + { + sessions.add((AMQSessionModel) channel); + } + return sessions; + } + + public LogSubject getLogSubject() + { + return _logSubject; + } + + public void registerMessageDelivered(long messageSize) + { + if (isStatisticsEnabled()) + { + _messagesDelivered.registerEvent(1L); + _dataDelivered.registerEvent(messageSize); + } + _virtualHost.registerMessageDelivered(messageSize); + } + + public void registerMessageReceived(long messageSize, long timestamp) + { + if (isStatisticsEnabled()) + { + _messagesReceived.registerEvent(1L, timestamp); + _dataReceived.registerEvent(messageSize, timestamp); + } + _virtualHost.registerMessageReceived(messageSize, timestamp); + } + + public StatisticsCounter getMessageReceiptStatistics() + { + return _messagesReceived; + } + + public StatisticsCounter getDataReceiptStatistics() + { + return _dataReceived; + } + + public StatisticsCounter getMessageDeliveryStatistics() + { + return _messagesDelivered; + } + + public StatisticsCounter getDataDeliveryStatistics() + { + return _dataDelivered; + } + + public void resetStatistics() + { + _messagesDelivered.reset(); + _dataDelivered.reset(); + _messagesReceived.reset(); + _dataReceived.reset(); + } + + public void initialiseStatistics() + { + setStatisticsEnabled(!StatisticsCounter.DISABLE_STATISTICS && + _registry.getConfiguration().isStatisticsGenerationConnectionsEnabled()); + + _messagesDelivered = new StatisticsCounter("messages-delivered-" + getSessionID()); + _dataDelivered = new StatisticsCounter("data-delivered-" + getSessionID()); + _messagesReceived = new StatisticsCounter("messages-received-" + getSessionID()); + _dataReceived = new StatisticsCounter("data-received-" + getSessionID()); + } + + public boolean isStatisticsEnabled() + { + return _statisticsEnabled; + } + + public void setStatisticsEnabled(boolean enabled) + { + _statisticsEnabled = enabled; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolEngineFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolEngineFactory.java new file mode 100644 index 0000000000..0e4444725e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolEngineFactory.java @@ -0,0 +1,50 @@ +package org.apache.qpid.server.protocol; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ + + +import org.apache.qpid.protocol.ProtocolEngine; +import org.apache.qpid.protocol.ProtocolEngineFactory; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; +import org.apache.qpid.transport.NetworkDriver; + +public class AMQProtocolEngineFactory implements ProtocolEngineFactory +{ + private VirtualHostRegistry _vhosts; + + public AMQProtocolEngineFactory() + { + this(1); + } + + public AMQProtocolEngineFactory(Integer port) + { + _vhosts = ApplicationRegistry.getInstance(port).getVirtualHostRegistry(); + } + + + public ProtocolEngine newProtocolEngine(NetworkDriver networkDriver) + { + return new AMQProtocolEngine(_vhosts, networkDriver); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSession.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSession.java new file mode 100644 index 0000000000..c64ed4ad5a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSession.java @@ -0,0 +1,235 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.protocol; + +import javax.security.sasl.SaslServer; + +import org.apache.qpid.AMQException; +import org.apache.qpid.common.ClientProperties; +import org.apache.qpid.framing.*; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.protocol.AMQVersionAwareProtocolSession; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.security.PrincipalHolder; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.output.ProtocolOutputConverter; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.security.Principal; +import java.util.List; + + +public interface AMQProtocolSession extends AMQVersionAwareProtocolSession, PrincipalHolder, AMQConnectionModel +{ + long getSessionID(); + + LogActor getLogActor(); + + void setMaxFrameSize(long frameMax); + + long getMaxFrameSize(); + + boolean isClosing(); + + public static final class ProtocolSessionIdentifier + { + private final Object _sessionIdentifier; + private final Object _sessionInstance; + + ProtocolSessionIdentifier(AMQProtocolSession session) + { + _sessionIdentifier = session.getClientIdentifier(); + _sessionInstance = session.getClientProperties() == null ? null : session.getClientProperties().getObject(ClientProperties.instance.toAMQShortString()); + } + + public Object getSessionIdentifier() + { + return _sessionIdentifier; + } + + public Object getSessionInstance() + { + return _sessionInstance; + } + } + + public static interface Task + { + public void doTask(AMQProtocolSession session) throws AMQException; + } + + /** + * Called when a protocol data block is received + * + * @param message the data block that has been received + * + * @throws Exception if processing the datablock fails + */ + void dataBlockReceived(AMQDataBlock message) throws Exception; + + /** + * Get the context key associated with this session. Context key is described in the AMQ protocol specification (RFC + * 6). + * + * @return the context key + */ + AMQShortString getContextKey(); + + /** + * Set the context key associated with this session. Context key is described in the AMQ protocol specification (RFC + * 6). + * + * @param contextKey the context key + */ + void setContextKey(AMQShortString contextKey); + + /** + * Get the channel for this session associated with the specified id. A channel id is unique per connection (i.e. + * per session). + * + * @param channelId the channel id which must be valid + * + * @return null if no channel exists, the channel otherwise + */ + AMQChannel getChannel(int channelId); + + /** + * Associate a channel with this session. + * + * @param channel the channel to associate with this session. It is an error to associate the same channel with more + * than one session but this is not validated. + */ + void addChannel(AMQChannel channel) throws AMQException; + + /** + * Close a specific channel. This will remove any resources used by the channel, including: <ul><li>any queue + * subscriptions (this may in turn remove queues if they are auto delete</li> </ul> + * + * @param channelId id of the channel to close + * + * @throws org.apache.qpid.AMQException if an error occurs closing the channel + * @throws IllegalArgumentException if the channel id is not valid + */ + void closeChannel(int channelId) throws AMQException; + + /** + * Markes the specific channel as closed. This will release the lock for that channel id so a new channel can be + * created on that id. + * + * @param channelId id of the channel to close + */ + void closeChannelOk(int channelId); + + /** + * Check to see if this chanel is closing + * + * @param channelId id to check + * @return boolean with state of channel awaiting closure + */ + boolean channelAwaitingClosure(int channelId); + + /** + * Remove a channel from the session but do not close it. + * + * @param channelId + */ + void removeChannel(int channelId); + + /** + * Initialise heartbeats on the session. + * + * @param delay delay in seconds (not ms) + */ + void initHeartbeats(int delay); + + /** This must be called when the session is _closed in order to free up any resources managed by the session. */ + void closeSession() throws AMQException; + + /** This must be called to close the session in order to free up any resources managed by the session. */ + void closeConnection(int channelId, AMQConnectionException e, boolean closeProtocolSession) throws AMQException; + + + /** @return a key that uniquely identifies this session */ + Object getKey(); + + /** + * Get the fully qualified domain name of the local address to which this session is bound. Since some servers may + * be bound to multiple addresses this could vary depending on the acceptor this session was created from. + * + * @return a String FQDN + */ + String getLocalFQDN(); + + /** @return the sasl server that can perform authentication for this session. */ + SaslServer getSaslServer(); + + /** + * Set the sasl server that is to perform authentication for this session. + * + * @param saslServer + */ + void setSaslServer(SaslServer saslServer); + + + FieldTable getClientProperties(); + + void setClientProperties(FieldTable clientProperties); + + Object getClientIdentifier(); + + VirtualHost getVirtualHost(); + + void setVirtualHost(VirtualHost virtualHost) throws AMQException; + + void addSessionCloseTask(Task task); + + void removeSessionCloseTask(Task task); + + public ProtocolOutputConverter getProtocolOutputConverter(); + + void setAuthorizedID(Principal authorizedID); + + public java.net.SocketAddress getRemoteAddress(); + + public MethodRegistry getMethodRegistry(); + + public MethodDispatcher getMethodDispatcher(); + + public ProtocolSessionIdentifier getSessionIdentifier(); + + String getClientVersion(); + + long getLastIoTime(); + + long getWrittenBytes(); + + Long getMaximumNumberOfChannels(); + + void setMaximumNumberOfChannels(Long value); + + void commitTransactions(AMQChannel channel) throws AMQException; + + void rollbackTransactions(AMQChannel channel) throws AMQException; + + List<AMQChannel> getChannels(); + + void mgmtCloseChannel(int channelId); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBean.java new file mode 100644 index 0000000000..fcac78fafa --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQProtocolSessionMBean.java @@ -0,0 +1,417 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ +/* + * + * Copyright (c) 2006 The Apache Software Foundation + * + * 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.apache.qpid.server.protocol; + +import java.util.Date; +import java.util.List; + +import javax.management.JMException; +import javax.management.MBeanException; +import javax.management.MBeanNotificationInfo; +import javax.management.NotCompliantMBeanException; +import javax.management.Notification; +import javax.management.ObjectName; +import javax.management.monitor.MonitorNotification; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.ConnectionCloseBody; +import org.apache.qpid.framing.MethodRegistry; +import org.apache.qpid.management.common.mbeans.ManagedConnection; +import org.apache.qpid.management.common.mbeans.annotations.MBeanConstructor; +import org.apache.qpid.management.common.mbeans.annotations.MBeanDescription; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.ManagementActor; +import org.apache.qpid.server.management.AMQManagedObject; +import org.apache.qpid.server.management.ManagedObject; + +/** + * This MBean class implements the management interface. In order to make more attributes, operations and notifications + * available over JMX simply augment the ManagedConnection interface and add the appropriate implementation here. + */ +@MBeanDescription("Management Bean for an AMQ Broker Connection") +public class AMQProtocolSessionMBean extends AMQManagedObject implements ManagedConnection +{ + private AMQProtocolSession _protocolSession = null; + private String _name = null; + + // openmbean data types for representing the channel attributes + + private static final OpenType[] _channelAttributeTypes = + { SimpleType.INTEGER, SimpleType.BOOLEAN, SimpleType.STRING, SimpleType.INTEGER, SimpleType.BOOLEAN }; + private static CompositeType _channelType = null; // represents the data type for channel data + private static TabularType _channelsType = null; // Data type for list of channels type + private static final AMQShortString BROKER_MANAGEMENT_CONSOLE_HAS_CLOSED_THE_CONNECTION = + new AMQShortString("Broker Management Console has closed the connection."); + + @MBeanConstructor("Creates an MBean exposing an AMQ Broker Connection") + public AMQProtocolSessionMBean(AMQProtocolSession amqProtocolSession) throws NotCompliantMBeanException, OpenDataException + { + super(ManagedConnection.class, ManagedConnection.TYPE); + _protocolSession = amqProtocolSession; + String remote = getRemoteAddress(); + _name = "anonymous".equals(remote) ? (remote + hashCode()) : remote; + init(); + } + + static + { + try + { + init(); + } + catch (JMException ex) + { + // This is not expected to ever occur. + throw new RuntimeException("Got JMException in static initializer.", ex); + } + } + + /** + * initialises the openmbean data types + */ + private static void init() throws OpenDataException + { + _channelType = + new CompositeType("Channel", "Channel Details", COMPOSITE_ITEM_NAMES_DESC.toArray(new String[COMPOSITE_ITEM_NAMES_DESC.size()]), + COMPOSITE_ITEM_NAMES_DESC.toArray(new String[COMPOSITE_ITEM_NAMES_DESC.size()]), _channelAttributeTypes); + _channelsType = new TabularType("Channels", "Channels", _channelType, TABULAR_UNIQUE_INDEX.toArray(new String[TABULAR_UNIQUE_INDEX.size()])); + } + + public String getClientId() + { + return String.valueOf(_protocolSession.getContextKey()); + } + + public String getAuthorizedId() + { + return (_protocolSession.getPrincipal() != null ) ? _protocolSession.getPrincipal().getName() : null; + } + + public String getVersion() + { + return (_protocolSession.getClientVersion() == null) ? null : _protocolSession.getClientVersion().toString(); + } + + public Date getLastIoTime() + { + return new Date(_protocolSession.getLastIoTime()); + } + + public String getRemoteAddress() + { + return _protocolSession.getRemoteAddress().toString(); + } + + public ManagedObject getParentObject() + { + return _protocolSession.getVirtualHost().getManagedObject(); + } + + public Long getWrittenBytes() + { + return _protocolSession.getWrittenBytes(); + } + + public Long getReadBytes() + { + return _protocolSession.getWrittenBytes(); + } + + public Long getMaximumNumberOfChannels() + { + return _protocolSession.getMaximumNumberOfChannels(); + } + + public void setMaximumNumberOfChannels(Long value) + { + _protocolSession.setMaximumNumberOfChannels(value); + } + + public String getObjectInstanceName() + { + return ObjectName.quote(_name); + } + + /** + * commits transactions for a transactional channel + * + * @param channelId + * @throws JMException if channel with given id doesn't exist or if commit fails + */ + public void commitTransactions(int channelId) throws JMException + { + CurrentActor.set(new ManagementActor(_logActor.getRootMessageLogger())); + try + { + AMQChannel channel = _protocolSession.getChannel(channelId); + if (channel == null) + { + throw new JMException("The channel (channel Id = " + channelId + ") does not exist"); + } + + _protocolSession.commitTransactions(channel); + } + catch (AMQException ex) + { + throw new MBeanException(ex, ex.toString()); + } + finally + { + CurrentActor.remove(); + } + } + + /** + * rollsback the transactions for a transactional channel + * + * @param channelId + * @throws JMException if channel with given id doesn't exist or if rollback fails + */ + public void rollbackTransactions(int channelId) throws JMException + { + CurrentActor.set(new ManagementActor(_logActor.getRootMessageLogger())); + try + { + AMQChannel channel = _protocolSession.getChannel(channelId); + if (channel == null) + { + throw new JMException("The channel (channel Id = " + channelId + ") does not exist"); + } + + _protocolSession.rollbackTransactions(channel); + } + catch (AMQException ex) + { + throw new MBeanException(ex, ex.toString()); + } + finally + { + CurrentActor.remove(); + } + } + + /** + * Creates the list of channels in tabular form from the _channelMap. + * + * @return list of channels in tabular form. + * @throws OpenDataException + */ + public TabularData channels() throws OpenDataException + { + TabularDataSupport channelsList = new TabularDataSupport(_channelsType); + List<AMQChannel> list = _protocolSession.getChannels(); + + for (AMQChannel channel : list) + { + Object[] itemValues = + { + channel.getChannelId(), channel.isTransactional(), + (channel.getDefaultQueue() != null) ? channel.getDefaultQueue().getNameShortString().asString() : null, + channel.getUnacknowledgedMessageMap().size(), channel.getBlocking() + }; + + CompositeData channelData = new CompositeDataSupport(_channelType, + COMPOSITE_ITEM_NAMES_DESC.toArray(new String[COMPOSITE_ITEM_NAMES_DESC.size()]), itemValues); + channelsList.put(channelData); + } + + return channelsList; + } + + /** + * closes the connection. The administrator can use this management operation to close connection to free up + * resources. + * @throws JMException + */ + public void closeConnection() throws JMException + { + + MethodRegistry methodRegistry = _protocolSession.getMethodRegistry(); + ConnectionCloseBody responseBody = + methodRegistry.createConnectionCloseBody(AMQConstant.REPLY_SUCCESS.getCode(), + // replyCode + BROKER_MANAGEMENT_CONSOLE_HAS_CLOSED_THE_CONNECTION, + // replyText, + 0, + 0); + + // This seems ugly but because we use closeConnection in both normal + // broker operation and as part of the management interface it cannot + // be avoided. The Current Actor will be null when this method is + // called via the Management interface. This is because we allow the + // Local API connection with JConsole. If we did not allow that option + // then the CurrentActor could be set in our JMX Proxy object. + // As it is we need to set the CurrentActor on all MBean methods + // Ideally we would not have a single method that can be called from + // two contexts. + boolean removeActor = false; + if (CurrentActor.get() == null) + { + removeActor = true; + CurrentActor.set(new ManagementActor(_logActor.getRootMessageLogger())); + } + + try + { + _protocolSession.writeFrame(responseBody.generateFrame(0)); + + try + { + + _protocolSession.closeSession(); + } + catch (AMQException ex) + { + throw new MBeanException(ex, ex.toString()); + } + } + finally + { + if (removeActor) + { + CurrentActor.remove(); + } + } + } + + @Override + public MBeanNotificationInfo[] getNotificationInfo() + { + String[] notificationTypes = new String[] { MonitorNotification.THRESHOLD_VALUE_EXCEEDED }; + String name = MonitorNotification.class.getName(); + String description = "Channel count has reached threshold value"; + MBeanNotificationInfo info1 = new MBeanNotificationInfo(notificationTypes, name, description); + + return new MBeanNotificationInfo[] { info1 }; + } + + public void notifyClients(String notificationMsg) + { + Notification n = + new Notification(MonitorNotification.THRESHOLD_VALUE_EXCEEDED, this, ++_notificationSequenceNumber, + System.currentTimeMillis(), notificationMsg); + _broadcaster.sendNotification(n); + } + + public void resetStatistics() throws Exception + { + _protocolSession.resetStatistics(); + } + + public double getPeakMessageDeliveryRate() + { + return _protocolSession.getMessageDeliveryStatistics().getPeak(); + } + + public double getPeakDataDeliveryRate() + { + return _protocolSession.getDataDeliveryStatistics().getPeak(); + } + + public double getMessageDeliveryRate() + { + return _protocolSession.getMessageDeliveryStatistics().getRate(); + } + + public double getDataDeliveryRate() + { + return _protocolSession.getDataDeliveryStatistics().getRate(); + } + + public long getTotalMessagesDelivered() + { + return _protocolSession.getMessageDeliveryStatistics().getTotal(); + } + + public long getTotalDataDelivered() + { + return _protocolSession.getDataDeliveryStatistics().getTotal(); + } + + public double getPeakMessageReceiptRate() + { + return _protocolSession.getMessageReceiptStatistics().getPeak(); + } + + public double getPeakDataReceiptRate() + { + return _protocolSession.getDataReceiptStatistics().getPeak(); + } + + public double getMessageReceiptRate() + { + return _protocolSession.getMessageReceiptStatistics().getRate(); + } + + public double getDataReceiptRate() + { + return _protocolSession.getDataReceiptStatistics().getRate(); + } + + public long getTotalMessagesReceived() + { + return _protocolSession.getMessageReceiptStatistics().getTotal(); + } + + public long getTotalDataReceived() + { + return _protocolSession.getDataReceiptStatistics().getTotal(); + } + + public boolean isStatisticsEnabled() + { + return _protocolSession.isStatisticsEnabled(); + } + + public void setStatisticsEnabled(boolean enabled) + { + _protocolSession.setStatisticsEnabled(enabled); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQSessionModel.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQSessionModel.java new file mode 100644 index 0000000000..bc63403a86 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/AMQSessionModel.java @@ -0,0 +1,54 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.protocol; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.logging.LogSubject; + +public interface AMQSessionModel +{ + public Object getID(); + + public AMQConnectionModel getConnectionModel(); + + public String getClientID(); + + public void close() throws AMQException; + + public LogSubject getLogSubject(); + + /** + * This method is called from the housekeeping thread to check the status of + * transactions on this session and react appropriately. + * + * If a transaction is open for too long or idle for too long then a warning + * is logged or the connection is closed, depending on the configuration. An open + * transaction is one that has recent activity. The transaction age is counted + * from the time the transaction was started. An idle transaction is one that + * has had no activity, such as publishing or acknowledgeing messages. + * + * @param openWarn time in milliseconds before alerting on open transaction + * @param openClose time in milliseconds before closing connection with open transaction + * @param idleWarn time in milliseconds before alerting on idle transaction + * @param idleClose time in milliseconds before closing connection with idle transaction + */ + public void checkTransactionStatus(long openWarn, long openClose, long idleWarn, long idleClose) throws AMQException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/MultiVersionProtocolEngine.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/MultiVersionProtocolEngine.java new file mode 100755 index 0000000000..eb957ee33c --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/MultiVersionProtocolEngine.java @@ -0,0 +1,425 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.protocol; + + +import org.apache.log4j.Logger; +import org.apache.qpid.protocol.ProtocolEngine; +import org.apache.qpid.server.protocol.MultiVersionProtocolEngineFactory.VERSION; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.transport.ServerConnection; +import org.apache.qpid.transport.ConnectionDelegate; +import org.apache.qpid.transport.NetworkDriver; + +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.util.Set; + +public class MultiVersionProtocolEngine implements ProtocolEngine +{ + private static final Logger _logger = Logger.getLogger(MultiVersionProtocolEngine.class); + + + + private NetworkDriver _networkDriver; + private Set<VERSION> _supported; + private String _fqdn; + private IApplicationRegistry _appRegistry; + + private volatile ProtocolEngine _delegate = new SelfDelegateProtocolEngine(); + + public MultiVersionProtocolEngine(IApplicationRegistry appRegistry, + String fqdn, + Set<VERSION> supported, NetworkDriver networkDriver) + { + _appRegistry = appRegistry; + _fqdn = fqdn; + _supported = supported; + _networkDriver = networkDriver; + } + + public void setNetworkDriver(NetworkDriver driver) + { + _delegate.setNetworkDriver(driver); + } + + public SocketAddress getRemoteAddress() + { + return _delegate.getRemoteAddress(); + } + + public SocketAddress getLocalAddress() + { + return _delegate.getLocalAddress(); + } + + public long getWrittenBytes() + { + return _delegate.getWrittenBytes(); + } + + public long getReadBytes() + { + return _delegate.getReadBytes(); + } + + public void closed() + { + _delegate.closed(); + } + + public void writerIdle() + { + _delegate.writerIdle(); + } + + public void readerIdle() + { + _delegate.readerIdle(); + } + + public void received(ByteBuffer msg) + { + _delegate.received(msg); + } + + public void exception(Throwable t) + { + _delegate.exception(t); + } + + private static final int MINIMUM_REQUIRED_HEADER_BYTES = 8; + + private static final byte[] AMQP_0_8_HEADER = + new byte[] { (byte) 'A', + (byte) 'M', + (byte) 'Q', + (byte) 'P', + (byte) 1, + (byte) 1, + (byte) 8, + (byte) 0 + }; + + private static final byte[] AMQP_0_9_HEADER = + new byte[] { (byte) 'A', + (byte) 'M', + (byte) 'Q', + (byte) 'P', + (byte) 1, + (byte) 1, + (byte) 0, + (byte) 9 + }; + +private static final byte[] AMQP_0_9_1_HEADER = + new byte[] { (byte) 'A', + (byte) 'M', + (byte) 'Q', + (byte) 'P', + (byte) 0, + (byte) 0, + (byte) 9, + (byte) 1 + }; + + + private static final byte[] AMQP_0_10_HEADER = + new byte[] { (byte) 'A', + (byte) 'M', + (byte) 'Q', + (byte) 'P', + (byte) 1, + (byte) 1, + (byte) 0, + (byte) 10 + }; + + private static interface DelegateCreator + { + VERSION getVersion(); + byte[] getHeaderIdentifier(); + ProtocolEngine getProtocolEngine(); + } + + private DelegateCreator creator_0_8 = new DelegateCreator() + { + + public VERSION getVersion() + { + return VERSION.v0_8; + } + + public byte[] getHeaderIdentifier() + { + return AMQP_0_8_HEADER; + } + + public ProtocolEngine getProtocolEngine() + { + return new AMQProtocolEngine(_appRegistry.getVirtualHostRegistry(), _networkDriver); + } + }; + + private DelegateCreator creator_0_9 = new DelegateCreator() + { + + public VERSION getVersion() + { + return VERSION.v0_9; + } + + + public byte[] getHeaderIdentifier() + { + return AMQP_0_9_HEADER; + } + + public ProtocolEngine getProtocolEngine() + { + return new AMQProtocolEngine(_appRegistry.getVirtualHostRegistry(), _networkDriver); + } + }; + + private DelegateCreator creator_0_9_1 = new DelegateCreator() + { + + public VERSION getVersion() + { + return VERSION.v0_9_1; + } + + + public byte[] getHeaderIdentifier() + { + return AMQP_0_9_1_HEADER; + } + + public ProtocolEngine getProtocolEngine() + { + return new AMQProtocolEngine(_appRegistry.getVirtualHostRegistry(), _networkDriver); + } + }; + + + private DelegateCreator creator_0_10 = new DelegateCreator() + { + + public VERSION getVersion() + { + return VERSION.v0_10; + } + + + public byte[] getHeaderIdentifier() + { + return AMQP_0_10_HEADER; + } + + public ProtocolEngine getProtocolEngine() + { + final ConnectionDelegate connDelegate = + new org.apache.qpid.server.transport.ServerConnectionDelegate(_appRegistry, _fqdn); + + ServerConnection conn = new ServerConnection(); + conn.setConnectionDelegate(connDelegate); + + return new ProtocolEngine_0_10( conn, _networkDriver, _appRegistry); + } + }; + + private final DelegateCreator[] _creators = + new DelegateCreator[] { creator_0_8, creator_0_9, creator_0_9_1, creator_0_10 }; + + + private class ClosedDelegateProtocolEngine implements ProtocolEngine + { + public void setNetworkDriver(NetworkDriver driver) + { + _networkDriver = driver; + } + + public SocketAddress getRemoteAddress() + { + return _networkDriver.getRemoteAddress(); + } + + public SocketAddress getLocalAddress() + { + return _networkDriver.getLocalAddress(); + } + + public long getWrittenBytes() + { + return 0; + } + + public long getReadBytes() + { + return 0; + } + + public void received(ByteBuffer msg) + { + _logger.error("Error processing incoming data, could not negotiate a common protocol"); + } + + public void exception(Throwable t) + { + _logger.error("Error establishing session", t); + } + + public void closed() + { + + } + + public void writerIdle() + { + + } + + public void readerIdle() + { + + } + } + + private class SelfDelegateProtocolEngine implements ProtocolEngine + { + + private final ByteBuffer _header = ByteBuffer.allocate(MINIMUM_REQUIRED_HEADER_BYTES); + + public void setNetworkDriver(NetworkDriver driver) + { + _networkDriver = driver; + } + + public SocketAddress getRemoteAddress() + { + return _networkDriver.getRemoteAddress(); + } + + public SocketAddress getLocalAddress() + { + return _networkDriver.getLocalAddress(); + } + + public long getWrittenBytes() + { + return 0; + } + + public long getReadBytes() + { + return 0; + } + + public void received(ByteBuffer msg) + { + ByteBuffer msgheader = msg.duplicate(); + if(_header.remaining() > msgheader.limit()) + { + msg.position(msg.limit()); + } + else + { + msgheader.limit(_header.remaining()); + msg.position(_header.remaining()); + } + + _header.put(msgheader); + + if(!_header.hasRemaining()) + { + _header.flip(); + byte[] headerBytes = new byte[MINIMUM_REQUIRED_HEADER_BYTES]; + _header.get(headerBytes); + + + ProtocolEngine newDelegate = null; + byte[] newestSupported = null; + + for(int i = 0; newDelegate == null && i < _creators.length; i++) + { + + if(_supported.contains(_creators[i].getVersion())) + { + newestSupported = _creators[i].getHeaderIdentifier(); + byte[] compareBytes = _creators[i].getHeaderIdentifier(); + boolean equal = true; + for(int j = 0; equal && j<compareBytes.length; j++) + { + equal = headerBytes[j] == compareBytes[j]; + } + if(equal) + { + newDelegate = _creators[i].getProtocolEngine(); + } + } + } + + // If no delegate is found then send back the most recent support protocol version id + if(newDelegate == null) + { + _networkDriver.send(ByteBuffer.wrap(newestSupported)); + + _delegate = new ClosedDelegateProtocolEngine(); + } + else + { + newDelegate.setNetworkDriver(_networkDriver); + + _delegate = newDelegate; + + _header.flip(); + _delegate.received(_header); + if(msg.hasRemaining()) + { + _delegate.received(msg); + } + } + + } + + } + + public void exception(Throwable t) + { + _logger.error("Error establishing session", t); + } + + public void closed() + { + + } + + public void writerIdle() + { + + } + + public void readerIdle() + { + + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/MultiVersionProtocolEngineFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/MultiVersionProtocolEngineFactory.java new file mode 100755 index 0000000000..75358c42d9 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/MultiVersionProtocolEngineFactory.java @@ -0,0 +1,75 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.protocol; + +import org.apache.qpid.protocol.ProtocolEngineFactory; +import org.apache.qpid.protocol.ProtocolEngine; +import org.apache.qpid.transport.NetworkDriver; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; + +import java.util.Set; +import java.util.Arrays; +import java.util.HashSet; + +public class MultiVersionProtocolEngineFactory implements ProtocolEngineFactory +{ + ; + + + public enum VERSION { v0_8, v0_9, v0_9_1, v0_10 }; + + private static final Set<VERSION> ALL_VERSIONS = new HashSet<VERSION>(Arrays.asList(VERSION.values())); + + private final IApplicationRegistry _appRegistry; + private final String _fqdn; + private final Set<VERSION> _supported; + + + public MultiVersionProtocolEngineFactory() + { + this(1, "localhost", ALL_VERSIONS); + } + + public MultiVersionProtocolEngineFactory(String fqdn, Set<VERSION> versions) + { + this(1, fqdn, versions); + } + + + public MultiVersionProtocolEngineFactory(String fqdn) + { + this(1, fqdn, ALL_VERSIONS); + } + + public MultiVersionProtocolEngineFactory(int instance, String fqdn, Set<VERSION> supportedVersions) + { + _appRegistry = ApplicationRegistry.getInstance(instance); + _fqdn = fqdn; + _supported = supportedVersions; + } + + + public ProtocolEngine newProtocolEngine(NetworkDriver networkDriver) + { + return new MultiVersionProtocolEngine(_appRegistry, _fqdn, _supported, networkDriver); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/ProtocolEngine_0_10.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/ProtocolEngine_0_10.java new file mode 100755 index 0000000000..30d506a89b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/protocol/ProtocolEngine_0_10.java @@ -0,0 +1,201 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.protocol; + +import org.apache.qpid.protocol.ProtocolEngine; +import org.apache.qpid.transport.NetworkDriver; +import org.apache.qpid.transport.network.InputHandler; +import org.apache.qpid.transport.network.Assembler; +import org.apache.qpid.transport.network.Disassembler; +import org.apache.qpid.server.configuration.*; +import org.apache.qpid.server.transport.ServerConnection; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.ConnectionMessages; +import org.apache.qpid.server.registry.IApplicationRegistry; + +import java.net.SocketAddress; +import java.util.UUID; + +public class ProtocolEngine_0_10 extends InputHandler implements ProtocolEngine, ConnectionConfig +{ + public static final int MAX_FRAME_SIZE = 64 * 1024 - 1; + + private NetworkDriver _networkDriver; + private long _readBytes; + private long _writtenBytes; + private ServerConnection _connection; + private final UUID _id; + private final IApplicationRegistry _appRegistry; + private long _createTime = System.currentTimeMillis(); + + public ProtocolEngine_0_10(ServerConnection conn, + NetworkDriver networkDriver, + final IApplicationRegistry appRegistry) + { + super(new Assembler(conn)); + _connection = conn; + _connection.setConnectionConfig(this); + _networkDriver = networkDriver; + _id = appRegistry.getConfigStore().createId(); + _appRegistry = appRegistry; + + // FIXME Two log messages to maintain compatinbility with earlier protocol versions + _connection.getLogActor().message(ConnectionMessages.OPEN(null, null, false, false)); + _connection.getLogActor().message(ConnectionMessages.OPEN(null, "0-10", false, true)); + } + + public void setNetworkDriver(NetworkDriver driver) + { + _networkDriver = driver; + Disassembler dis = new Disassembler(driver, MAX_FRAME_SIZE); + _connection.setSender(dis); + _connection.onOpen(new Runnable() + { + public void run() + { + getConfigStore().addConfiguredObject(ProtocolEngine_0_10.this); + } + }); + + } + + public SocketAddress getRemoteAddress() + { + return _networkDriver.getRemoteAddress(); + } + + public SocketAddress getLocalAddress() + { + return _networkDriver.getLocalAddress(); + } + + public long getReadBytes() + { + return _readBytes; + } + + public long getWrittenBytes() + { + return _writtenBytes; + } + + public void writerIdle() + { + //Todo + } + + public void readerIdle() + { + //Todo + } + + public VirtualHostConfig getVirtualHost() + { + return _connection.getVirtualHost(); + } + + public String getAddress() + { + return getRemoteAddress().toString(); + } + + public Boolean isIncoming() + { + return true; + } + + public Boolean isSystemConnection() + { + return false; + } + + public Boolean isFederationLink() + { + return false; + } + + public String getAuthId() + { + return _connection.getAuthorizationID(); + } + + public String getRemoteProcessName() + { + return null; + } + + public Integer getRemotePID() + { + return null; + } + + public Integer getRemoteParentPID() + { + return null; + } + + public ConfigStore getConfigStore() + { + return _appRegistry.getConfigStore(); + } + + public UUID getId() + { + return _id; + } + + public ConnectionConfigType getConfigType() + { + return ConnectionConfigType.getInstance(); + } + + public ConfiguredObject getParent() + { + return getVirtualHost(); + } + + public boolean isDurable() + { + return false; + } + + @Override + public void closed() + { + super.closed(); + getConfigStore().removeConfiguredObject(this); + } + + public long getCreateTime() + { + return _createTime; + } + + public Boolean isShadow() + { + return false; + } + + public void mgmtClose() + { + _connection.mgmtClose(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQPriorityQueue.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQPriorityQueue.java new file mode 100644 index 0000000000..b6e97e08fb --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQPriorityQueue.java @@ -0,0 +1,90 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.queue; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.subscription.SubscriptionList; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.Map; + +public class AMQPriorityQueue extends SimpleAMQQueue +{ + protected AMQPriorityQueue(final AMQShortString name, + final boolean durable, + final AMQShortString owner, + final boolean autoDelete, + boolean exclusive, + final VirtualHost virtualHost, + int priorities, Map<String, Object> arguments) + { + super(name, durable, owner, autoDelete, exclusive, virtualHost,new PriorityQueueList.Factory(priorities), arguments); + } + + public AMQPriorityQueue(String queueName, + boolean durable, + String owner, + boolean autoDelete, + boolean exclusive, VirtualHost virtualHost, int priorities, Map<String,Object> arguments) + { + this(queueName == null ? null : new AMQShortString(queueName), durable, owner == null ? null : new AMQShortString(owner), + autoDelete, exclusive,virtualHost, priorities, arguments); + } + + public int getPriorities() + { + return ((PriorityQueueList) _entries).getPriorities(); + } + + @Override + protected void checkSubscriptionsNotAheadOfDelivery(final QueueEntry entry) + { + // check that all subscriptions are not in advance of the entry + SubscriptionList.SubscriptionNodeIterator subIter = _subscriptionList.iterator(); + while(subIter.advance() && !entry.isAcquired()) + { + final Subscription subscription = subIter.getNode().getSubscription(); + if(!subscription.isClosed()) + { + QueueContext context = (QueueContext) subscription.getQueueContext(); + if(context != null) + { + QueueEntry subnode = context._lastSeenEntry; + QueueEntry released = context._releasedEntry; + while(subnode != null && entry.compareTo(subnode) < 0 && !entry.isAcquired() && (released == null || released.compareTo(entry) < 0)) + { + if(QueueContext._releasedUpdater.compareAndSet(context,released,entry)) + { + break; + } + else + { + subnode = context._lastSeenEntry; + released = context._releasedEntry; + } + } + } + } + + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java new file mode 100644 index 0000000000..9b9de8333b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueue.java @@ -0,0 +1,275 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.configuration.QueueConfig; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeReferrer; +import org.apache.qpid.server.management.Managable; +import org.apache.qpid.server.management.ManagedObject; +import org.apache.qpid.server.security.PrincipalHolder; +import org.apache.qpid.server.store.TransactionLogResource; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public interface AMQQueue extends Managable, Comparable<AMQQueue>, ExchangeReferrer, TransactionLogResource, BaseQueue, + QueueConfig +{ + boolean getDeleteOnNoConsumers(); + + void setDeleteOnNoConsumers(boolean b); + + void addBinding(Binding binding); + + void removeBinding(Binding binding); + + List<Binding> getBindings(); + + int getBindingCount(); + + LogSubject getLogSubject(); + + public interface Context + { + QueueEntry getLastSeenEntry(); + } + + void setNoLocal(boolean b); + + boolean isAutoDelete(); + + AMQShortString getOwner(); + PrincipalHolder getPrincipalHolder(); + void setPrincipalHolder(PrincipalHolder principalHolder); + + void setExclusiveOwningSession(AMQSessionModel owner); + AMQSessionModel getExclusiveOwningSession(); + + VirtualHost getVirtualHost(); + + void registerSubscription(final Subscription subscription, final boolean exclusive) throws AMQException; + + void unregisterSubscription(final Subscription subscription) throws AMQException; + + + int getConsumerCount(); + + int getActiveConsumerCount(); + + boolean hasExclusiveSubscriber(); + + boolean isUnused(); + + boolean isEmpty(); + + int getMessageCount(); + + int getUndeliveredMessageCount(); + + + long getQueueDepth(); + + long getReceivedMessageCount(); + + long getOldestMessageArrivalTime(); + + boolean isDeleted(); + + int delete() throws AMQException; + + void requeue(QueueEntry entry); + + void dequeue(QueueEntry entry, Subscription sub); + + void decrementUnackedMsgCount(); + + boolean resend(final QueueEntry entry, final Subscription subscription) throws AMQException; + + void addQueueDeleteTask(final Task task); + void removeQueueDeleteTask(final Task task); + + + + List<QueueEntry> getMessagesOnTheQueue(); + + List<QueueEntry> getMessagesOnTheQueue(long fromMessageId, long toMessageId); + + List<Long> getMessagesOnTheQueue(int num); + + List<Long> getMessagesOnTheQueue(int num, int offest); + + QueueEntry getMessageOnTheQueue(long messageId); + + /** + * Returns a list of QueEntries from a given range of queue positions, eg messages 5 to 10 on the queue. + * + * The 'queue position' index starts from 1. Using 0 in 'from' will be ignored and continue from 1. + * Using 0 in the 'to' field will return an empty list regardless of the 'from' value. + * @param fromPosition + * @param toPosition + * @return + */ + public List<QueueEntry> getMessagesRangeOnTheQueue(final long fromPosition, final long toPosition); + + + void moveMessagesToAnotherQueue(long fromMessageId, long toMessageId, String queueName, + ServerTransaction transaction); + + void copyMessagesToAnotherQueue(long fromMessageId, long toMessageId, String queueName, ServerTransaction transaction); + + void removeMessagesFromQueue(long fromMessageId, long toMessageId); + + + + long getMaximumMessageSize(); + + void setMaximumMessageSize(long value); + + + long getMaximumMessageCount(); + + void setMaximumMessageCount(long value); + + + long getMaximumQueueDepth(); + + void setMaximumQueueDepth(long value); + + + long getMaximumMessageAge(); + + void setMaximumMessageAge(final long maximumMessageAge); + + + long getMinimumAlertRepeatGap(); + + void setMinimumAlertRepeatGap(long value); + + + long getCapacity(); + + void setCapacity(long capacity); + + + long getFlowResumeCapacity(); + + void setFlowResumeCapacity(long flowResumeCapacity); + + boolean isOverfull(); + + void deleteMessageFromTop(); + + long clearQueue() throws AMQException; + + /** + * Checks the status of messages on the queue, purging expired ones, firing age related alerts etc. + * @throws AMQException + */ + void checkMessageStatus() throws AMQException; + + Set<NotificationCheck> getNotificationChecks(); + + void flushSubscription(final Subscription sub) throws AMQException; + + void deliverAsync(final Subscription sub); + + void deliverAsync(); + + void stop(); + + boolean isExclusive(); + + Exchange getAlternateExchange(); + + void setAlternateExchange(Exchange exchange); + + Map<String, Object> getArguments(); + + void checkCapacity(AMQChannel channel); + + /** + * ExistingExclusiveSubscription signals a failure to create a subscription, because an exclusive subscription + * already exists. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represent failure to create a subscription, because an exclusive subscription already exists. + * </table> + * + * @todo Not an AMQP exception as no status code. + * + * @todo Move to top level, used outside this class. + */ + static final class ExistingExclusiveSubscription extends AMQException + { + + public ExistingExclusiveSubscription() + { + super(""); + } + } + + /** + * ExistingSubscriptionPreventsExclusive signals a failure to create an exclusize subscription, as a subscription + * already exists. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Represent failure to create an exclusize subscription, as a subscription already exists. + * </table> + * + * @todo Not an AMQP exception as no status code. + * + * @todo Move to top level, used outside this class. + */ + static final class ExistingSubscriptionPreventsExclusive extends AMQException + { + public ExistingSubscriptionPreventsExclusive() + { + super(""); + } + } + + static interface Task + { + public void doTask(AMQQueue queue) throws AMQException; + } + + void configure(ConfigurationPlugin config); + + ConfigurationPlugin getConfiguration(); + + ManagedObject getManagedObject(); + + void setExclusive(boolean exclusive) throws AMQException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueFactory.java new file mode 100644 index 0000000000..bee55118ba --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueFactory.java @@ -0,0 +1,253 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.configuration.QueueConfiguration; + +import java.util.Map; +import java.util.HashMap; + +public class AMQQueueFactory +{ + public static final AMQShortString X_QPID_PRIORITIES = new AMQShortString("x-qpid-priorities"); + public static final String QPID_LVQ_KEY = "qpid.LVQ_key"; + public static final String QPID_LAST_VALUE_QUEUE = "qpid.last_value_queue"; + public static final String QPID_LAST_VALUE_QUEUE_KEY = "qpid.last_value_queue_key"; + + private abstract static class QueueProperty + { + + private final AMQShortString _argumentName; + + + public QueueProperty(String argumentName) + { + _argumentName = new AMQShortString(argumentName); + } + + public AMQShortString getArgumentName() + { + return _argumentName; + } + + + public abstract void setPropertyValue(AMQQueue queue, Object value); + + } + + private abstract static class QueueLongProperty extends QueueProperty + { + + public QueueLongProperty(String argumentName) + { + super(argumentName); + } + + public void setPropertyValue(AMQQueue queue, Object value) + { + if(value instanceof Number) + { + setPropertyValue(queue, ((Number)value).longValue()); + } + + } + + abstract void setPropertyValue(AMQQueue queue, long value); + + + } + + private static final QueueProperty[] DECLAREABLE_PROPERTIES = { + new QueueLongProperty("x-qpid-maximum-message-age") + { + public void setPropertyValue(AMQQueue queue, long value) + { + queue.setMaximumMessageAge(value); + } + }, + new QueueLongProperty("x-qpid-maximum-message-size") + { + public void setPropertyValue(AMQQueue queue, long value) + { + queue.setMaximumMessageSize(value); + } + }, + new QueueLongProperty("x-qpid-maximum-message-count") + { + public void setPropertyValue(AMQQueue queue, long value) + { + queue.setMaximumMessageCount(value); + } + }, + new QueueLongProperty("x-qpid-minimum-alert-repeat-gap") + { + public void setPropertyValue(AMQQueue queue, long value) + { + queue.setMinimumAlertRepeatGap(value); + } + }, + new QueueLongProperty("x-qpid-capacity") + { + public void setPropertyValue(AMQQueue queue, long value) + { + queue.setCapacity(value); + } + }, + new QueueLongProperty("x-qpid-flow-resume-capacity") + { + public void setPropertyValue(AMQQueue queue, long value) + { + queue.setFlowResumeCapacity(value); + } + } + + }; + + + /** @see #createAMQQueueImpl(String, boolean, String, boolean, boolean, VirtualHost, Map) */ + public static AMQQueue createAMQQueueImpl(AMQShortString name, + boolean durable, + AMQShortString owner, + boolean autoDelete, + boolean exclusive, + VirtualHost virtualHost, final FieldTable arguments) throws AMQException + { + return createAMQQueueImpl(name == null ? null : name.toString(), + durable, + owner == null ? null : owner.toString(), + autoDelete, + exclusive, + virtualHost, FieldTable.convertToMap(arguments)); + } + + + public static AMQQueue createAMQQueueImpl(String queueName, + boolean durable, + String owner, + boolean autoDelete, + boolean exclusive, + VirtualHost virtualHost, Map<String, Object> arguments) throws AMQSecurityException + { + // Access check + if (!virtualHost.getSecurityManager().authoriseCreateQueue(autoDelete, durable, exclusive, null, null, new AMQShortString(queueName), owner)) + { + String description = "Permission denied: queue-name '" + queueName + "'"; + throw new AMQSecurityException(description); + } + + int priorities = 1; + String conflationKey = null; + if(arguments != null) + { + if(arguments.containsKey(QPID_LAST_VALUE_QUEUE) || arguments.containsKey(QPID_LAST_VALUE_QUEUE_KEY)) + { + conflationKey = (String) arguments.get(QPID_LAST_VALUE_QUEUE_KEY); + if(conflationKey == null) + { + conflationKey = QPID_LVQ_KEY; + } + } + else if(arguments.containsKey(X_QPID_PRIORITIES.toString())) + { + Object prioritiesObj = arguments.get(X_QPID_PRIORITIES.toString()); + if(prioritiesObj instanceof Number) + { + priorities = ((Number)prioritiesObj).intValue(); + } + } + } + + AMQQueue q; + if(conflationKey != null) + { + q = new ConflationQueue(queueName, durable, owner, autoDelete, exclusive, virtualHost, arguments, conflationKey); + } + else if(priorities > 1) + { + q = new AMQPriorityQueue(queueName, durable, owner, autoDelete, exclusive, virtualHost, priorities, arguments); + } + else + { + q = new SimpleAMQQueue(queueName, durable, owner, autoDelete, exclusive, virtualHost, arguments); + } + + //Register the new queue + virtualHost.getQueueRegistry().registerQueue(q); + q.configure(virtualHost.getConfiguration().getQueueConfiguration(queueName)); + + if(arguments != null) + { + for(QueueProperty p : DECLAREABLE_PROPERTIES) + { + if(arguments.containsKey(p.getArgumentName().toString())) + { + p.setPropertyValue(q, arguments.get(p.getArgumentName().toString())); + } + } + } + + return q; + + } + + + public static AMQQueue createAMQQueueImpl(QueueConfiguration config, VirtualHost host) throws AMQException + { + String queueName = config.getName(); + + boolean durable = config.getDurable(); + boolean autodelete = config.getAutoDelete(); + boolean exclusive = config.getExclusive(); + String owner = config.getOwner(); + Map<String,Object> arguments = null; + if(config.isLVQ() || config.getLVQKey() != null) + { + + arguments = new HashMap<String,Object>(); + arguments.put(QPID_LAST_VALUE_QUEUE, 1); + arguments.put(QPID_LAST_VALUE_QUEUE_KEY, config.getLVQKey() == null ? QPID_LVQ_KEY : config.getLVQKey()); + } + else + { + boolean priority = config.getPriority(); + int priorities = config.getPriorities(); + if(priority || priorities > 0) + { + arguments = new HashMap<String,Object>(); + if (priorities < 0) + { + priorities = 10; + } + arguments.put("x-qpid-priorities", priorities); + } + } + + AMQQueue q = createAMQQueueImpl(queueName, durable, owner, autodelete, exclusive, host, arguments); + q.configure(config); + return q; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueMBean.java new file mode 100644 index 0000000000..c8eb118b11 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/AMQQueueMBean.java @@ -0,0 +1,647 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.queue; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.management.common.mbeans.ManagedQueue; +import org.apache.qpid.management.common.mbeans.annotations.MBeanConstructor; +import org.apache.qpid.management.common.mbeans.annotations.MBeanDescription; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.management.AMQManagedObject; +import org.apache.qpid.server.management.ManagedObject; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.AMQMessage; +import org.apache.qpid.server.message.MessageTransferMessage; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.txn.LocalTransaction; +import org.apache.qpid.transport.MessageProperties; + +import javax.management.JMException; +import javax.management.MBeanException; +import javax.management.MBeanNotificationInfo; +import javax.management.Notification; +import javax.management.ObjectName; +import javax.management.OperationsException; +import javax.management.monitor.MonitorNotification; +import javax.management.openmbean.ArrayType; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; + +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * AMQQueueMBean is the management bean for an {@link AMQQueue}. + * + * <p/><tablse id="crc"><caption>CRC Caption</caption> + * <tr><th> Responsibilities <th> Collaborations + * </table> + */ +@MBeanDescription("Management Interface for AMQQueue") +public class AMQQueueMBean extends AMQManagedObject implements ManagedQueue, QueueNotificationListener +{ + /** Used for debugging purposes. */ + private static final Logger _logger = Logger.getLogger(AMQQueueMBean.class); + + private static final SimpleDateFormat _dateFormat = new SimpleDateFormat("MM-dd-yy HH:mm:ss.SSS z"); + + private AMQQueue _queue = null; + private String _queueName = null; + // OpenMBean data types for viewMessages method + + private static OpenType[] _msgAttributeTypes = new OpenType[5]; // AMQ message attribute types. + private static CompositeType _messageDataType = null; // Composite type for representing AMQ Message data. + private static TabularType _messagelistDataType = null; // Datatype for representing AMQ messages list. + + // OpenMBean data types for viewMessageContent method + private static CompositeType _msgContentType = null; + private static OpenType[] _msgContentAttributeTypes = new OpenType[4]; + + private final long[] _lastNotificationTimes = new long[NotificationCheck.values().length]; + private Notification _lastNotification = null; + + + + + @MBeanConstructor("Creates an MBean exposing an AMQQueue") + public AMQQueueMBean(AMQQueue queue) throws JMException + { + super(ManagedQueue.class, ManagedQueue.TYPE); + _queue = queue; + _queueName = queue.getName(); + } + + public ManagedObject getParentObject() + { + return _queue.getVirtualHost().getManagedObject(); + } + + static + { + try + { + init(); + } + catch (JMException ex) + { + // This is not expected to ever occur. + throw new RuntimeException("Got JMException in static initializer.", ex); + } + } + + /** + * initialises the openmbean data types + */ + private static void init() throws OpenDataException + { + _msgContentAttributeTypes[0] = SimpleType.LONG; // For message id + _msgContentAttributeTypes[1] = SimpleType.STRING; // For MimeType + _msgContentAttributeTypes[2] = SimpleType.STRING; // For Encoding + _msgContentAttributeTypes[3] = new ArrayType(1, SimpleType.BYTE); // For message content + _msgContentType = new CompositeType("Message Content", "AMQ Message Content", + VIEW_MSG_CONTENT_COMPOSITE_ITEM_NAMES_DESC.toArray(new String[VIEW_MSG_CONTENT_COMPOSITE_ITEM_NAMES_DESC.size()]), + VIEW_MSG_CONTENT_COMPOSITE_ITEM_NAMES_DESC.toArray(new String[VIEW_MSG_CONTENT_COMPOSITE_ITEM_NAMES_DESC.size()]), + _msgContentAttributeTypes); + + _msgAttributeTypes[0] = SimpleType.LONG; // For message id + _msgAttributeTypes[1] = new ArrayType(1, SimpleType.STRING); // For header attributes + _msgAttributeTypes[2] = SimpleType.LONG; // For size + _msgAttributeTypes[3] = SimpleType.BOOLEAN; // For redelivered + _msgAttributeTypes[4] = SimpleType.LONG; // For queue position + + _messageDataType = new CompositeType("Message", "AMQ Message", + VIEW_MSGS_COMPOSITE_ITEM_NAMES_DESC.toArray(new String[VIEW_MSGS_COMPOSITE_ITEM_NAMES_DESC.size()]), + VIEW_MSGS_COMPOSITE_ITEM_NAMES_DESC.toArray(new String[VIEW_MSGS_COMPOSITE_ITEM_NAMES_DESC.size()]), _msgAttributeTypes); + _messagelistDataType = new TabularType("Messages", "List of messages", _messageDataType, + VIEW_MSGS_TABULAR_UNIQUE_INDEX.toArray(new String[VIEW_MSGS_TABULAR_UNIQUE_INDEX.size()])); + } + + public String getObjectInstanceName() + { + return ObjectName.quote(_queueName); + } + + public String getName() + { + return _queueName; + } + + public boolean isDurable() + { + return _queue.isDurable(); + } + + public String getOwner() + { + return String.valueOf(_queue.getOwner()); + } + + public boolean isAutoDelete() + { + return _queue.isAutoDelete(); + } + + public Integer getMessageCount() + { + return _queue.getMessageCount(); + } + + public Long getMaximumMessageSize() + { + return _queue.getMaximumMessageSize(); + } + + public Long getMaximumMessageAge() + { + return _queue.getMaximumMessageAge(); + } + + public void setMaximumMessageAge(Long maximumMessageAge) + { + _queue.setMaximumMessageAge(maximumMessageAge); + } + + public void setMaximumMessageSize(Long value) + { + _queue.setMaximumMessageSize(value); + } + + public Integer getConsumerCount() + { + return _queue.getConsumerCount(); + } + + public Integer getActiveConsumerCount() + { + return _queue.getActiveConsumerCount(); + } + + public Long getReceivedMessageCount() + { + return _queue.getReceivedMessageCount(); + } + + public Long getMaximumMessageCount() + { + return _queue.getMaximumMessageCount(); + } + + public void setMaximumMessageCount(Long value) + { + _queue.setMaximumMessageCount(value); + } + + /** + * returns the maximum total size of messages(bytes) in the queue. + */ + public Long getMaximumQueueDepth() + { + return _queue.getMaximumQueueDepth(); + } + + public void setMaximumQueueDepth(Long value) + { + _queue.setMaximumQueueDepth(value); + } + + /** + * returns the total size of messages(bytes) in the queue. + */ + public Long getQueueDepth() throws JMException + { + return _queue.getQueueDepth(); + } + + public Long getCapacity() + { + return _queue.getCapacity(); + } + + public void setCapacity(Long capacity) throws IllegalArgumentException + { + if( _queue.getFlowResumeCapacity() > capacity ) + { + throw new IllegalArgumentException("Capacity must not be less than FlowResumeCapacity"); + } + + _queue.setCapacity(capacity); + } + + public Long getFlowResumeCapacity() + { + return _queue.getFlowResumeCapacity(); + } + + public void setFlowResumeCapacity(Long flowResumeCapacity) throws IllegalArgumentException + { + if( _queue.getCapacity() < flowResumeCapacity ) + { + throw new IllegalArgumentException("FlowResumeCapacity must not exceed Capacity"); + } + + _queue.setFlowResumeCapacity(flowResumeCapacity); + } + + public boolean isFlowOverfull() + { + return _queue.isOverfull(); + } + + public boolean isExclusive() + { + return _queue.isExclusive(); + } + + public void setExclusive(boolean exclusive) throws JMException + { + try + { + _queue.setExclusive(exclusive); + } + catch (AMQException e) + { + throw new JMException(e.toString()); + } + } + + /** + * Checks if there is any notification to be send to the listeners + */ + public void checkForNotification(ServerMessage msg) throws AMQException + { + + final Set<NotificationCheck> notificationChecks = _queue.getNotificationChecks(); + + if(!notificationChecks.isEmpty()) + { + final long currentTime = System.currentTimeMillis(); + final long thresholdTime = currentTime - _queue.getMinimumAlertRepeatGap(); + + for (NotificationCheck check : notificationChecks) + { + if (check.isMessageSpecific() || (_lastNotificationTimes[check.ordinal()] < thresholdTime)) + { + if (check.notifyIfNecessary(msg, _queue, this)) + { + _lastNotificationTimes[check.ordinal()] = currentTime; + } + } + } + } + + } + + /** + * Sends the notification to the listeners + */ + public void notifyClients(NotificationCheck notification, AMQQueue queue, String notificationMsg) + { + // important : add log to the log file - monitoring tools may be looking for this + _logger.info(notification.name() + " On Queue " + queue.getNameShortString() + " - " + notificationMsg); + notificationMsg = notification.name() + " " + notificationMsg; + + _lastNotification = + new Notification(MonitorNotification.THRESHOLD_VALUE_EXCEEDED, this, ++_notificationSequenceNumber, + System.currentTimeMillis(), notificationMsg); + + _broadcaster.sendNotification(_lastNotification); + } + + public Notification getLastNotification() + { + return _lastNotification; + } + + /** + * @see AMQQueue#deleteMessageFromTop + */ + public void deleteMessageFromTop() throws JMException + { + _queue.deleteMessageFromTop(); + } + + /** + * Clears the queue of non-acquired messages + * + * @return the number of messages deleted + * @see AMQQueue#clearQueue + */ + public Long clearQueue() throws JMException + { + try + { + return _queue.clearQueue(); + } + catch (AMQException ex) + { + throw new MBeanException(ex, "Error clearing queue " + _queueName); + } + } + + /** + * returns message content as byte array and related attributes for the given message id. + */ + public CompositeData viewMessageContent(long msgId) throws JMException + { + QueueEntry entry = _queue.getMessageOnTheQueue(msgId); + + if (entry == null) + { + throw new OperationsException("AMQMessage with message id = " + msgId + " is not in the " + _queueName); + } + + ServerMessage serverMsg = entry.getMessage(); + final int bodySize = (int) serverMsg.getSize(); + + + List<Byte> msgContent = new ArrayList<Byte>(); + + java.nio.ByteBuffer buf = java.nio.ByteBuffer.allocate(bodySize); + int position = 0; + + while(position < bodySize) + { + position += serverMsg.getContent(buf, position); + buf.flip(); + for(int i = 0; i < buf.limit(); i++) + { + msgContent.add(buf.get(i)); + } + buf.clear(); + } + + AMQMessageHeader header = serverMsg.getMessageHeader(); + + String mimeType = null, encoding = null; + if (header != null) + { + mimeType = header.getMimeType(); + + encoding = header.getEncoding(); + } + + + Object[] itemValues = { msgId, mimeType, encoding, msgContent.toArray(new Byte[0]) }; + + return new CompositeDataSupport(_msgContentType, + VIEW_MSG_CONTENT_COMPOSITE_ITEM_NAMES_DESC.toArray( + new String[VIEW_MSG_CONTENT_COMPOSITE_ITEM_NAMES_DESC.size()]), itemValues); + + } + + /** + * Returns the header contents of the messages stored in this queue in tabular form. + * Deprecated as of Qpid JMX API 1.3 + */ + @Deprecated + public TabularData viewMessages(int beginIndex, int endIndex) throws JMException + { + return viewMessages((long)beginIndex,(long)endIndex); + } + + + /** + * Returns the header contents of the messages stored in this queue in tabular form. + * @param startPosition The queue position of the first message to be viewed + * @param endPosition The queue position of the last message to be viewed + */ + public TabularData viewMessages(long startPosition, long endPosition) throws JMException + { + if ((startPosition > endPosition) || (startPosition < 1)) + { + throw new OperationsException("From Index = " + startPosition + ", To Index = " + endPosition + + "\n\"From Index\" should be greater than 0 and less than \"To Index\""); + } + + if ((endPosition - startPosition) > Integer.MAX_VALUE) + { + throw new OperationsException("Specified MessageID interval is too large. Intervals must be less than 2^31 in size"); + } + + List<QueueEntry> list = _queue.getMessagesRangeOnTheQueue(startPosition,endPosition); + TabularDataSupport _messageList = new TabularDataSupport(_messagelistDataType); + + try + { + // Create the tabular list of message header contents + int size = list.size(); + + for (int i = 0; i < size ; i++) + { + long position = startPosition + i; + final QueueEntry queueEntry = list.get(i); + ServerMessage serverMsg = queueEntry.getMessage(); + + String[] headerAttributes = null; + Object[] itemValues = null; + + if(serverMsg instanceof AMQMessage) + { + AMQMessage msg = (AMQMessage) serverMsg; + ContentHeaderBody headerBody = msg.getContentHeaderBody(); + // Create header attributes list + headerAttributes = getMessageHeaderProperties(headerBody); + itemValues = new Object[]{msg.getMessageId(), headerAttributes, headerBody.bodySize, queueEntry.isRedelivered(), position}; + } + else if(serverMsg instanceof MessageTransferMessage) + { + // We have a 0-10 message + MessageTransferMessage msg = (MessageTransferMessage) serverMsg; + + // Create header attributes list + headerAttributes = getMessageTransferMessageHeaderProps(msg); + itemValues = new Object[]{msg.getMessageNumber(), headerAttributes, msg.getSize(), queueEntry.isRedelivered(), position}; + } + else + { + //unknown message + headerAttributes = new String[]{"N/A"}; + itemValues = new Object[]{serverMsg.getMessageNumber(), headerAttributes, serverMsg.getSize(), queueEntry.isRedelivered(), position}; + } + + CompositeData messageData = new CompositeDataSupport(_messageDataType, + VIEW_MSGS_COMPOSITE_ITEM_NAMES_DESC.toArray(new String[VIEW_MSGS_COMPOSITE_ITEM_NAMES_DESC.size()]), itemValues); + _messageList.put(messageData); + } + } + catch (AMQException e) + { + JMException jme = new JMException("Error creating message contents: " + e); + jme.initCause(e); + throw jme; + } + + return _messageList; + } + + private String[] getMessageHeaderProperties(ContentHeaderBody headerBody) + { + List<String> list = new ArrayList<String>(); + BasicContentHeaderProperties headerProperties = (BasicContentHeaderProperties) headerBody.getProperties(); + list.add("reply-to = " + headerProperties.getReplyToAsString()); + list.add("propertyFlags = " + headerProperties.getPropertyFlags()); + list.add("ApplicationID = " + headerProperties.getAppIdAsString()); + list.add("ClusterID = " + headerProperties.getClusterIdAsString()); + list.add("UserId = " + headerProperties.getUserIdAsString()); + list.add("JMSMessageID = " + headerProperties.getMessageIdAsString()); + list.add("JMSCorrelationID = " + headerProperties.getCorrelationIdAsString()); + + int delMode = headerProperties.getDeliveryMode(); + list.add("JMSDeliveryMode = " + + ((delMode == BasicContentHeaderProperties.PERSISTENT) ? "Persistent" : "Non_Persistent")); + + list.add("JMSPriority = " + headerProperties.getPriority()); + list.add("JMSType = " + headerProperties.getType()); + + long longDate = headerProperties.getExpiration(); + String strDate = (longDate != 0) ? _dateFormat.format(new Date(longDate)) : null; + list.add("JMSExpiration = " + strDate); + + longDate = headerProperties.getTimestamp(); + strDate = (longDate != 0) ? _dateFormat.format(new Date(longDate)) : null; + list.add("JMSTimestamp = " + strDate); + + return list.toArray(new String[list.size()]); + } + + private String[] getMessageTransferMessageHeaderProps(MessageTransferMessage msg) + { + List<String> list = new ArrayList<String>(); + + AMQMessageHeader header = msg.getMessageHeader(); + MessageProperties msgProps = msg.getHeader().get(MessageProperties.class); + + String appID = null; + String userID = null; + + if(msgProps != null) + { + appID = msgProps.getAppId() == null ? "null" : new String(msgProps.getAppId()); + userID = msgProps.getUserId() == null ? "null" : new String(msgProps.getUserId()); + } + + list.add("reply-to = " + header.getReplyTo()); + list.add("propertyFlags = "); //TODO + list.add("ApplicationID = " + appID); + list.add("ClusterID = "); //TODO + list.add("UserId = " + userID); + list.add("JMSMessageID = " + header.getMessageId()); + list.add("JMSCorrelationID = " + header.getCorrelationId()); + list.add("JMSDeliveryMode = " + (msg.isPersistent() ? "Persistent" : "Non_Persistent")); + list.add("JMSPriority = " + header.getPriority()); + list.add("JMSType = " + header.getType()); + + long longDate = header.getExpiration(); + String strDate = (longDate != 0) ? _dateFormat.format(new Date(longDate)) : null; + list.add("JMSExpiration = " + strDate); + + longDate = header.getTimestamp(); + strDate = (longDate != 0) ? _dateFormat.format(new Date(longDate)) : null; + list.add("JMSTimestamp = " + strDate); + + return list.toArray(new String[list.size()]); + } + + /** + * @see ManagedQueue#moveMessages + * @param fromMessageId + * @param toMessageId + * @param toQueueName + * @throws JMException + */ + public void moveMessages(long fromMessageId, long toMessageId, String toQueueName) throws JMException + { + if ((fromMessageId > toMessageId) || (fromMessageId < 1)) + { + throw new OperationsException("\"From MessageId\" should be greater than 0 and less than \"To MessageId\""); + } + + ServerTransaction txn = new LocalTransaction(_queue.getVirtualHost().getTransactionLog()); + _queue.moveMessagesToAnotherQueue(fromMessageId, toMessageId, toQueueName, txn); + txn.commit(); + } + + /** + * @see ManagedQueue#deleteMessages + * @param fromMessageId + * @param toMessageId + * @throws JMException + */ + public void deleteMessages(long fromMessageId, long toMessageId) throws JMException + { + if ((fromMessageId > toMessageId) || (fromMessageId < 1)) + { + throw new OperationsException("\"From MessageId\" should be greater than 0 and less than \"To MessageId\""); + } + + _queue.removeMessagesFromQueue(fromMessageId, toMessageId); + } + + /** + * @see ManagedQueue#copyMessages + * @param fromMessageId + * @param toMessageId + * @param toQueueName + * @throws JMException + */ + public void copyMessages(long fromMessageId, long toMessageId, String toQueueName) throws JMException + { + if ((fromMessageId > toMessageId) || (fromMessageId < 1)) + { + throw new OperationsException("\"From MessageId\" should be greater than 0 and less than \"To MessageId\""); + } + + ServerTransaction txn = new LocalTransaction(_queue.getVirtualHost().getTransactionLog()); + + _queue.copyMessagesToAnotherQueue(fromMessageId, toMessageId, toQueueName, txn); + + txn.commit(); + + + } + + /** + * returns Notifications sent by this MBean. + */ + @Override + public MBeanNotificationInfo[] getNotificationInfo() + { + String[] notificationTypes = new String[] { MonitorNotification.THRESHOLD_VALUE_EXCEEDED }; + String name = MonitorNotification.class.getName(); + String description = "Either Message count or Queue depth or Message size has reached threshold high value"; + MBeanNotificationInfo info1 = new MBeanNotificationInfo(notificationTypes, name, description); + + return new MBeanNotificationInfo[] { info1 }; + } + +} // End of AMQQueueMBean class diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/BaseQueue.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/BaseQueue.java new file mode 100644 index 0000000000..05e0efd9a6 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/BaseQueue.java @@ -0,0 +1,42 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.queue; + +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.store.TransactionLogResource; +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; + +public interface BaseQueue extends TransactionLogResource +{ + public static interface PostEnqueueAction + { + public void onEnqueue(QueueEntry entry); + } + + void enqueue(ServerMessage message) throws AMQException; + void enqueue(ServerMessage message, PostEnqueueAction action) throws AMQException; + + boolean isDurable(); + + AMQShortString getNameShortString(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ConflationQueue.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ConflationQueue.java new file mode 100644 index 0000000000..b5293f51be --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ConflationQueue.java @@ -0,0 +1,47 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.queue; + +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.Map; + +public class ConflationQueue extends SimpleAMQQueue +{ + protected ConflationQueue(String name, + boolean durable, + String owner, + boolean autoDelete, + boolean exclusive, + VirtualHost virtualHost, + Map<String, Object> args, + String conflationKey) + { + super(name, durable, owner, autoDelete, exclusive, virtualHost, new ConflationQueueList.Factory(conflationKey), args); + } + + public String getConflationKey() + { + return ((ConflationQueueList) _entries).getConflationKey(); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ConflationQueueList.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ConflationQueueList.java new file mode 100644 index 0000000000..2c1883e763 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/ConflationQueueList.java @@ -0,0 +1,167 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.queue; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.txn.AutoCommitTransaction; +import org.apache.qpid.server.txn.ServerTransaction; + +public class ConflationQueueList extends SimpleQueueEntryList +{ + + private final String _conflationKey; + private final ConcurrentHashMap<Object, AtomicReference<QueueEntry>> _latestValuesMap = + new ConcurrentHashMap<Object, AtomicReference<QueueEntry>>(); + + public ConflationQueueList(AMQQueue queue, String conflationKey) + { + super(queue); + _conflationKey = conflationKey; + } + + public String getConflationKey() + { + return _conflationKey; + } + + @Override + protected ConflationQueueEntry createQueueEntry(ServerMessage message) + { + return new ConflationQueueEntry(this, message); + } + + + @Override + public QueueEntry add(final ServerMessage message) + { + ConflationQueueEntry entry = (ConflationQueueEntry) (super.add(message)); + AtomicReference<QueueEntry> latestValueReference = null; + + Object value = message.getMessageHeader().getHeader(_conflationKey); + if(value != null) + { + latestValueReference = _latestValuesMap.get(value); + if(latestValueReference == null) + { + _latestValuesMap.putIfAbsent(value, new AtomicReference<QueueEntry>(entry)); + latestValueReference = _latestValuesMap.get(value); + } + QueueEntry oldEntry; + + do + { + oldEntry = latestValueReference.get(); + } + while(oldEntry.compareTo(entry) < 0 && !latestValueReference.compareAndSet(oldEntry, entry)); + + if(oldEntry.compareTo(entry) < 0) + { + // We replaced some other entry to become the newest value + if(oldEntry.acquire()) + { + discardEntry(oldEntry); + } + } + else if (oldEntry.compareTo(entry) > 0) + { + // A newer entry came along + discardEntry(entry); + + } + } + + entry.setLatestValueReference(latestValueReference); + return entry; + } + + private void discardEntry(final QueueEntry entry) + { + if(entry.acquire()) + { + ServerTransaction txn = new AutoCommitTransaction(getQueue().getVirtualHost().getTransactionLog()); + txn.dequeue(entry.getQueue(),entry.getMessage(), + new ServerTransaction.Action() + { + public void postCommit() + { + entry.discard(); + } + + public void onRollback() + { + + } + }); + } + } + + private final class ConflationQueueEntry extends QueueEntryImpl + { + + + private AtomicReference<QueueEntry> _latestValueReference; + + public ConflationQueueEntry(SimpleQueueEntryList queueEntryList, ServerMessage message) + { + super(queueEntryList, message); + } + + + public void release() + { + super.release(); + + if(_latestValueReference != null) + { + if(_latestValueReference.get() != this) + { + discardEntry(this); + } + } + + } + + public void setLatestValueReference(final AtomicReference<QueueEntry> latestValueReference) + { + _latestValueReference = latestValueReference; + } + } + + static class Factory implements QueueEntryListFactory + { + private final String _conflationKey; + + Factory(String conflationKey) + { + _conflationKey = conflationKey; + } + + public QueueEntryList createQueueEntryList(AMQQueue queue) + { + return new ConflationQueueList(queue, _conflationKey); + } + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java new file mode 100644 index 0000000000..d76487073d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/DefaultQueueRegistry.java @@ -0,0 +1,75 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.queue; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class DefaultQueueRegistry implements QueueRegistry +{ + private ConcurrentMap<AMQShortString, AMQQueue> _queueMap = new ConcurrentHashMap<AMQShortString, AMQQueue>(); + + private final VirtualHost _virtualHost; + + public DefaultQueueRegistry(VirtualHost virtualHost) + { + _virtualHost = virtualHost; + } + + public VirtualHost getVirtualHost() + { + return _virtualHost; + } + + public void registerQueue(AMQQueue queue) + { + _queueMap.put(queue.getNameShortString(), queue); + } + + public void unregisterQueue(AMQShortString name) + { + _queueMap.remove(name); + } + + public AMQQueue getQueue(AMQShortString name) + { + return _queueMap.get(name); + } + + public Collection<AMQShortString> getQueueNames() + { + return _queueMap.keySet(); + } + + public Collection<AMQQueue> getQueues() + { + return _queueMap.values(); + } + + public AMQQueue getQueue(String queue) + { + return getQueue(new AMQShortString(queue)); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/FailedDequeueException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/FailedDequeueException.java new file mode 100644 index 0000000000..6466e81dd2 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/FailedDequeueException.java @@ -0,0 +1,50 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; + +/** + * Signals that the dequeue of a message from a queue failed. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Indicates the a message could not be dequeued from a queue. + * <tr><td> + * </table> + * + * @todo Not an AMQP exception as no status code. + * + * @todo Happens as a consequence of a message store failure, or reference counting error. Both of which migh become + * runtime exceptions, as unrecoverable conditions? In which case this one might be dropped too. + */ +public class FailedDequeueException extends AMQException +{ + public FailedDequeueException(String queue) + { + super("Failed to dequeue message from " + queue); + } + + public FailedDequeueException(String queue, AMQException e) + { + super("Failed to dequeue message from " + queue, e); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/Filterable.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/Filterable.java new file mode 100644 index 0000000000..eaa3992e98 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/Filterable.java @@ -0,0 +1,34 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.queue; + +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.message.AMQMessageHeader; + +public interface Filterable +{ + AMQMessageHeader getMessageHeader(); + + boolean isPersistent(); + + boolean isRedelivered(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/InboundMessageAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/InboundMessageAdapter.java new file mode 100755 index 0000000000..77da08d8c4 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/InboundMessageAdapter.java @@ -0,0 +1,71 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.queue; + +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.message.AMQMessageHeader; + +class InboundMessageAdapter implements InboundMessage +{ + + private QueueEntry _entry; + + InboundMessageAdapter() + { + } + + InboundMessageAdapter(QueueEntry entry) + { + _entry = entry; + } + + public void setEntry(QueueEntry entry) + { + _entry = entry; + } + + + public String getRoutingKey() + { + return _entry.getMessage().getRoutingKey(); + } + + public AMQMessageHeader getMessageHeader() + { + return _entry.getMessageHeader(); + } + + public boolean isPersistent() + { + return _entry.isPersistent(); + } + + public boolean isRedelivered() + { + return _entry.isRedelivered(); + } + + public long getSize() + { + return _entry.getSize(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/IncomingMessage.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/IncomingMessage.java new file mode 100644 index 0000000000..3e3288404f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/IncomingMessage.java @@ -0,0 +1,300 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.queue; + +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.framing.abstraction.ContentChunk; +import org.apache.qpid.framing.ContentHeaderBody; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.EnqueableMessage; +import org.apache.qpid.server.message.MessageContentSource; +import org.apache.qpid.server.message.MessageMetaData; +import org.apache.qpid.AMQException; +import org.apache.log4j.Logger; + +import java.util.ArrayList; +import java.util.List; +import java.nio.ByteBuffer; + +public class IncomingMessage implements Filterable, InboundMessage, EnqueableMessage, MessageContentSource +{ + + /** Used for debugging purposes. */ + private static final Logger _logger = Logger.getLogger(IncomingMessage.class); + + private static final boolean SYNCHED_CLOCKS = + ApplicationRegistry.getInstance().getConfiguration().getSynchedClocks(); + + private final MessagePublishInfo _messagePublishInfo; + private ContentHeaderBody _contentHeaderBody; + + + /** + * Keeps a track of how many bytes we have received in body frames + */ + private long _bodyLengthReceived = 0; + + /** + * This is stored during routing, to know the queues to which this message should immediately be + * delivered. It is <b>cleared after delivery has been attempted</b>. Any persistent record of destinations is done + * by the message handle. + */ + private ArrayList<? extends BaseQueue> _destinationQueues; + + private long _expiration; + + private Exchange _exchange; + + + private int _receivedChunkCount = 0; + private List<ContentChunk> _contentChunks = new ArrayList<ContentChunk>(); + + // we keep both the original meta data object and the store reference to it just in case the + // store would otherwise flow it to disk + + private MessageMetaData _messageMetaData; + + private StoredMessage<MessageMetaData> _storedMessageHandle; + + + public IncomingMessage( + final MessagePublishInfo info + ) + { + _messagePublishInfo = info; + } + + public void setContentHeaderBody(final ContentHeaderBody contentHeaderBody) throws AMQException + { + _contentHeaderBody = contentHeaderBody; + } + + public void setExpiration() + { + long expiration = + ((BasicContentHeaderProperties) _contentHeaderBody.getProperties()).getExpiration(); + long timestamp = + ((BasicContentHeaderProperties) _contentHeaderBody.getProperties()).getTimestamp(); + + if (SYNCHED_CLOCKS) + { + _expiration = expiration; + } + else + { + // Update TTL to be in broker time. + if (expiration != 0L) + { + if (timestamp != 0L) + { + // todo perhaps use arrival time + long diff = (System.currentTimeMillis() - timestamp); + + if ((diff > 1000L) || (diff < 1000L)) + { + _expiration = expiration + diff; + } + } + } + } + + } + + public MessageMetaData headersReceived() + { + _messageMetaData = new MessageMetaData(_messagePublishInfo, _contentHeaderBody, 0); + return _messageMetaData; + } + + + public ArrayList<? extends BaseQueue> getDestinationQueues() + { + return _destinationQueues; + } + + public int addContentBodyFrame(final ContentChunk contentChunk) + throws AMQException + { + _storedMessageHandle.addContent((int)_bodyLengthReceived, contentChunk.getData().buf()); + _bodyLengthReceived += contentChunk.getSize(); + _contentChunks.add(contentChunk); + + + + return _receivedChunkCount++; + } + + public boolean allContentReceived() + { + return (_bodyLengthReceived == getContentHeader().bodySize); + } + + public AMQShortString getExchange() + { + return _messagePublishInfo.getExchange(); + } + + public String getRoutingKey() + { + return _messagePublishInfo.getRoutingKey() == null ? null : _messagePublishInfo.getRoutingKey().toString(); + } + + public String getBinding() + { + return _messagePublishInfo.getRoutingKey() == null ? null : _messagePublishInfo.getRoutingKey().toString(); + } + + + public boolean isMandatory() + { + return _messagePublishInfo.isMandatory(); + } + + + public boolean isImmediate() + { + return _messagePublishInfo.isImmediate(); + } + + public ContentHeaderBody getContentHeader() + { + return _contentHeaderBody; + } + + + public AMQMessageHeader getMessageHeader() + { + return _messageMetaData.getMessageHeader(); + } + + public boolean isPersistent() + { + return getContentHeader().getProperties() instanceof BasicContentHeaderProperties && + ((BasicContentHeaderProperties) getContentHeader().getProperties()).getDeliveryMode() == + BasicContentHeaderProperties.PERSISTENT; + } + + public boolean isRedelivered() + { + return false; + } + + + public long getSize() + { + return getContentHeader().bodySize; + } + + public Long getMessageNumber() + { + return _storedMessageHandle.getMessageNumber(); + } + + public void setExchange(final Exchange e) + { + _exchange = e; + } + + public void route() + { + enqueue(_exchange.route(this)); + + } + + public void enqueue(final ArrayList<? extends BaseQueue> queues) + { + _destinationQueues = queues; + } + + public MessagePublishInfo getMessagePublishInfo() + { + return _messagePublishInfo; + } + + public long getExpiration() + { + return _expiration; + } + + public int getReceivedChunkCount() + { + return _receivedChunkCount; + } + + + public int getBodyCount() throws AMQException + { + return _contentChunks.size(); + } + + public ContentChunk getContentChunk(int index) throws IllegalArgumentException, AMQException + { + return _contentChunks.get(index); + } + + + public int getContent(ByteBuffer buf, int offset) + { + int pos = 0; + int written = 0; + for(ContentChunk cb : _contentChunks) + { + ByteBuffer data = cb.getData().buf(); + if(offset+written >= pos && offset < pos + data.limit()) + { + ByteBuffer src = data.duplicate(); + src.position(offset+written - pos); + src = src.slice(); + + if(buf.remaining() < src.limit()) + { + src.limit(buf.remaining()); + } + int count = src.limit(); + buf.put(src); + written += count; + if(buf.remaining() == 0) + { + break; + } + } + pos+=data.limit(); + } + return written; + + } + + public void setStoredMessage(StoredMessage<MessageMetaData> storedMessageHandle) + { + _storedMessageHandle = storedMessageHandle; + } + + public StoredMessage<MessageMetaData> getStoredMessage() + { + return _storedMessageHandle; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageCleanupException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageCleanupException.java new file mode 100644 index 0000000000..090096d3c3 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/MessageCleanupException.java @@ -0,0 +1,52 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; + +/** + * MessageCleanupException represents the failure to perform reference counting on messages correctly. This should not + * happen, but there may be programming errors giving race conditions that cause the reference counting to go wrong. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Signals that the reference count of a message has gone below zero. + * <tr><td> Indicates that a message store has lost a message which is still referenced. + * </table> + * + * @todo Not an AMQP exception as no status code. + * + * @todo The race conditions leading to this error should be cleaned up, and a runtime exception used instead. If the + * message store loses messages, then something is seriously wrong and it would be sensible to terminate the + * broker. This may be disguising out of memory errors. + */ +public class MessageCleanupException extends AMQException +{ + public MessageCleanupException(long messageId, AMQException e) + { + super("Failed to cleanup message with id " + messageId, e); + } + + public MessageCleanupException(String message) + { + super(message); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/NotificationCheck.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/NotificationCheck.java new file mode 100644 index 0000000000..d1fb0f3fe6 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/NotificationCheck.java @@ -0,0 +1,132 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.qpid.server.queue;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.server.message.ServerMessage;
+
+public enum NotificationCheck
+{
+
+ MESSAGE_COUNT_ALERT
+ {
+ boolean notifyIfNecessary(ServerMessage msg, AMQQueue queue, QueueNotificationListener listener)
+ {
+ int msgCount;
+ final long maximumMessageCount = queue.getMaximumMessageCount();
+ if (maximumMessageCount!= 0 && (msgCount = queue.getMessageCount()) >= maximumMessageCount)
+ {
+ listener.notifyClients(this, queue, msgCount + ": Maximum count on queue threshold ("+ maximumMessageCount +") breached.");
+ return true;
+ }
+ return false;
+ }
+ },
+ MESSAGE_SIZE_ALERT(true)
+ {
+ boolean notifyIfNecessary(ServerMessage msg, AMQQueue queue, QueueNotificationListener listener)
+ {
+ final long maximumMessageSize = queue.getMaximumMessageSize();
+ if(maximumMessageSize != 0)
+ {
+ // Check for threshold message size
+ long messageSize;
+ messageSize = (msg == null) ? 0 : msg.getSize();
+
+
+ if (messageSize >= maximumMessageSize)
+ {
+ listener.notifyClients(this, queue, messageSize + "b : Maximum message size threshold ("+ maximumMessageSize +") breached. [Message ID=" + msg.getMessageNumber() + "]");
+ return true;
+ }
+ }
+ return false;
+ }
+
+ },
+ QUEUE_DEPTH_ALERT
+ {
+ boolean notifyIfNecessary(ServerMessage msg, AMQQueue queue, QueueNotificationListener listener)
+ {
+ // Check for threshold queue depth in bytes
+ final long maximumQueueDepth = queue.getMaximumQueueDepth();
+
+ if(maximumQueueDepth != 0)
+ {
+ final long queueDepth = queue.getQueueDepth();
+
+ if (queueDepth >= maximumQueueDepth)
+ {
+ listener.notifyClients(this, queue, (queueDepth>>10) + "Kb : Maximum queue depth threshold ("+(maximumQueueDepth>>10)+"Kb) breached.");
+ return true;
+ }
+ }
+ return false;
+ }
+
+ },
+ MESSAGE_AGE_ALERT
+ {
+ boolean notifyIfNecessary(ServerMessage msg, AMQQueue queue, QueueNotificationListener listener)
+ {
+
+ final long maxMessageAge = queue.getMaximumMessageAge();
+ if(maxMessageAge != 0)
+ {
+ final long currentTime = System.currentTimeMillis();
+ final long thresholdTime = currentTime - maxMessageAge;
+ final long firstArrivalTime = queue.getOldestMessageArrivalTime();
+
+ if(firstArrivalTime < thresholdTime)
+ {
+ long oldestAge = currentTime - firstArrivalTime;
+ listener.notifyClients(this, queue, (oldestAge/1000) + "s : Maximum age on queue threshold ("+(maxMessageAge /1000)+"s) breached.");
+
+ return true;
+ }
+ }
+ return false;
+
+ }
+
+ }
+ ;
+
+ private final boolean _messageSpecific;
+
+ NotificationCheck()
+ {
+ this(false);
+ }
+
+ NotificationCheck(boolean messageSpecific)
+ {
+ _messageSpecific = messageSpecific;
+ }
+
+ public boolean isMessageSpecific()
+ {
+ return _messageSpecific;
+ }
+
+ abstract boolean notifyIfNecessary(ServerMessage msg, AMQQueue queue, QueueNotificationListener listener);
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/PriorityQueueList.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/PriorityQueueList.java new file mode 100644 index 0000000000..0c6b84d2b6 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/PriorityQueueList.java @@ -0,0 +1,162 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.queue; + +import org.apache.qpid.framing.CommonContentHeaderProperties; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.message.ServerMessage; + +public class PriorityQueueList implements QueueEntryList +{ + private final AMQQueue _queue; + private final QueueEntryList[] _priorityLists; + private final int _priorities; + private final int _priorityOffset; + + public PriorityQueueList(AMQQueue queue, int priorities) + { + _queue = queue; + _priorityLists = new QueueEntryList[priorities]; + _priorities = priorities; + _priorityOffset = 5-((priorities + 1)/2); + for(int i = 0; i < priorities; i++) + { + _priorityLists[i] = new SimpleQueueEntryList(queue); + } + } + + public int getPriorities() + { + return _priorities; + } + + public AMQQueue getQueue() + { + return _queue; + } + + public QueueEntry add(ServerMessage message) + { + int index = message.getMessageHeader().getPriority() - _priorityOffset; + if(index >= _priorities) + { + index = _priorities-1; + } + else if(index < 0) + { + index = 0; + } + return _priorityLists[index].add(message); + + } + + public QueueEntry next(QueueEntry node) + { + QueueEntryImpl nodeImpl = (QueueEntryImpl)node; + QueueEntry next = nodeImpl.getNext(); + + if(next == null) + { + QueueEntryList nodeEntryList = nodeImpl.getQueueEntryList(); + int index; + for(index = _priorityLists.length-1; _priorityLists[index] != nodeEntryList; index--); + + while(next == null && index != 0) + { + index--; + next = ((QueueEntryImpl)_priorityLists[index].getHead()).getNext(); + } + + } + return next; + } + + private final class PriorityQueueEntryListIterator implements QueueEntryIterator + { + private final QueueEntryIterator[] _iterators = new QueueEntryIterator[ _priorityLists.length ]; + private QueueEntry _lastNode; + + PriorityQueueEntryListIterator() + { + for(int i = 0; i < _priorityLists.length; i++) + { + _iterators[i] = _priorityLists[i].iterator(); + } + _lastNode = _iterators[_iterators.length - 1].getNode(); + } + + + public boolean atTail() + { + for(int i = 0; i < _iterators.length; i++) + { + if(!_iterators[i].atTail()) + { + return false; + } + } + return true; + } + + public QueueEntry getNode() + { + return _lastNode; + } + + public boolean advance() + { + for(int i = _iterators.length-1; i >= 0; i--) + { + if(_iterators[i].advance()) + { + _lastNode = _iterators[i].getNode(); + return true; + } + } + return false; + } + } + + public QueueEntryIterator iterator() + { + return new PriorityQueueEntryListIterator(); + } + + public QueueEntry getHead() + { + return _priorityLists[_priorities-1].getHead(); + } + + static class Factory implements QueueEntryListFactory + { + private final int _priorities; + + Factory(int priorities) + { + _priorities = priorities; + } + + public QueueEntryList createQueueEntryList(AMQQueue queue) + { + return new PriorityQueueList(queue, _priorities); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueContext.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueContext.java new file mode 100755 index 0000000000..825a85a89c --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueContext.java @@ -0,0 +1,49 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.queue; + +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + +final class QueueContext implements AMQQueue.Context +{ + volatile QueueEntry _lastSeenEntry; + volatile QueueEntry _releasedEntry; + + static final AtomicReferenceFieldUpdater<QueueContext, QueueEntry> + _lastSeenUpdater = + AtomicReferenceFieldUpdater.newUpdater + (QueueContext.class, QueueEntry.class, "_lastSeenEntry"); + static final AtomicReferenceFieldUpdater<QueueContext, QueueEntry> + _releasedUpdater = + AtomicReferenceFieldUpdater.newUpdater + (QueueContext.class, QueueEntry.class, "_releasedEntry"); + + public QueueContext(QueueEntry head) + { + _lastSeenEntry = head; + } + + public QueueEntry getLastSeenEntry() + { + return _lastSeenEntry; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntry.java new file mode 100644 index 0000000000..79ede2694e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntry.java @@ -0,0 +1,210 @@ +package org.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.message.ServerMessage; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ +public interface QueueEntry extends Comparable<QueueEntry>, Filterable +{ + + + + public static enum State + { + AVAILABLE, + ACQUIRED, + EXPIRED, + DEQUEUED, + DELETED; + + + } + + public static interface StateChangeListener + { + public void stateChanged(QueueEntry entry, State oldSate, State newState); + } + + public abstract class EntryState + { + private EntryState() + { + } + + public abstract State getState(); + } + + + public final class AvailableState extends EntryState + { + + public State getState() + { + return State.AVAILABLE; + } + } + + + public final class DequeuedState extends EntryState + { + + public State getState() + { + return State.DEQUEUED; + } + } + + + public final class DeletedState extends EntryState + { + + public State getState() + { + return State.DELETED; + } + } + + public final class ExpiredState extends EntryState + { + + public State getState() + { + return State.EXPIRED; + } + } + + + public final class NonSubscriptionAcquiredState extends EntryState + { + public State getState() + { + return State.ACQUIRED; + } + } + + public final class SubscriptionAcquiredState extends EntryState + { + private final Subscription _subscription; + + public SubscriptionAcquiredState(Subscription subscription) + { + _subscription = subscription; + } + + + public State getState() + { + return State.ACQUIRED; + } + + public Subscription getSubscription() + { + return _subscription; + } + } + + public final class SubscriptionAssignedState extends EntryState + { + private final Subscription _subscription; + + public SubscriptionAssignedState(Subscription subscription) + { + _subscription = subscription; + } + + + public State getState() + { + return State.AVAILABLE; + } + + public Subscription getSubscription() + { + return _subscription; + } + } + + + final static EntryState AVAILABLE_STATE = new AvailableState(); + final static EntryState DELETED_STATE = new DeletedState(); + final static EntryState DEQUEUED_STATE = new DequeuedState(); + final static EntryState EXPIRED_STATE = new ExpiredState(); + final static EntryState NON_SUBSCRIPTION_ACQUIRED_STATE = new NonSubscriptionAcquiredState(); + + + + + AMQQueue getQueue(); + + ServerMessage getMessage(); + + long getSize(); + + boolean getDeliveredToConsumer(); + + boolean expired() throws AMQException; + + boolean isAvailable(); + + boolean isAcquired(); + + boolean acquire(); + boolean acquire(Subscription sub); + + boolean delete(); + boolean isDeleted(); + + boolean acquiredBySubscription(); + boolean isAcquiredBy(Subscription subscription); + + void release(); + boolean releaseButRetain(); + + + boolean immediateAndNotDelivered(); + + void setRedelivered(); + + boolean isRedelivered(); + + Subscription getDeliveredSubscription(); + + void reject(); + + void reject(Subscription subscription); + + boolean isRejectedBy(Subscription subscription); + + void dequeue(); + + void dispose(); + + void discard(); + + void routeToAlternate(); + + boolean isQueueDeleted(); + + void addStateChangeListener(StateChangeListener listener); + boolean removeStateChangeListener(StateChangeListener listener); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryImpl.java new file mode 100644 index 0000000000..809ba3277e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryImpl.java @@ -0,0 +1,550 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.queue; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.message.AMQMessageHeader; +import org.apache.qpid.server.message.MessageReference; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.txn.AutoCommitTransaction; +import org.apache.qpid.server.txn.ServerTransaction; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; + + +public class QueueEntryImpl implements QueueEntry +{ + + /** + * Used for debugging purposes. + */ + private static final Logger _log = Logger.getLogger(QueueEntryImpl.class); + + private final SimpleQueueEntryList _queueEntryList; + + private MessageReference _message; + + private Set<Subscription> _rejectedBy = null; + + private volatile EntryState _state = AVAILABLE_STATE; + + private static final + AtomicReferenceFieldUpdater<QueueEntryImpl, EntryState> + _stateUpdater = + AtomicReferenceFieldUpdater.newUpdater + (QueueEntryImpl.class, EntryState.class, "_state"); + + + private volatile Set<StateChangeListener> _stateChangeListeners; + + private static final + AtomicReferenceFieldUpdater<QueueEntryImpl, Set> + _listenersUpdater = + AtomicReferenceFieldUpdater.newUpdater + (QueueEntryImpl.class, Set.class, "_stateChangeListeners"); + + + private static final + AtomicLongFieldUpdater<QueueEntryImpl> + _entryIdUpdater = + AtomicLongFieldUpdater.newUpdater + (QueueEntryImpl.class, "_entryId"); + + + private volatile long _entryId; + + volatile QueueEntryImpl _next; + + private static final int DELIVERED_TO_CONSUMER = 1; + private static final int REDELIVERED = 2; + + private volatile int _deliveryState; + + + QueueEntryImpl(SimpleQueueEntryList queueEntryList) + { + this(queueEntryList,null,Long.MIN_VALUE); + _state = DELETED_STATE; + } + + + public QueueEntryImpl(SimpleQueueEntryList queueEntryList, ServerMessage message, final long entryId) + { + _queueEntryList = queueEntryList; + + _message = message == null ? null : message.newReference(); + + _entryIdUpdater.set(this, entryId); + } + + public QueueEntryImpl(SimpleQueueEntryList queueEntryList, ServerMessage message) + { + _queueEntryList = queueEntryList; + _message = message == null ? null : message.newReference(); + } + + protected void setEntryId(long entryId) + { + _entryIdUpdater.set(this, entryId); + } + + protected long getEntryId() + { + return _entryId; + } + + public AMQQueue getQueue() + { + return _queueEntryList.getQueue(); + } + + public ServerMessage getMessage() + { + return _message == null ? null : _message.getMessage(); + } + + public long getSize() + { + return getMessage() == null ? 0 : getMessage().getSize(); + } + + public boolean getDeliveredToConsumer() + { + return (_deliveryState & DELIVERED_TO_CONSUMER) != 0; + } + + public boolean expired() throws AMQException + { + ServerMessage message = getMessage(); + if(message != null) + { + long expiration = message.getExpiration(); + if (expiration != 0L) + { + long now = System.currentTimeMillis(); + + return (now > expiration); + } + } + return false; + + } + + public boolean isAvailable() + { + return _state == AVAILABLE_STATE; + } + + public boolean isAcquired() + { + return _state.getState() == State.ACQUIRED; + } + + public boolean acquire() + { + return acquire(NON_SUBSCRIPTION_ACQUIRED_STATE); + } + + private boolean acquire(final EntryState state) + { + boolean acquired = _stateUpdater.compareAndSet(this,AVAILABLE_STATE, state); + + // deal with the case where the node has been assigned to a given subscription already + // including the case that the node is assigned to a closed subscription + if(!acquired) + { + if(state != NON_SUBSCRIPTION_ACQUIRED_STATE) + { + EntryState currentState = _state; + if(currentState.getState() == State.AVAILABLE + && ((currentState == AVAILABLE_STATE) + || (((SubscriptionAcquiredState)state).getSubscription() == + ((SubscriptionAssignedState)currentState).getSubscription()) + || ((SubscriptionAssignedState)currentState).getSubscription().isClosed() )) + { + acquired = _stateUpdater.compareAndSet(this,currentState, state); + } + } + } + if(acquired && _stateChangeListeners != null) + { + notifyStateChange(State.AVAILABLE, State.ACQUIRED); + } + + return acquired; + } + + public boolean acquire(Subscription sub) + { + final boolean acquired = acquire(sub.getOwningState()); + if(acquired) + { + _deliveryState |= DELIVERED_TO_CONSUMER; + } + return acquired; + } + + public boolean acquiredBySubscription() + { + + return (_state instanceof SubscriptionAcquiredState); + } + + public boolean isAcquiredBy(Subscription subscription) + { + EntryState state = _state; + return state instanceof SubscriptionAcquiredState + && ((SubscriptionAcquiredState)state).getSubscription() == subscription; + } + + public void release() + { + EntryState state = _state; + + if((state.getState() == State.ACQUIRED) &&_stateUpdater.compareAndSet(this, state, AVAILABLE_STATE)) + { + if(state instanceof SubscriptionAcquiredState) + { + getQueue().decrementUnackedMsgCount(); + } + + if(!getQueue().isDeleted()) + { + getQueue().requeue(this); + if(_stateChangeListeners != null) + { + notifyStateChange(QueueEntry.State.ACQUIRED, QueueEntry.State.AVAILABLE); + } + + } + else if(acquire()) + { + routeToAlternate(); + } + } + } + + public boolean releaseButRetain() + { + EntryState state = _state; + + boolean stateUpdated = false; + + if(state instanceof SubscriptionAcquiredState) + { + Subscription sub = ((SubscriptionAcquiredState) state).getSubscription(); + if(_stateUpdater.compareAndSet(this, state, sub.getAssignedState())) + { + System.err.println("Message released (and retained)" + getMessage().getMessageNumber()); + getQueue().requeue(this); + if(_stateChangeListeners != null) + { + notifyStateChange(QueueEntry.State.ACQUIRED, QueueEntry.State.AVAILABLE); + } + stateUpdated = true; + } + } + + return stateUpdated; + + } + + public boolean immediateAndNotDelivered() + { + return !getDeliveredToConsumer() && isImmediate(); + } + + private boolean isImmediate() + { + final ServerMessage message = getMessage(); + return message != null && message.isImmediate(); + } + + public void setRedelivered() + { + _deliveryState |= REDELIVERED; + } + + public AMQMessageHeader getMessageHeader() + { + final ServerMessage message = getMessage(); + return message == null ? null : message.getMessageHeader(); + } + + public boolean isPersistent() + { + final ServerMessage message = getMessage(); + return message != null && message.isPersistent(); + } + + public boolean isRedelivered() + { + return (_deliveryState & REDELIVERED) != 0; + } + + public Subscription getDeliveredSubscription() + { + EntryState state = _state; + if (state instanceof SubscriptionAcquiredState) + { + return ((SubscriptionAcquiredState) state).getSubscription(); + } + else + { + return null; + } + + } + + public void reject() + { + reject(getDeliveredSubscription()); + } + + public void reject(Subscription subscription) + { + if (subscription != null) + { + if (_rejectedBy == null) + { + _rejectedBy = new HashSet<Subscription>(); + } + + _rejectedBy.add(subscription); + } + else + { + _log.warn("Requesting rejection by null subscriber:" + this); + } + } + + public boolean isRejectedBy(Subscription subscription) + { + + if (_rejectedBy != null) // We have subscriptions that rejected this message + { + return _rejectedBy.contains(subscription); + } + else // This messasge hasn't been rejected yet. + { + return false; + } + } + + public void dequeue() + { + EntryState state = _state; + + if((state.getState() == State.ACQUIRED) &&_stateUpdater.compareAndSet(this, state, DEQUEUED_STATE)) + { + Subscription s = null; + if (state instanceof SubscriptionAcquiredState) + { + getQueue().decrementUnackedMsgCount(); + s = ((SubscriptionAcquiredState) state).getSubscription(); + s.onDequeue(this); + } + + getQueue().dequeue(this,s); + if(_stateChangeListeners != null) + { + notifyStateChange(state.getState() , QueueEntry.State.DEQUEUED); + } + + } + + } + + private void notifyStateChange(final State oldState, final State newState) + { + for(StateChangeListener l : _stateChangeListeners) + { + l.stateChanged(this, oldState, newState); + } + } + + public void dispose() + { + if(delete()) + { + _message.release(); + } + } + + public void discard() + { + //if the queue is null then the message is waiting to be acked, but has been removed. + if (getQueue() != null) + { + dequeue(); + } + + dispose(); + } + + public void routeToAlternate() + { + final AMQQueue currentQueue = getQueue(); + Exchange alternateExchange = currentQueue.getAlternateExchange(); + + if(alternateExchange != null) + { + final List<? extends BaseQueue> rerouteQueues = alternateExchange.route(new InboundMessageAdapter(this)); + final ServerMessage message = getMessage(); + if(rerouteQueues != null && rerouteQueues.size() != 0) + { + ServerTransaction txn = new AutoCommitTransaction(getQueue().getVirtualHost().getTransactionLog()); + + txn.enqueue(rerouteQueues, message, new ServerTransaction.Action() { + public void postCommit() + { + try + { + for(BaseQueue queue : rerouteQueues) + { + queue.enqueue(message); + } + } + catch (AMQException e) + { + throw new RuntimeException(e); + } + } + + public void onRollback() + { + + } + }); + txn.dequeue(currentQueue,message, + new ServerTransaction.Action() + { + public void postCommit() + { + discard(); + } + + public void onRollback() + { + + } + }); + } + } + } + + public boolean isQueueDeleted() + { + return getQueue().isDeleted(); + } + + public void addStateChangeListener(StateChangeListener listener) + { + Set<StateChangeListener> listeners = _stateChangeListeners; + if(listeners == null) + { + _listenersUpdater.compareAndSet(this, null, new CopyOnWriteArraySet<StateChangeListener>()); + listeners = _stateChangeListeners; + } + + listeners.add(listener); + } + + public boolean removeStateChangeListener(StateChangeListener listener) + { + Set<StateChangeListener> listeners = _stateChangeListeners; + if(listeners != null) + { + return listeners.remove(listener); + } + + return false; + } + + + public int compareTo(final QueueEntry o) + { + QueueEntryImpl other = (QueueEntryImpl)o; + return getEntryId() > other.getEntryId() ? 1 : getEntryId() < other.getEntryId() ? -1 : 0; + } + + public QueueEntryImpl getNext() + { + + QueueEntryImpl next = nextNode(); + while(next != null && next.isDeleted()) + { + + final QueueEntryImpl newNext = next.nextNode(); + if(newNext != null) + { + SimpleQueueEntryList._nextUpdater.compareAndSet(this,next, newNext); + next = nextNode(); + } + else + { + next = null; + } + + } + return next; + } + + QueueEntryImpl nextNode() + { + return _next; + } + + public boolean isDeleted() + { + return _state == DELETED_STATE; + } + + public boolean delete() + { + EntryState state = _state; + + if(state != DELETED_STATE && _stateUpdater.compareAndSet(this,state,DELETED_STATE)) + { + _queueEntryList.advanceHead(); + return true; + } + else + { + return false; + } + } + + public QueueEntryList getQueueEntryList() + { + return _queueEntryList; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryIterator.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryIterator.java new file mode 100644 index 0000000000..c5c115a2d1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryIterator.java @@ -0,0 +1,30 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.queue; + +public interface QueueEntryIterator +{ + boolean atTail(); + + QueueEntry getNode(); + + boolean advance(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryList.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryList.java new file mode 100644 index 0000000000..b4042ce02c --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryList.java @@ -0,0 +1,36 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.queue; + +import org.apache.qpid.server.message.ServerMessage; + +public interface QueueEntryList +{ + AMQQueue getQueue(); + + QueueEntry add(ServerMessage message); + + QueueEntry next(QueueEntry node); + + QueueEntryIterator iterator(); + + QueueEntry getHead(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryListFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryListFactory.java new file mode 100644 index 0000000000..4dbce45f67 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueEntryListFactory.java @@ -0,0 +1,26 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.queue; + +interface QueueEntryListFactory +{ + public QueueEntryList createQueueEntryList(AMQQueue queue); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueNotificationListener.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueNotificationListener.java new file mode 100644 index 0000000000..959ca03c80 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueNotificationListener.java @@ -0,0 +1,27 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.qpid.server.queue;
+
+
+public interface QueueNotificationListener
+{
+ void notifyClients(NotificationCheck notification, AMQQueue queue, String notificationMsg);
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueRegistry.java new file mode 100644 index 0000000000..a537e0c83f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueRegistry.java @@ -0,0 +1,44 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.queue; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.Collection; + +public interface QueueRegistry +{ + VirtualHost getVirtualHost(); + + void registerQueue(AMQQueue queue); + + void unregisterQueue(AMQShortString name); + + AMQQueue getQueue(AMQShortString name); + + Collection<AMQShortString> getQueueNames(); + + Collection<AMQQueue> getQueues(); + + AMQQueue getQueue(String queue); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueRunner.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueRunner.java new file mode 100644 index 0000000000..7e1d57e205 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/QueueRunner.java @@ -0,0 +1,84 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.queue; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.pool.ReadWriteRunnable; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.queue.QueueRunner; +import org.apache.qpid.server.queue.SimpleAMQQueue; + +/** + * QueueRunners are Runnables used to process a queue when requiring + * asynchronous message delivery to subscriptions, which is necessary + * when straight-through delivery of a message to a subscription isn't + * possible during the enqueue operation. + */ +public class QueueRunner implements ReadWriteRunnable +{ + private static final Logger _logger = Logger.getLogger(QueueRunner.class); + + private final String _name; + private final SimpleAMQQueue _queue; + + public QueueRunner(SimpleAMQQueue queue, long count) + { + _queue = queue; + _name = "QueueRunner-" + count + "-" + queue.getLogActor(); + } + + public void run() + { + String originalName = Thread.currentThread().getName(); + try + { + Thread.currentThread().setName(_name); + CurrentActor.set(_queue.getLogActor()); + + _queue.processQueue(this); + } + catch (AMQException e) + { + _logger.error("Exception during asynchronous delivery by " + _name, e); + } + finally + { + CurrentActor.remove(); + Thread.currentThread().setName(originalName); + } + } + + public boolean isRead() + { + return false; + } + + public boolean isWrite() + { + return true; + } + + public String toString() + { + return _name; + } +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleAMQQueue.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleAMQQueue.java new file mode 100644 index 0000000000..b02d03a1ad --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleAMQQueue.java @@ -0,0 +1,2233 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.queue; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQSecurityException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.pool.ReadWriteRunnable; +import org.apache.qpid.pool.ReferenceCountingExecutorService; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.configuration.ConfigStore; +import org.apache.qpid.server.configuration.ConfiguredObject; +import org.apache.qpid.server.configuration.QueueConfigType; +import org.apache.qpid.server.configuration.QueueConfiguration; +import org.apache.qpid.server.configuration.SessionConfig; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.QueueActor; +import org.apache.qpid.server.logging.messages.QueueMessages; +import org.apache.qpid.server.logging.subjects.QueueLogSubject; +import org.apache.qpid.server.management.ManagedObject; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.PrincipalHolder; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.subscription.SubscriptionList; +import org.apache.qpid.server.txn.AutoCommitTransaction; +import org.apache.qpid.server.txn.LocalTransaction; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import javax.management.JMException; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +public class SimpleAMQQueue implements AMQQueue, Subscription.StateListener +{ + private static final Logger _logger = Logger.getLogger(SimpleAMQQueue.class); + + + private final VirtualHost _virtualHost; + + private final AMQShortString _name; + private final String _resourceName; + + /** null means shared */ + private final AMQShortString _owner; + + private PrincipalHolder _prinicpalHolder; + + private boolean _exclusive = false; + private AMQSessionModel _exclusiveOwner; + + + private final boolean _durable; + + /** If true, this queue is deleted when the last subscriber is removed */ + private final boolean _autoDelete; + + private Exchange _alternateExchange; + + /** Used to track bindings to exchanges so that on deletion they can easily be cancelled. */ + + + + protected final QueueEntryList _entries; + + protected final SubscriptionList _subscriptionList = new SubscriptionList(this); + + private final AtomicReference<SubscriptionList.SubscriptionNode> _lastSubscriptionNode = new AtomicReference<SubscriptionList.SubscriptionNode>(_subscriptionList.getHead()); + + private volatile Subscription _exclusiveSubscriber; + + + + private final AtomicInteger _atomicQueueCount = new AtomicInteger(0); + + private final AtomicLong _atomicQueueSize = new AtomicLong(0L); + + private final AtomicInteger _activeSubscriberCount = new AtomicInteger(); + + private final AtomicLong _totalMessagesReceived = new AtomicLong(); + + private final AtomicLong _dequeueCount = new AtomicLong(); + private final AtomicLong _dequeueSize = new AtomicLong(); + private final AtomicLong _enqueueSize = new AtomicLong(); + private final AtomicLong _persistentMessageEnqueueSize = new AtomicLong(); + private final AtomicLong _persistentMessageDequeueSize = new AtomicLong(); + private final AtomicLong _persistentMessageEnqueueCount = new AtomicLong(); + private final AtomicLong _persistentMessageDequeueCount = new AtomicLong(); + private final AtomicInteger _counsumerCountHigh = new AtomicInteger(0); + private final AtomicLong _msgTxnEnqueues = new AtomicLong(0); + private final AtomicLong _byteTxnEnqueues = new AtomicLong(0); + private final AtomicLong _msgTxnDequeues = new AtomicLong(0); + private final AtomicLong _byteTxnDequeues = new AtomicLong(0); + private final AtomicLong _unackedMsgCount = new AtomicLong(0); + private final AtomicLong _unackedMsgCountHigh = new AtomicLong(0); + + private final AtomicInteger _bindingCountHigh = new AtomicInteger(); + + /** max allowed size(KB) of a single message */ + public long _maximumMessageSize = ApplicationRegistry.getInstance().getConfiguration().getMaximumMessageSize(); + + /** max allowed number of messages on a queue. */ + public long _maximumMessageCount = ApplicationRegistry.getInstance().getConfiguration().getMaximumMessageCount(); + + /** max queue depth for the queue */ + public long _maximumQueueDepth = ApplicationRegistry.getInstance().getConfiguration().getMaximumQueueDepth(); + + /** maximum message age before alerts occur */ + public long _maximumMessageAge = ApplicationRegistry.getInstance().getConfiguration().getMaximumMessageAge(); + + /** the minimum interval between sending out consecutive alerts of the same type */ + public long _minimumAlertRepeatGap = ApplicationRegistry.getInstance().getConfiguration().getMinimumAlertRepeatGap(); + + private long _capacity = ApplicationRegistry.getInstance().getConfiguration().getCapacity(); + + private long _flowResumeCapacity = ApplicationRegistry.getInstance().getConfiguration().getFlowResumeCapacity(); + + private final Set<NotificationCheck> _notificationChecks = EnumSet.noneOf(NotificationCheck.class); + + + static final int MAX_ASYNC_DELIVERIES = 10; + + + private final AtomicLong _stateChangeCount = new AtomicLong(Long.MIN_VALUE); + private AtomicReference<Runnable> _asynchronousRunner = new AtomicReference<Runnable>(null); + private final Executor _asyncDelivery; + private AtomicInteger _deliveredMessages = new AtomicInteger(); + private AtomicBoolean _stopped = new AtomicBoolean(false); + + private final ConcurrentMap<AMQChannel, Boolean> _blockedChannels = new ConcurrentHashMap<AMQChannel, Boolean>(); + + private final AtomicBoolean _deleted = new AtomicBoolean(false); + private final List<Task> _deleteTaskList = new CopyOnWriteArrayList<Task>(); + + + private LogSubject _logSubject; + private LogActor _logActor; + + private AMQQueueMBean _managedObject; + private static final String SUB_FLUSH_RUNNER = "SUB_FLUSH_RUNNER"; + private boolean _nolocal; + + private final AtomicBoolean _overfull = new AtomicBoolean(false); + private boolean _deleteOnNoConsumers; + private final CopyOnWriteArrayList<Binding> _bindings = new CopyOnWriteArrayList<Binding>(); + private UUID _id; + private final Map<String, Object> _arguments; + + //TODO : persist creation time + private long _createTime = System.currentTimeMillis(); + private ConfigurationPlugin _queueConfiguration; + + + + protected SimpleAMQQueue(AMQShortString name, boolean durable, AMQShortString owner, boolean autoDelete, boolean exclusive, VirtualHost virtualHost, Map<String,Object> arguments) + { + this(name, durable, owner, autoDelete, exclusive, virtualHost,new SimpleQueueEntryList.Factory(), arguments); + } + + public SimpleAMQQueue(String queueName, boolean durable, String owner, boolean autoDelete, boolean exclusive, VirtualHost virtualHost, Map<String, Object> arguments) + { + this(queueName, durable, owner, autoDelete, exclusive, virtualHost, new SimpleQueueEntryList.Factory(), arguments); + } + + public SimpleAMQQueue(String queueName, boolean durable, String owner, boolean autoDelete, boolean exclusive, VirtualHost virtualHost, QueueEntryListFactory entryListFactory, Map<String, Object> arguments) + { + this(queueName == null ? null : new AMQShortString(queueName), durable, owner == null ? null : new AMQShortString(owner), autoDelete, exclusive, virtualHost, entryListFactory, arguments); + } + + protected SimpleAMQQueue(AMQShortString name, + boolean durable, + AMQShortString owner, + boolean autoDelete, + boolean exclusive, + VirtualHost virtualHost, + QueueEntryListFactory entryListFactory, + Map<String,Object> arguments) + { + + if (name == null) + { + throw new IllegalArgumentException("Queue name must not be null"); + } + + if (virtualHost == null) + { + throw new IllegalArgumentException("Virtual Host must not be null"); + } + + _name = name; + _resourceName = String.valueOf(name); + _durable = durable; + _owner = owner; + _autoDelete = autoDelete; + _exclusive = exclusive; + _virtualHost = virtualHost; + _entries = entryListFactory.createQueueEntryList(this); + _arguments = arguments; + + _id = virtualHost.getConfigStore().createId(); + + _asyncDelivery = ReferenceCountingExecutorService.getInstance().acquireExecutorService(); + + _logSubject = new QueueLogSubject(this); + _logActor = new QueueActor(this, CurrentActor.get().getRootMessageLogger()); + + // Log the correct creation message + + // Extract the number of priorities for this Queue. + // Leave it as 0 if we are a SimpleQueueEntryList + int priorities = 0; + if (entryListFactory instanceof PriorityQueueList.Factory) + { + priorities = ((PriorityQueueList)_entries).getPriorities(); + } + + // Log the creation of this Queue. + // The priorities display is toggled on if we set priorities > 0 + CurrentActor.get().message(_logSubject, + QueueMessages.CREATED(String.valueOf(_owner), + priorities, + _owner != null, + autoDelete, + durable, !durable, + priorities > 0)); + + getConfigStore().addConfiguredObject(this); + + try + { + _managedObject = new AMQQueueMBean(this); + _managedObject.register(); + } + catch (JMException e) + { + _logger.error("AMQQueue MBean creation has failed ", e); + } + + resetNotifications(); + + } + + public void resetNotifications() + { + // This ensure that the notification checks for the configured alerts are created. + setMaximumMessageAge(_maximumMessageAge); + setMaximumMessageCount(_maximumMessageCount); + setMaximumMessageSize(_maximumMessageSize); + setMaximumQueueDepth(_maximumQueueDepth); + } + + // ------ Getters and Setters + + public void execute(ReadWriteRunnable runnable) + { + _asyncDelivery.execute(runnable); + } + + public AMQShortString getNameShortString() + { + return _name; + } + + public void setNoLocal(boolean nolocal) + { + _nolocal = nolocal; + } + + public UUID getId() + { + return _id; + } + + public QueueConfigType getConfigType() + { + return QueueConfigType.getInstance(); + } + + public ConfiguredObject getParent() + { + return getVirtualHost(); + } + + public boolean isDurable() + { + return _durable; + } + + public boolean isExclusive() + { + return _exclusive; + } + + public void setExclusive(boolean exclusive) throws AMQException + { + _exclusive = exclusive; + + if(isDurable()) + { + getVirtualHost().getDurableConfigurationStore().updateQueue(this); + } + } + + public Exchange getAlternateExchange() + { + return _alternateExchange; + } + + public void setAlternateExchange(Exchange exchange) + { + if(_alternateExchange != null) + { + _alternateExchange.removeReference(this); + } + if(exchange != null) + { + exchange.addReference(this); + } + _alternateExchange = exchange; + } + + public Map<String, Object> getArguments() + { + return _arguments; + } + + public boolean isAutoDelete() + { + return _autoDelete; + } + + public AMQShortString getOwner() + { + return _owner; + } + + public PrincipalHolder getPrincipalHolder() + { + return _prinicpalHolder; + } + + public void setPrincipalHolder(PrincipalHolder prinicpalHolder) + { + _prinicpalHolder = prinicpalHolder; + } + + + public VirtualHost getVirtualHost() + { + return _virtualHost; + } + + public String getName() + { + return getNameShortString().toString(); + } + + // ------ Manage Subscriptions + + public synchronized void registerSubscription(final Subscription subscription, final boolean exclusive) + throws AMQSecurityException, ExistingExclusiveSubscription, ExistingSubscriptionPreventsExclusive + { + // Access control + if (!getVirtualHost().getSecurityManager().authoriseConsume(this)) + { + throw new AMQSecurityException("Permission denied"); + } + + + if (hasExclusiveSubscriber()) + { + throw new ExistingExclusiveSubscription(); + } + + if (exclusive && !subscription.isTransient()) + { + if (getConsumerCount() != 0) + { + throw new ExistingSubscriptionPreventsExclusive(); + } + else + { + _exclusiveSubscriber = subscription; + } + } + + _activeSubscriberCount.incrementAndGet(); + subscription.setStateListener(this); + subscription.setQueueContext(new QueueContext(_entries.getHead())); + + if (!isDeleted()) + { + subscription.setQueue(this, exclusive); + if(_nolocal) + { + subscription.setNoLocal(_nolocal); + } + _subscriptionList.add(subscription); + + //Increment consumerCountHigh if necessary. (un)registerSubscription are both + //synchronized methods so we don't need additional synchronization here + if(_counsumerCountHigh.get() < getConsumerCount()) + { + _counsumerCountHigh.incrementAndGet(); + } + + if (isDeleted()) + { + subscription.queueDeleted(this); + } + } + else + { + // TODO + } + + deliverAsync(subscription); + + } + + public synchronized void unregisterSubscription(final Subscription subscription) throws AMQException + { + if (subscription == null) + { + throw new NullPointerException("subscription argument is null"); + } + + boolean removed = _subscriptionList.remove(subscription); + + if (removed) + { + subscription.close(); + // No longer can the queue have an exclusive consumer + setExclusiveSubscriber(null); + subscription.setQueueContext(null); + + // auto-delete queues must be deleted if there are no remaining subscribers + + if (_autoDelete && getDeleteOnNoConsumers() && !subscription.isTransient() && getConsumerCount() == 0 ) + { + if (_logger.isInfoEnabled()) + { + _logger.info("Auto-deleteing queue:" + this); + } + + delete(); + + // we need to manually fire the event to the removed subscription (which was the last one left for this + // queue. This is because the delete method uses the subscription set which has just been cleared + subscription.queueDeleted(this); + } + } + + } + + public boolean getDeleteOnNoConsumers() + { + return _deleteOnNoConsumers; + } + + public void setDeleteOnNoConsumers(boolean b) + { + _deleteOnNoConsumers = b; + } + + public void addBinding(final Binding binding) + { + _bindings.add(binding); + int bindingCount = _bindings.size(); + int bindingCountHigh; + while(bindingCount > (bindingCountHigh = _bindingCountHigh.get())) + { + if(_bindingCountHigh.compareAndSet(bindingCountHigh, bindingCount)) + { + break; + } + } + + reconfigure(); + } + + private void reconfigure() + { + //Reconfigure the queue for to reflect this new binding. + ConfigurationPlugin config = getVirtualHost().getConfiguration().getQueueConfiguration(this); + + if (_logger.isDebugEnabled()) + { + _logger.debug("Reconfiguring queue(" + this + ") with config:" + config + " was "+ _queueConfiguration); + } + + if (config != null) + { + // Reconfigure with new config. + configure(config); + } + } + + public int getBindingCountHigh() + { + return _bindingCountHigh.get(); + } + + public void removeBinding(final Binding binding) + { + _bindings.remove(binding); + + reconfigure(); + } + + public List<Binding> getBindings() + { + return Collections.unmodifiableList(_bindings); + } + + public int getBindingCount() + { + return getBindings().size(); + } + + public LogSubject getLogSubject() + { + return _logSubject; + } + + // ------ Enqueue / Dequeue + public void enqueue(ServerMessage message) throws AMQException + { + enqueue(message, null); + } + + public void enqueue(ServerMessage message, PostEnqueueAction action) throws AMQException + { + incrementTxnEnqueueStats(message); + incrementQueueCount(); + incrementQueueSize(message); + _totalMessagesReceived.incrementAndGet(); + + + QueueEntry entry; + Subscription exclusiveSub = _exclusiveSubscriber; + + if (exclusiveSub != null) + { + exclusiveSub.getSendLock(); + + try + { + entry = _entries.add(message); + + deliverToSubscription(exclusiveSub, entry); + } + finally + { + exclusiveSub.releaseSendLock(); + } + } + else + { + entry = _entries.add(message); + /* + + iterate over subscriptions and if any is at the end of the queue and can deliver this message, then deliver the message + + */ + SubscriptionList.SubscriptionNode node = _lastSubscriptionNode.get(); + SubscriptionList.SubscriptionNode nextNode = node.getNext(); + if (nextNode == null) + { + nextNode = _subscriptionList.getHead().getNext(); + } + while (nextNode != null) + { + if (_lastSubscriptionNode.compareAndSet(node, nextNode)) + { + break; + } + else + { + node = _lastSubscriptionNode.get(); + nextNode = node.getNext(); + if (nextNode == null) + { + nextNode = _subscriptionList.getHead().getNext(); + } + } + } + + // always do one extra loop after we believe we've finished + // this catches the case where we *just* miss an update + int loops = 2; + + while (!(entry.isAcquired() || entry.isDeleted()) && loops != 0) + { + if (nextNode == null) + { + loops--; + nextNode = _subscriptionList.getHead(); + } + else + { + // if subscription at end, and active, offer + Subscription sub = nextNode.getSubscription(); + deliverToSubscription(sub, entry); + } + nextNode = nextNode.getNext(); + + } + } + + + if (!(entry.isAcquired() || entry.isDeleted())) + { + checkSubscriptionsNotAheadOfDelivery(entry); + + deliverAsync(); + } + + if(_managedObject != null) + { + _managedObject.checkForNotification(entry.getMessage()); + } + + if(action != null) + { + action.onEnqueue(entry); + } + + } + + private void deliverToSubscription(final Subscription sub, final QueueEntry entry) + throws AMQException + { + + sub.getSendLock(); + try + { + if (subscriptionReadyAndHasInterest(sub, entry) + && !sub.isSuspended()) + { + if (!sub.wouldSuspend(entry)) + { + if (sub.acquires() && !entry.acquire(sub)) + { + // restore credit here that would have been taken away by wouldSuspend since we didn't manage + // to acquire the entry for this subscription + sub.onDequeue(entry); + } + else + { + deliverMessage(sub, entry); + } + } + } + } + finally + { + sub.releaseSendLock(); + } + } + + protected void checkSubscriptionsNotAheadOfDelivery(final QueueEntry entry) + { + // This method is only required for queues which mess with ordering + // Simple Queues don't :-) + } + + private void incrementQueueSize(final ServerMessage message) + { + long size = message.getSize(); + getAtomicQueueSize().addAndGet(size); + _enqueueSize.addAndGet(size); + if(message.isPersistent() && isDurable()) + { + _persistentMessageEnqueueSize.addAndGet(size); + _persistentMessageEnqueueCount.incrementAndGet(); + } + } + + private void incrementQueueCount() + { + getAtomicQueueCount().incrementAndGet(); + } + + private void incrementTxnEnqueueStats(final ServerMessage message) + { + SessionConfig session = message.getSessionConfig(); + + if(session !=null && session.isTransactional()) + { + _msgTxnEnqueues.incrementAndGet(); + _byteTxnEnqueues.addAndGet(message.getSize()); + } + } + + private void incrementTxnDequeueStats(QueueEntry entry) + { + _msgTxnDequeues.incrementAndGet(); + _byteTxnDequeues.addAndGet(entry.getSize()); + } + + private void deliverMessage(final Subscription sub, final QueueEntry entry) + throws AMQException + { + setLastSeenEntry(sub, entry); + + _deliveredMessages.incrementAndGet(); + incrementUnackedMsgCount(); + + sub.send(entry); + } + + private boolean subscriptionReadyAndHasInterest(final Subscription sub, final QueueEntry entry) throws AMQException + { + return sub.hasInterest(entry) && (getNextAvailableEntry(sub) == entry); + } + + + private void setLastSeenEntry(final Subscription sub, final QueueEntry entry) + { + QueueContext subContext = (QueueContext) sub.getQueueContext(); + QueueEntry releasedEntry = subContext._releasedEntry; + + QueueContext._lastSeenUpdater.set(subContext, entry); + if(releasedEntry == entry) + { + QueueContext._releasedUpdater.compareAndSet(subContext, releasedEntry, null); + } + } + + private void updateSubRequeueEntry(final Subscription sub, final QueueEntry entry) + { + + QueueContext subContext = (QueueContext) sub.getQueueContext(); + if(subContext != null) + { + QueueEntry oldEntry; + + while((oldEntry = subContext._releasedEntry) == null || oldEntry.compareTo(entry) > 0) + { + if(QueueContext._releasedUpdater.compareAndSet(subContext, oldEntry, entry)) + { + break; + } + } + } + } + + public void requeue(QueueEntry entry) + { + + SubscriptionList.SubscriptionNodeIterator subscriberIter = _subscriptionList.iterator(); + // iterate over all the subscribers, and if they are in advance of this queue entry then move them backwards + while (subscriberIter.advance() && entry.isAvailable()) + { + Subscription sub = subscriberIter.getNode().getSubscription(); + + // we don't make browsers send the same stuff twice + if (sub.seesRequeues()) + { + updateSubRequeueEntry(sub, entry); + } + } + + deliverAsync(); + + } + + public void dequeue(QueueEntry entry, Subscription sub) + { + decrementQueueCount(); + decrementQueueSize(entry); + if (entry.acquiredBySubscription()) + { + _deliveredMessages.decrementAndGet(); + } + + if(sub != null && sub.isSessionTransactional()) + { + incrementTxnDequeueStats(entry); + } + + checkCapacity(); + + } + + private void decrementQueueSize(final QueueEntry entry) + { + final ServerMessage message = entry.getMessage(); + long size = message.getSize(); + getAtomicQueueSize().addAndGet(-size); + _dequeueSize.addAndGet(size); + if(message.isPersistent() && isDurable()) + { + _persistentMessageDequeueSize.addAndGet(size); + _persistentMessageDequeueCount.incrementAndGet(); + } + } + + void decrementQueueCount() + { + getAtomicQueueCount().decrementAndGet(); + _dequeueCount.incrementAndGet(); + } + + public boolean resend(final QueueEntry entry, final Subscription subscription) throws AMQException + { + /* TODO : This is wrong as the subscription may be suspended, we should instead change the state of the message + entry to resend and move back the subscription pointer. */ + + subscription.getSendLock(); + try + { + if (!subscription.isClosed()) + { + deliverMessage(subscription, entry); + return true; + } + else + { + return false; + } + } + finally + { + subscription.releaseSendLock(); + } + } + + public int getConsumerCount() + { + return _subscriptionList.size(); + } + + public int getConsumerCountHigh() + { + return _counsumerCountHigh.get(); + } + + public int getActiveConsumerCount() + { + return _activeSubscriberCount.get(); + } + + public boolean isUnused() + { + return getConsumerCount() == 0; + } + + public boolean isEmpty() + { + return getMessageCount() == 0; + } + + public int getMessageCount() + { + return getAtomicQueueCount().get(); + } + + public long getQueueDepth() + { + return getAtomicQueueSize().get(); + } + + public int getUndeliveredMessageCount() + { + int count = getMessageCount() - _deliveredMessages.get(); + if (count < 0) + { + return 0; + } + else + { + return count; + } + } + + public long getReceivedMessageCount() + { + return _totalMessagesReceived.get(); + } + + public long getOldestMessageArrivalTime() + { + QueueEntry entry = getOldestQueueEntry(); + return entry == null ? Long.MAX_VALUE : entry.getMessage().getArrivalTime(); + } + + protected QueueEntry getOldestQueueEntry() + { + return _entries.next(_entries.getHead()); + } + + public boolean isDeleted() + { + return _deleted.get(); + } + + public List<QueueEntry> getMessagesOnTheQueue() + { + ArrayList<QueueEntry> entryList = new ArrayList<QueueEntry>(); + QueueEntryIterator queueListIterator = _entries.iterator(); + while (queueListIterator.advance()) + { + QueueEntry node = queueListIterator.getNode(); + if (node != null && !node.isDeleted()) + { + entryList.add(node); + } + } + return entryList; + + } + + public void stateChange(Subscription sub, Subscription.State oldState, Subscription.State newState) + { + if (oldState == Subscription.State.ACTIVE && newState != Subscription.State.ACTIVE) + { + _activeSubscriberCount.decrementAndGet(); + + } + else if (newState == Subscription.State.ACTIVE) + { + if (oldState != Subscription.State.ACTIVE) + { + _activeSubscriberCount.incrementAndGet(); + + } + deliverAsync(sub); + } + } + + public int compareTo(final AMQQueue o) + { + return _name.compareTo(o.getNameShortString()); + } + + public AtomicInteger getAtomicQueueCount() + { + return _atomicQueueCount; + } + + public AtomicLong getAtomicQueueSize() + { + return _atomicQueueSize; + } + + public boolean hasExclusiveSubscriber() + { + return _exclusiveSubscriber != null; + } + + private void setExclusiveSubscriber(Subscription exclusiveSubscriber) + { + _exclusiveSubscriber = exclusiveSubscriber; + } + + public static interface QueueEntryFilter + { + public boolean accept(QueueEntry entry); + + public boolean filterComplete(); + } + + public List<QueueEntry> getMessagesOnTheQueue(final long fromMessageId, final long toMessageId) + { + return getMessagesOnTheQueue(new QueueEntryFilter() + { + + public boolean accept(QueueEntry entry) + { + final long messageId = entry.getMessage().getMessageNumber(); + return messageId >= fromMessageId && messageId <= toMessageId; + } + + public boolean filterComplete() + { + return false; + } + }); + } + + public QueueEntry getMessageOnTheQueue(final long messageId) + { + List<QueueEntry> entries = getMessagesOnTheQueue(new QueueEntryFilter() + { + private boolean _complete; + + public boolean accept(QueueEntry entry) + { + _complete = entry.getMessage().getMessageNumber() == messageId; + return _complete; + } + + public boolean filterComplete() + { + return _complete; + } + }); + return entries.isEmpty() ? null : entries.get(0); + } + + public List<QueueEntry> getMessagesOnTheQueue(QueueEntryFilter filter) + { + ArrayList<QueueEntry> entryList = new ArrayList<QueueEntry>(); + QueueEntryIterator queueListIterator = _entries.iterator(); + while (queueListIterator.advance() && !filter.filterComplete()) + { + QueueEntry node = queueListIterator.getNode(); + if (!node.isDeleted() && filter.accept(node)) + { + entryList.add(node); + } + } + return entryList; + + } + + /** + * Returns a list of QueEntries from a given range of queue positions, eg messages 5 to 10 on the queue. + * + * The 'queue position' index starts from 1. Using 0 in 'from' will be ignored and continue from 1. + * Using 0 in the 'to' field will return an empty list regardless of the 'from' value. + * @param fromPosition + * @param toPosition + * @return + */ + public List<QueueEntry> getMessagesRangeOnTheQueue(final long fromPosition, final long toPosition) + { + return getMessagesOnTheQueue(new QueueEntryFilter() + { + private long position = 0; + + public boolean accept(QueueEntry entry) + { + position++; + return (position >= fromPosition) && (position <= toPosition); + } + + public boolean filterComplete() + { + return position >= toPosition; + } + }); + + } + + public void moveMessagesToAnotherQueue(final long fromMessageId, + final long toMessageId, + String queueName, + ServerTransaction txn) throws IllegalArgumentException + { + + final AMQQueue toQueue = getVirtualHost().getQueueRegistry().getQueue(new AMQShortString(queueName)); + if (toQueue == null) + { + throw new IllegalArgumentException("Queue '" + queueName + "' is not registered with the virtualhost."); + } + else if (toQueue == this) + { + throw new IllegalArgumentException("The destination queue cant be the same as the source queue"); + } + + List<QueueEntry> entries = getMessagesOnTheQueue(new QueueEntryFilter() + { + + public boolean accept(QueueEntry entry) + { + final long messageId = entry.getMessage().getMessageNumber(); + return (messageId >= fromMessageId) + && (messageId <= toMessageId) + && entry.acquire(); + } + + public boolean filterComplete() + { + return false; + } + }); + + + + // Move the messages in on the message store. + for (final QueueEntry entry : entries) + { + final ServerMessage message = entry.getMessage(); + txn.enqueue(toQueue, message, + new ServerTransaction.Action() + { + + public void postCommit() + { + try + { + toQueue.enqueue(message); + } + catch (AMQException e) + { + throw new RuntimeException(e); + } + } + + public void onRollback() + { + entry.release(); + } + }); + txn.dequeue(this, message, + new ServerTransaction.Action() + { + + public void postCommit() + { + entry.discard(); + } + + public void onRollback() + { + + } + }); + + } + + } + + public void copyMessagesToAnotherQueue(final long fromMessageId, + final long toMessageId, + String queueName, + final ServerTransaction txn) throws IllegalArgumentException + { + final AMQQueue toQueue = getVirtualHost().getQueueRegistry().getQueue(new AMQShortString(queueName)); + if (toQueue == null) + { + throw new IllegalArgumentException("Queue '" + queueName + "' is not registered with the virtualhost."); + } + else if (toQueue == this) + { + throw new IllegalArgumentException("The destination queue cant be the same as the source queue"); + } + + List<QueueEntry> entries = getMessagesOnTheQueue(new QueueEntryFilter() + { + + public boolean accept(QueueEntry entry) + { + final long messageId = entry.getMessage().getMessageNumber(); + return ((messageId >= fromMessageId) + && (messageId <= toMessageId)); + } + + public boolean filterComplete() + { + return false; + } + }); + + + // Move the messages in on the message store. + for (QueueEntry entry : entries) + { + final ServerMessage message = entry.getMessage(); + + txn.enqueue(toQueue, message, new ServerTransaction.Action() + { + public void postCommit() + { + try + { + toQueue.enqueue(message); + } + catch (AMQException e) + { + throw new RuntimeException(e); + } + } + + public void onRollback() + { + + } + }); + + } + + } + + public void removeMessagesFromQueue(long fromMessageId, long toMessageId) + { + + QueueEntryIterator queueListIterator = _entries.iterator(); + + while (queueListIterator.advance()) + { + QueueEntry node = queueListIterator.getNode(); + + final ServerMessage message = node.getMessage(); + if(message != null) + { + final long messageId = message.getMessageNumber(); + + if ((messageId >= fromMessageId) + && (messageId <= toMessageId) + && !node.isDeleted() + && node.acquire()) + { + dequeueEntry(node); + } + } + } + + } + + public void purge(final long request) throws AMQException + { + clear(request); + } + + public long getCreateTime() + { + return _createTime; + } + + // ------ Management functions + + public void deleteMessageFromTop() + { + QueueEntryIterator queueListIterator = _entries.iterator(); + boolean noDeletes = true; + + while (noDeletes && queueListIterator.advance()) + { + QueueEntry node = queueListIterator.getNode(); + if (!node.isDeleted() && node.acquire()) + { + dequeueEntry(node); + noDeletes = false; + } + + } + } + + public long clearQueue() throws AMQException + { + return clear(0l); + } + + private long clear(final long request) throws AMQSecurityException + { + //Perform ACLs + if (!getVirtualHost().getSecurityManager().authorisePurge(this)) + { + throw new AMQSecurityException("Permission denied: queue " + getName()); + } + + QueueEntryIterator queueListIterator = _entries.iterator(); + long count = 0; + + ServerTransaction txn = new LocalTransaction(getVirtualHost().getTransactionLog()); + + while (queueListIterator.advance()) + { + QueueEntry node = queueListIterator.getNode(); + if (!node.isDeleted() && node.acquire()) + { + dequeueEntry(node, txn); + if(++count == request) + { + break; + } + } + + } + + txn.commit(); + + return count; + } + + private void dequeueEntry(final QueueEntry node) + { + ServerTransaction txn = new AutoCommitTransaction(getVirtualHost().getTransactionLog()); + dequeueEntry(node, txn); + } + + private void dequeueEntry(final QueueEntry node, ServerTransaction txn) + { + txn.dequeue(this, node.getMessage(), + new ServerTransaction.Action() + { + + public void postCommit() + { + node.discard(); + } + + public void onRollback() + { + + } + }); + } + + public void addQueueDeleteTask(final Task task) + { + _deleteTaskList.add(task); + } + + public void removeQueueDeleteTask(final Task task) + { + _deleteTaskList.remove(task); + } + + // TODO list all thrown exceptions + public int delete() throws AMQSecurityException, AMQException + { + // Check access + if (!_virtualHost.getSecurityManager().authoriseDelete(this)) + { + throw new AMQSecurityException("Permission denied: " + getName()); + } + + if (!_deleted.getAndSet(true)) + { + + for (Binding b : getBindings()) + { + _virtualHost.getBindingFactory().removeBinding(b); + } + + SubscriptionList.SubscriptionNodeIterator subscriptionIter = _subscriptionList.iterator(); + + while (subscriptionIter.advance()) + { + Subscription s = subscriptionIter.getNode().getSubscription(); + if (s != null) + { + s.queueDeleted(this); + } + } + + _virtualHost.getQueueRegistry().unregisterQueue(_name); + getConfigStore().removeConfiguredObject(this); + + List<QueueEntry> entries = getMessagesOnTheQueue(new QueueEntryFilter() + { + + public boolean accept(QueueEntry entry) + { + return entry.acquire(); + } + + public boolean filterComplete() + { + return false; + } + }); + + ServerTransaction txn = new LocalTransaction(getVirtualHost().getTransactionLog()); + + if(_alternateExchange != null) + { + + InboundMessageAdapter adapter = new InboundMessageAdapter(); + for(final QueueEntry entry : entries) + { + adapter.setEntry(entry); + final List<? extends BaseQueue> rerouteQueues = _alternateExchange.route(adapter); + final ServerMessage message = entry.getMessage(); + if(rerouteQueues != null && rerouteQueues.size() != 0) + { + txn.enqueue(rerouteQueues, entry.getMessage(), + new ServerTransaction.Action() + { + + public void postCommit() + { + try + { + for(BaseQueue queue : rerouteQueues) + { + queue.enqueue(message); + } + } + catch (AMQException e) + { + throw new RuntimeException(e); + } + + } + + public void onRollback() + { + + } + }); + txn.dequeue(this, entry.getMessage(), + new ServerTransaction.Action() + { + + public void postCommit() + { + entry.discard(); + } + + public void onRollback() + { + } + }); + } + + } + + _alternateExchange.removeReference(this); + } + else + { + // TODO log discard + + for(final QueueEntry entry : entries) + { + final ServerMessage message = entry.getMessage(); + if(message != null) + { + txn.dequeue(this, message, + new ServerTransaction.Action() + { + + public void postCommit() + { + entry.discard(); + } + + public void onRollback() + { + } + }); + } + } + } + + txn.commit(); + + + if(_managedObject!=null) + { + _managedObject.unregister(); + } + + for (Task task : _deleteTaskList) + { + task.doTask(this); + } + + _deleteTaskList.clear(); + stop(); + + //Log Queue Deletion + CurrentActor.get().message(_logSubject, QueueMessages.DELETED()); + + } + return getMessageCount(); + + } + + public void stop() + { + if (!_stopped.getAndSet(true)) + { + ReferenceCountingExecutorService.getInstance().releaseExecutorService(); + } + } + + public void checkCapacity(AMQChannel channel) + { + if(_capacity != 0l) + { + if(_atomicQueueSize.get() > _capacity) + { + _overfull.set(true); + //Overfull log message + _logActor.message(_logSubject, QueueMessages.OVERFULL(_atomicQueueSize.get(), _capacity)); + + if(_blockedChannels.putIfAbsent(channel, Boolean.TRUE)==null) + { + channel.block(this); + } + + if(_atomicQueueSize.get() <= _flowResumeCapacity) + { + + //Underfull log message + _logActor.message(_logSubject, QueueMessages.UNDERFULL(_atomicQueueSize.get(), _flowResumeCapacity)); + + channel.unblock(this); + _blockedChannels.remove(channel); + + } + + } + + + + } + } + + private void checkCapacity() + { + if(_capacity != 0L) + { + if(_overfull.get() && _atomicQueueSize.get() <= _flowResumeCapacity) + { + if(_overfull.compareAndSet(true,false)) + {//Underfull log message + _logActor.message(_logSubject, QueueMessages.UNDERFULL(_atomicQueueSize.get(), _flowResumeCapacity)); + } + + + for(AMQChannel c : _blockedChannels.keySet()) + { + c.unblock(this); + _blockedChannels.remove(c); + } + } + } + } + + + public void deliverAsync() + { + QueueRunner runner = new QueueRunner(this, _stateChangeCount.incrementAndGet()); + + if (_asynchronousRunner.compareAndSet(null, runner)) + { + _asyncDelivery.execute(runner); + } + } + + public void deliverAsync(Subscription sub) + { + SubFlushRunner flusher = (SubFlushRunner) sub.get(SUB_FLUSH_RUNNER); + if(flusher == null) + { + flusher = new SubFlushRunner(sub); + sub.set(SUB_FLUSH_RUNNER, flusher); + } + _asyncDelivery.execute(flusher); + } + + public void flushSubscription(Subscription sub) throws AMQException + { + // Access control + if (!getVirtualHost().getSecurityManager().authoriseConsume(this)) + { + throw new AMQSecurityException("Permission denied: " + getName()); + } + flushSubscription(sub, Long.MAX_VALUE); + } + + public boolean flushSubscription(Subscription sub, long iterations) throws AMQException + { + boolean atTail = false; + + while (!sub.isSuspended() && !atTail && iterations != 0) + { + try + { + sub.getSendLock(); + atTail = attemptDelivery(sub); + if (atTail && sub.isAutoClose()) + { + unregisterSubscription(sub); + + sub.confirmAutoClose(); + + } + else if (!atTail) + { + iterations--; + } + } + finally + { + sub.releaseSendLock(); + } + } + + // if there's (potentially) more than one subscription the others will potentially not have been advanced to the + // next entry they are interested in yet. This would lead to holding on to references to expired messages, etc + // which would give us memory "leak". + + if (!hasExclusiveSubscriber()) + { + advanceAllSubscriptions(); + } + return atTail; + } + + /** + * Attempt delivery for the given subscription. + * + * Looks up the next node for the subscription and attempts to deliver it. + * + * @param sub + * @return true if we have completed all possible deliveries for this sub. + * @throws AMQException + */ + private boolean attemptDelivery(Subscription sub) throws AMQException + { + boolean atTail = false; + + boolean subActive = sub.isActive() && !sub.isSuspended(); + if (subActive) + { + + QueueEntry node = getNextAvailableEntry(sub); + + if (node != null && !(node.isAcquired() || node.isDeleted())) + { + if (sub.hasInterest(node)) + { + if (!sub.wouldSuspend(node)) + { + if (sub.acquires() && !node.acquire(sub)) + { + sub.onDequeue(node); + } + else + { + deliverMessage(sub, node); + } + + } + else // Not enough Credit for message and wouldSuspend + { + //QPID-1187 - Treat the subscription as suspended for this message + // and wait for the message to be removed to continue delivery. + subActive = false; + node.addStateChangeListener(new QueueEntryListener(sub)); + } + } + + } + atTail = (node == null) || (_entries.next(node) == null); + } + return atTail || !subActive; + } + + protected void advanceAllSubscriptions() throws AMQException + { + SubscriptionList.SubscriptionNodeIterator subscriberIter = _subscriptionList.iterator(); + while (subscriberIter.advance()) + { + SubscriptionList.SubscriptionNode subNode = subscriberIter.getNode(); + Subscription sub = subNode.getSubscription(); + if(sub.acquires()) + { + getNextAvailableEntry(sub); + } + else + { + // TODO + } + } + } + + private QueueEntry getNextAvailableEntry(final Subscription sub) + throws AMQException + { + QueueContext context = (QueueContext) sub.getQueueContext(); + if(context != null) + { + QueueEntry lastSeen = context._lastSeenEntry; + QueueEntry releasedNode = context._releasedEntry; + + QueueEntry node = (releasedNode != null && lastSeen.compareTo(releasedNode)>=0) ? releasedNode : _entries.next(lastSeen); + + boolean expired = false; + while (node != null && (node.isAcquired() || node.isDeleted() || (expired = node.expired()) || !sub.hasInterest(node))) + { + if (expired) + { + expired = false; + if (node.acquire()) + { + dequeueEntry(node); + } + } + + if(QueueContext._lastSeenUpdater.compareAndSet(context, lastSeen, node)) + { + QueueContext._releasedUpdater.compareAndSet(context, releasedNode, null); + } + + lastSeen = context._lastSeenEntry; + releasedNode = context._releasedEntry; + node = (releasedNode != null && lastSeen.compareTo(releasedNode)>0) ? releasedNode : _entries.next(lastSeen); + } + return node; + } + else + { + return null; + } + } + + + /** + * Used by queue Runners to asynchronously deliver messages to consumers. + * + * A queue Runner is started whenever a state change occurs, e.g when a new + * message arrives on the queue and cannot be immediately delivered to a + * subscription (i.e. asynchronous delivery is required). Unless there are + * SubFlushRunners operating (due to subscriptions unsuspending) which are + * capable of accepting/delivering all messages then these messages would + * otherwise remain on the queue. + * + * processQueue should be running while there are messages on the queue AND + * there are subscriptions that can deliver them. If there are no + * subscriptions capable of delivering the remaining messages on the queue + * then processQueue should stop to prevent spinning. + * + * Since processQueue is runs in a fixed size Executor, it should not run + * indefinitely to prevent starving other tasks of CPU (e.g jobs to process + * incoming messages may not be able to be scheduled in the thread pool + * because all threads are working on clearing down large queues). To solve + * this problem, after an arbitrary number of message deliveries the + * processQueue job stops iterating, resubmits itself to the executor, and + * ends the current instance + * + * @param runner the Runner to schedule + * @throws AMQException + */ + public void processQueue(QueueRunner runner) throws AMQException + { + long stateChangeCount; + long previousStateChangeCount = Long.MIN_VALUE; + boolean deliveryIncomplete = true; + + boolean lastLoop = false; + int iterations = MAX_ASYNC_DELIVERIES; + + _asynchronousRunner.compareAndSet(runner, null); + + // For every message enqueue/requeue the we fire deliveryAsync() which + // increases _stateChangeCount. If _sCC changes whilst we are in our loop + // (detected by setting previousStateChangeCount to stateChangeCount in the loop body) + // then we will continue to run for a maximum of iterations. + // So whilst delivery/rejection is going on a processQueue thread will be running + while (iterations != 0 && ((previousStateChangeCount != (stateChangeCount = _stateChangeCount.get())) || deliveryIncomplete) && _asynchronousRunner.compareAndSet(null, runner)) + { + // we want to have one extra loop after every subscription has reached the point where it cannot move + // further, just in case the advance of one subscription in the last loop allows a different subscription to + // move forward in the next iteration + + if (previousStateChangeCount != stateChangeCount) + { + //further asynchronous delivery is required since the + //previous loop. keep going if iteration slicing allows. + lastLoop = false; + } + + previousStateChangeCount = stateChangeCount; + boolean allSubscriptionsDone = true; + boolean subscriptionDone; + + SubscriptionList.SubscriptionNodeIterator subscriptionIter = _subscriptionList.iterator(); + //iterate over the subscribers and try to advance their pointer + while (subscriptionIter.advance()) + { + Subscription sub = subscriptionIter.getNode().getSubscription(); + sub.getSendLock(); + try + { + //attempt delivery. returns true if no further delivery currently possible to this sub + subscriptionDone = attemptDelivery(sub); + if (subscriptionDone) + { + //close autoClose subscriptions if we are not currently intent on continuing + if (lastLoop && sub.isAutoClose()) + { + unregisterSubscription(sub); + + sub.confirmAutoClose(); + } + } + else + { + //this subscription can accept additional deliveries, so we must + //keep going after this (if iteration slicing allows it) + allSubscriptionsDone = false; + lastLoop = false; + iterations--; + } + } + finally + { + sub.releaseSendLock(); + } + } + + if(allSubscriptionsDone && lastLoop) + { + //We have done an extra loop already and there are again + //again no further delivery attempts possible, only + //keep going if state change demands it. + deliveryIncomplete = false; + } + else if(allSubscriptionsDone) + { + //All subscriptions reported being done, but we have to do + //an extra loop if the iterations are not exhausted and + //there is still any work to be done + deliveryIncomplete = _subscriptionList.size() != 0; + lastLoop = true; + } + else + { + //some subscriptions can still accept more messages, + //keep going if iteration count allows. + lastLoop = false; + deliveryIncomplete = true; + } + + _asynchronousRunner.set(null); + } + + // If iterations == 0 then the limiting factor was the time-slicing rather than available messages or credit + // therefore we should schedule this runner again (unless someone beats us to it :-) ). + if (iterations == 0 && _asynchronousRunner.compareAndSet(null, runner)) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Rescheduling runner:" + runner); + } + _asyncDelivery.execute(runner); + } + } + + public void checkMessageStatus() throws AMQException + { + + QueueEntryIterator queueListIterator = _entries.iterator(); + + while (queueListIterator.advance()) + { + QueueEntry node = queueListIterator.getNode(); + // Only process nodes that are not currently deleted + if (!node.isDeleted()) + { + // If the node has exired then aquire it + if (node.expired() && node.acquire()) + { + // Then dequeue it. + dequeueEntry(node); + } + else + { + if (_managedObject != null) + { + // There is a chance that the node could be deleted by + // the time the check actually occurs. So verify we + // can actually get the message to perform the check. + ServerMessage msg = node.getMessage(); + if (msg != null) + { + _managedObject.checkForNotification(msg); + } + } + } + } + } + + } + + public long getMinimumAlertRepeatGap() + { + return _minimumAlertRepeatGap; + } + + public void setMinimumAlertRepeatGap(long minimumAlertRepeatGap) + { + _minimumAlertRepeatGap = minimumAlertRepeatGap; + } + + public long getMaximumMessageAge() + { + return _maximumMessageAge; + } + + public void setMaximumMessageAge(long maximumMessageAge) + { + _maximumMessageAge = maximumMessageAge; + if (maximumMessageAge == 0L) + { + _notificationChecks.remove(NotificationCheck.MESSAGE_AGE_ALERT); + } + else + { + _notificationChecks.add(NotificationCheck.MESSAGE_AGE_ALERT); + } + } + + public long getMaximumMessageCount() + { + return _maximumMessageCount; + } + + public void setMaximumMessageCount(final long maximumMessageCount) + { + _maximumMessageCount = maximumMessageCount; + if (maximumMessageCount == 0L) + { + _notificationChecks.remove(NotificationCheck.MESSAGE_COUNT_ALERT); + } + else + { + _notificationChecks.add(NotificationCheck.MESSAGE_COUNT_ALERT); + } + + } + + public long getMaximumQueueDepth() + { + return _maximumQueueDepth; + } + + // Sets the queue depth, the max queue size + public void setMaximumQueueDepth(final long maximumQueueDepth) + { + _maximumQueueDepth = maximumQueueDepth; + if (maximumQueueDepth == 0L) + { + _notificationChecks.remove(NotificationCheck.QUEUE_DEPTH_ALERT); + } + else + { + _notificationChecks.add(NotificationCheck.QUEUE_DEPTH_ALERT); + } + + } + + public long getMaximumMessageSize() + { + return _maximumMessageSize; + } + + public void setMaximumMessageSize(final long maximumMessageSize) + { + _maximumMessageSize = maximumMessageSize; + if (maximumMessageSize == 0L) + { + _notificationChecks.remove(NotificationCheck.MESSAGE_SIZE_ALERT); + } + else + { + _notificationChecks.add(NotificationCheck.MESSAGE_SIZE_ALERT); + } + } + + public long getCapacity() + { + return _capacity; + } + + public void setCapacity(long capacity) + { + _capacity = capacity; + } + + public long getFlowResumeCapacity() + { + return _flowResumeCapacity; + } + + public void setFlowResumeCapacity(long flowResumeCapacity) + { + _flowResumeCapacity = flowResumeCapacity; + + checkCapacity(); + } + + public boolean isOverfull() + { + return _overfull.get(); + } + + public Set<NotificationCheck> getNotificationChecks() + { + return _notificationChecks; + } + + public ManagedObject getManagedObject() + { + return _managedObject; + } + + private final class QueueEntryListener implements QueueEntry.StateChangeListener + { + + private final Subscription _sub; + + public QueueEntryListener(final Subscription sub) + { + _sub = sub; + } + + public boolean equals(Object o) + { + assert o != null; + assert o instanceof QueueEntryListener; + return _sub == ((QueueEntryListener) o)._sub; + } + + public int hashCode() + { + return System.identityHashCode(_sub); + } + + public void stateChanged(QueueEntry entry, QueueEntry.State oldSate, QueueEntry.State newState) + { + entry.removeStateChangeListener(this); + deliverAsync(_sub); + } + } + + public List<Long> getMessagesOnTheQueue(int num) + { + return getMessagesOnTheQueue(num, 0); + } + + public List<Long> getMessagesOnTheQueue(int num, int offset) + { + ArrayList<Long> ids = new ArrayList<Long>(num); + QueueEntryIterator it = _entries.iterator(); + for (int i = 0; i < offset; i++) + { + it.advance(); + } + + for (int i = 0; i < num && !it.atTail(); i++) + { + it.advance(); + ids.add(it.getNode().getMessage().getMessageNumber()); + } + return ids; + } + + public AMQSessionModel getExclusiveOwningSession() + { + return _exclusiveOwner; + } + + public void setExclusiveOwningSession(AMQSessionModel exclusiveOwner) + { + _exclusive = true; + _exclusiveOwner = exclusiveOwner; + } + + + public void configure(ConfigurationPlugin config) + { + if (config != null) + { + if (config instanceof QueueConfiguration) + { + + setMaximumMessageAge(((QueueConfiguration)config).getMaximumMessageAge()); + setMaximumQueueDepth(((QueueConfiguration)config).getMaximumQueueDepth()); + setMaximumMessageSize(((QueueConfiguration)config).getMaximumMessageSize()); + setMaximumMessageCount(((QueueConfiguration)config).getMaximumMessageCount()); + setMinimumAlertRepeatGap(((QueueConfiguration)config).getMinimumAlertRepeatGap()); + _capacity = ((QueueConfiguration)config).getCapacity(); + _flowResumeCapacity = ((QueueConfiguration)config).getFlowResumeCapacity(); + } + + _queueConfiguration = config; + + } + } + + + public ConfigurationPlugin getConfiguration() + { + return _queueConfiguration; + } + + public String getResourceName() + { + return _resourceName; + } + + + public ConfigStore getConfigStore() + { + return getVirtualHost().getConfigStore(); + } + + public long getMessageDequeueCount() + { + return _dequeueCount.get(); + } + + public long getTotalEnqueueSize() + { + return _enqueueSize.get(); + } + + public long getTotalDequeueSize() + { + return _dequeueSize.get(); + } + + public long getByteTxnEnqueues() + { + return _byteTxnEnqueues.get(); + } + + public long getByteTxnDequeues() + { + return _byteTxnDequeues.get(); + } + + public long getMsgTxnEnqueues() + { + return _msgTxnEnqueues.get(); + } + + public long getMsgTxnDequeues() + { + return _msgTxnDequeues.get(); + } + + public long getPersistentByteEnqueues() + { + return _persistentMessageEnqueueSize.get(); + } + + public long getPersistentByteDequeues() + { + return _persistentMessageDequeueSize.get(); + } + + public long getPersistentMsgEnqueues() + { + return _persistentMessageEnqueueCount.get(); + } + + public long getPersistentMsgDequeues() + { + return _persistentMessageDequeueCount.get(); + } + + + @Override + public String toString() + { + return String.valueOf(getNameShortString()); + } + + public long getUnackedMessageCountHigh() + { + return _unackedMsgCountHigh.get(); + } + + public long getUnackedMessageCount() + { + return _unackedMsgCount.get(); + } + + public void decrementUnackedMsgCount() + { + _unackedMsgCount.decrementAndGet(); + } + + private void incrementUnackedMsgCount() + { + long unackedMsgCount = _unackedMsgCount.incrementAndGet(); + + long unackedMsgCountHigh; + while(unackedMsgCount > (unackedMsgCountHigh = _unackedMsgCountHigh.get())) + { + if(_unackedMsgCountHigh.compareAndSet(unackedMsgCountHigh, unackedMsgCount)) + { + break; + } + } + } + + public LogActor getLogActor() + { + return _logActor; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleQueueEntryList.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleQueueEntryList.java new file mode 100644 index 0000000000..b97c2c55c5 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SimpleQueueEntryList.java @@ -0,0 +1,198 @@ +package org.apache.qpid.server.queue; + +import org.apache.qpid.server.message.InboundMessage; +import org.apache.qpid.server.message.ServerMessage; + +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.concurrent.atomic.AtomicLong; + +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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. +* +*/ +public class SimpleQueueEntryList implements QueueEntryList +{ + + private final QueueEntryImpl _head; + + private volatile QueueEntryImpl _tail; + + static final AtomicReferenceFieldUpdater<SimpleQueueEntryList, QueueEntryImpl> + _tailUpdater = + AtomicReferenceFieldUpdater.newUpdater + (SimpleQueueEntryList.class, QueueEntryImpl.class, "_tail"); + + + private final AMQQueue _queue; + + static final AtomicReferenceFieldUpdater<QueueEntryImpl, QueueEntryImpl> + _nextUpdater = + AtomicReferenceFieldUpdater.newUpdater + (QueueEntryImpl.class, QueueEntryImpl.class, "_next"); + + private AtomicLong _scavenges = new AtomicLong(0L); + private final long _scavengeCount = Integer.getInteger("qpid.queue.scavenge_count", 50); + + + public SimpleQueueEntryList(AMQQueue queue) + { + _queue = queue; + _head = new QueueEntryImpl(this); + _tail = _head; + } + + void advanceHead() + { + QueueEntryImpl next = _head.nextNode(); + QueueEntryImpl newNext = _head.getNext(); + + if (next == newNext) + { + if (_scavenges.incrementAndGet() > _scavengeCount) + { + _scavenges.set(0L); + scavenge(); + } + } + } + + void scavenge() + { + QueueEntryImpl next = _head.getNext(); + + while (next != null) + { + next = next.getNext(); + } + } + + + public AMQQueue getQueue() + { + return _queue; + } + + + public QueueEntry add(ServerMessage message) + { + QueueEntryImpl node = createQueueEntry(message); + for (;;) + { + QueueEntryImpl tail = _tail; + QueueEntryImpl next = tail.nextNode(); + if (tail == _tail) + { + if (next == null) + { + node.setEntryId(tail.getEntryId()+1); + if (_nextUpdater.compareAndSet(tail, null, node)) + { + _tailUpdater.compareAndSet(this, tail, node); + + return node; + } + } + else + { + _tailUpdater.compareAndSet(this,tail, next); + } + } + } + } + + protected QueueEntryImpl createQueueEntry(ServerMessage message) + { + return new QueueEntryImpl(this, message); + } + + public QueueEntry next(QueueEntry node) + { + return ((QueueEntryImpl)node).getNext(); + } + + + public static class QueueEntryIteratorImpl implements QueueEntryIterator + { + + private QueueEntryImpl _lastNode; + + QueueEntryIteratorImpl(QueueEntryImpl startNode) + { + _lastNode = startNode; + } + + + public boolean atTail() + { + return _lastNode.nextNode() == null; + } + + public QueueEntry getNode() + { + + return _lastNode; + + } + + public boolean advance() + { + + if(!atTail()) + { + QueueEntryImpl nextNode = _lastNode.nextNode(); + while(nextNode.isDeleted() && nextNode.nextNode() != null) + { + nextNode = nextNode.nextNode(); + } + _lastNode = nextNode; + return true; + + } + else + { + return false; + } + + } + + } + + + public QueueEntryIterator iterator() + { + return new QueueEntryIteratorImpl(_head); + } + + + public QueueEntry getHead() + { + return _head; + } + + static class Factory implements QueueEntryListFactory + { + + public QueueEntryList createQueueEntryList(AMQQueue queue) + { + return new SimpleQueueEntryList(queue); + } + } + + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SubFlushRunner.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SubFlushRunner.java new file mode 100755 index 0000000000..46c1a6af9a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/queue/SubFlushRunner.java @@ -0,0 +1,96 @@ +package org.apache.qpid.server.queue; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ + + +import org.apache.qpid.pool.ReadWriteRunnable; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.AMQException; +import org.apache.log4j.Logger; + + +class SubFlushRunner implements ReadWriteRunnable +{ + private static final Logger _logger = Logger.getLogger(SubFlushRunner.class); + + + private final Subscription _sub; + private final String _name; + private static final long ITERATIONS = SimpleAMQQueue.MAX_ASYNC_DELIVERIES; + + public SubFlushRunner(Subscription sub) + { + _sub = sub; + _name = "SubFlushRunner-"+_sub; + } + + public void run() + { + + String originalName = Thread.currentThread().getName(); + try + { + Thread.currentThread().setName(_name); + + boolean complete = false; + try + { + CurrentActor.set(_sub.getLogActor()); + complete = getQueue().flushSubscription(_sub, ITERATIONS); + + } + catch (AMQException e) + { + _logger.error(e); + } + finally + { + CurrentActor.remove(); + } + if (!complete && !_sub.isSuspended()) + { + getQueue().execute(this); + } + + } + finally + { + Thread.currentThread().setName(originalName); + } + + } + + private SimpleAMQQueue getQueue() + { + return (SimpleAMQQueue) _sub.getQueue(); + } + + public boolean isRead() + { + return false; + } + + public boolean isWrite() + { + return true; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java new file mode 100644 index 0000000000..b6df0cc0a6 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ApplicationRegistry.java @@ -0,0 +1,655 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.registry; + +import java.net.InetSocketAddress; +import java.util.HashMap; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.common.Closeable; +import org.apache.qpid.common.QpidProperties; +import org.apache.qpid.qmf.QMFService; +import org.apache.qpid.server.configuration.BrokerConfig; +import org.apache.qpid.server.configuration.ConfigStore; +import org.apache.qpid.server.configuration.ConfigurationManager; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.configuration.SystemConfig; +import org.apache.qpid.server.configuration.SystemConfigImpl; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.logging.CompositeStartupMessageLogger; +import org.apache.qpid.server.logging.Log4jMessageLogger; +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.SystemOutMessageLogger; +import org.apache.qpid.server.logging.actors.AbstractActor; +import org.apache.qpid.server.logging.actors.BrokerActor; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.BrokerMessages; +import org.apache.qpid.server.logging.messages.VirtualHostMessages; +import org.apache.qpid.server.management.ManagedObjectRegistry; +import org.apache.qpid.server.management.NoopManagedObjectRegistry; +import org.apache.qpid.server.plugins.PluginManager; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.security.auth.database.ConfigurationFilePrincipalDatabaseManager; +import org.apache.qpid.server.security.auth.database.PrincipalDatabaseManager; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager; +import org.apache.qpid.server.stats.StatisticsCounter; +import org.apache.qpid.server.transport.QpidAcceptor; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.virtualhost.VirtualHostImpl; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; + +/** + * An abstract application registry that provides access to configuration information and handles the + * construction and caching of configurable objects. + * <p/> + * Subclasses should handle the construction of the "registered objects" such as the exchange registry. + */ +public abstract class ApplicationRegistry implements IApplicationRegistry +{ + protected static final Logger _logger = Logger.getLogger(ApplicationRegistry.class); + + private static Map<Integer, IApplicationRegistry> _instanceMap = new HashMap<Integer, IApplicationRegistry>(); + + protected final ServerConfiguration _configuration; + + public static final int DEFAULT_INSTANCE = 1; + + protected final Map<InetSocketAddress, QpidAcceptor> _acceptors = new HashMap<InetSocketAddress, QpidAcceptor>(); + + protected ManagedObjectRegistry _managedObjectRegistry; + + protected AuthenticationManager _authenticationManager; + + protected VirtualHostRegistry _virtualHostRegistry; + + protected SecurityManager _securityManager; + + protected PrincipalDatabaseManager _databaseManager; + + protected PluginManager _pluginManager; + + protected ConfigurationManager _configurationManager; + + protected RootMessageLogger _rootMessageLogger; + + protected CompositeStartupMessageLogger _startupMessageLogger; + + protected UUID _brokerId = UUID.randomUUID(); + + protected QMFService _qmfService; + + private BrokerConfig _broker; + + private ConfigStore _configStore; + + protected String _registryName; + + private Timer _reportingTimer; + private boolean _statisticsEnabled = false; + private StatisticsCounter _messagesDelivered, _dataDelivered, _messagesReceived, _dataReceived; + + static + { + Runtime.getRuntime().addShutdownHook(new Thread(new ShutdownService())); + } + + private static class ShutdownService implements Runnable + { + public void run() + { + removeAll(); + } + } + + public static void initialise(IApplicationRegistry instance) throws Exception + { + initialise(instance, DEFAULT_INSTANCE); + } + + @SuppressWarnings("finally") + public static void initialise(IApplicationRegistry instance, int instanceID) throws Exception + { + if (instance != null) + { + _logger.info("Initialising Application Registry(" + instance + "):" + instanceID); + _instanceMap.put(instanceID, instance); + + final ConfigStore store = ConfigStore.newInstance(); + store.setRoot(new SystemConfigImpl(store)); + instance.setConfigStore(store); + + BrokerConfig broker = new BrokerConfigAdapter(instance); + + SystemConfig system = (SystemConfig) store.getRoot(); + system.addBroker(broker); + instance.setBroker(broker); + + try + { + instance.initialise(instanceID); + } + catch (Exception e) + { + _instanceMap.remove(instanceID); + try + { + system.removeBroker(broker); + } + finally + { + throw e; + } + } + } + else + { + remove(instanceID); + } + } + + public ConfigStore getConfigStore() + { + return _configStore; + } + + public void setConfigStore(final ConfigStore configStore) + { + _configStore = configStore; + } + + public static boolean isConfigured() + { + return isConfigured(DEFAULT_INSTANCE); + } + + public static boolean isConfigured(int instanceID) + { + return _instanceMap.containsKey(instanceID); + } + + /** Method to cleanly shutdown the default registry running in this JVM */ + public static void remove() + { + remove(DEFAULT_INSTANCE); + } + + /** + * Method to cleanly shutdown specified registry running in this JVM + * + * @param instanceID the instance to shutdown + */ + public static void remove(int instanceID) + { + try + { + IApplicationRegistry instance = _instanceMap.get(instanceID); + if (instance != null) + { + if (_logger.isInfoEnabled()) + { + _logger.info("Shutting down ApplicationRegistry(" + instanceID + "):" + instance); + } + instance.close(); + instance.getBroker().getSystem().removeBroker(instance.getBroker()); + } + } + catch (Exception e) + { + _logger.error("Error shutting down Application Registry(" + instanceID + "): " + e, e); + } + finally + { + _instanceMap.remove(instanceID); + } + } + + /** Method to cleanly shutdown all registries currently running in this JVM */ + public static void removeAll() + { + Object[] keys = _instanceMap.keySet().toArray(); + for (Object k : keys) + { + remove((Integer) k); + } + } + + protected ApplicationRegistry(ServerConfiguration configuration) + { + _configuration = configuration; + } + + public void configure() throws ConfigurationException + { + _configurationManager = new ConfigurationManager(); + + try + { + _pluginManager = new PluginManager(_configuration.getPluginDirectory(), _configuration.getCacheDirectory()); + } + catch (Exception e) + { + throw new ConfigurationException(e); + } + + _configuration.initialise(); + } + + public void initialise(int instanceID) throws Exception + { + //Create the RootLogger to be used during broker operation + _rootMessageLogger = new Log4jMessageLogger(_configuration); + _registryName = String.valueOf(instanceID); + + //Create the composite (log4j+SystemOut MessageLogger to be used during startup + RootMessageLogger[] messageLoggers = {new SystemOutMessageLogger(), _rootMessageLogger}; + _startupMessageLogger = new CompositeStartupMessageLogger(messageLoggers); + + CurrentActor.set(new BrokerActor(_startupMessageLogger)); + + try + { + configure(); + + _qmfService = new QMFService(getConfigStore(), this); + + CurrentActor.get().message(BrokerMessages.STARTUP(QpidProperties.getReleaseVersion(), QpidProperties.getBuildVersion())); + + initialiseManagedObjectRegistry(); + + _virtualHostRegistry = new VirtualHostRegistry(this); + + _securityManager = new SecurityManager(_configuration, _pluginManager); + + createDatabaseManager(_configuration); + + _authenticationManager = new PrincipalDatabaseAuthenticationManager(); + + _databaseManager.initialiseManagement(_configuration); + + _managedObjectRegistry.start(); + } + finally + { + CurrentActor.remove(); + } + + CurrentActor.set(new BrokerActor(_rootMessageLogger)); + try + { + initialiseVirtualHosts(); + initialiseStatistics(); + initialiseStatisticsReporting(); + } + finally + { + // Startup complete, so pop the current actor + CurrentActor.remove(); + } + } + + protected void createDatabaseManager(ServerConfiguration configuration) throws Exception + { + _databaseManager = new ConfigurationFilePrincipalDatabaseManager(_configuration); + } + + protected void initialiseVirtualHosts() throws Exception + { + for (String name : _configuration.getVirtualHosts()) + { + createVirtualHost(_configuration.getVirtualHostConfig(name)); + } + getVirtualHostRegistry().setDefaultVirtualHostName(_configuration.getDefaultVirtualHost()); + } + + protected void initialiseManagedObjectRegistry() throws AMQException + { + _managedObjectRegistry = new NoopManagedObjectRegistry(); + } + + public void initialiseStatisticsReporting() + { + long report = _configuration.getStatisticsReportingPeriod() * 1000; // convert to ms + final boolean broker = _configuration.isStatisticsGenerationBrokerEnabled(); + final boolean virtualhost = _configuration.isStatisticsGenerationVirtualhostsEnabled(); + final boolean reset = _configuration.isStatisticsReportResetEnabled(); + + /* add a timer task to report statistics if generation is enabled for broker or virtualhosts */ + if (report > 0L && (broker || virtualhost)) + { + _reportingTimer = new Timer("Statistics-Reporting", true); + + class StatisticsReportingTask extends TimerTask + { + private final int DELIVERED = 0; + private final int RECEIVED = 1; + + public void run() + { + CurrentActor.set(new AbstractActor(ApplicationRegistry.getInstance().getRootMessageLogger()) { + public String getLogMessage() + { + return "[" + Thread.currentThread().getName() + "] "; + } + }); + + if (broker) + { + CurrentActor.get().message(BrokerMessages.STATS_DATA(DELIVERED, _dataDelivered.getPeak() / 1024.0, _dataDelivered.getTotal())); + CurrentActor.get().message(BrokerMessages.STATS_MSGS(DELIVERED, _messagesDelivered.getPeak(), _messagesDelivered.getTotal())); + CurrentActor.get().message(BrokerMessages.STATS_DATA(RECEIVED, _dataReceived.getPeak() / 1024.0, _dataReceived.getTotal())); + CurrentActor.get().message(BrokerMessages.STATS_MSGS(RECEIVED, _messagesReceived.getPeak(), _messagesReceived.getTotal())); + } + + if (virtualhost) + { + for (VirtualHost vhost : getVirtualHostRegistry().getVirtualHosts()) + { + String name = vhost.getName(); + StatisticsCounter dataDelivered = vhost.getDataDeliveryStatistics(); + StatisticsCounter messagesDelivered = vhost.getMessageDeliveryStatistics(); + StatisticsCounter dataReceived = vhost.getDataReceiptStatistics(); + StatisticsCounter messagesReceived = vhost.getMessageReceiptStatistics(); + + CurrentActor.get().message(VirtualHostMessages.STATS_DATA(name, DELIVERED, dataDelivered.getPeak() / 1024.0, dataDelivered.getTotal())); + CurrentActor.get().message(VirtualHostMessages.STATS_MSGS(name, DELIVERED, messagesDelivered.getPeak(), messagesDelivered.getTotal())); + CurrentActor.get().message(VirtualHostMessages.STATS_DATA(name, RECEIVED, dataReceived.getPeak() / 1024.0, dataReceived.getTotal())); + CurrentActor.get().message(VirtualHostMessages.STATS_MSGS(name, RECEIVED, messagesReceived.getPeak(), messagesReceived.getTotal())); + } + } + + if (reset) + { + resetStatistics(); + } + + CurrentActor.remove(); + } + } + + _reportingTimer.scheduleAtFixedRate(new StatisticsReportingTask(), + report / 2, + report); + } + } + + public static IApplicationRegistry getInstance() + { + return getInstance(DEFAULT_INSTANCE); + } + + public static IApplicationRegistry getInstance(int instanceID) + { + synchronized (IApplicationRegistry.class) + { + IApplicationRegistry instance = _instanceMap.get(instanceID); + + if (instance == null) + { + throw new IllegalStateException("Application Registry (" + instanceID + ") not created"); + } + else + { + return instance; + } + } + } + + /** + * Close non-null Closeable items and log any errors + * @param close + */ + private void close(Closeable close) + { + try + { + if (close != null) + { + close.close(); + } + } + catch (Throwable e) + { + _logger.error("Error thrown whilst closing " + close.getClass().getSimpleName(), e); + } + } + + + public void close() + { + if (_logger.isInfoEnabled()) + { + _logger.info("Shutting down ApplicationRegistry:" + this); + } + + //Stop Statistics Reporting + if (_reportingTimer != null) + { + _reportingTimer.cancel(); + } + + //Stop incoming connections + unbind(); + + //Shutdown virtualhosts + close(_virtualHostRegistry); + +// close(_accessManager); +// +// close(_databaseManager); + + close(_authenticationManager); + + close(_managedObjectRegistry); + + close(_qmfService); + + close(_pluginManager); + + CurrentActor.get().message(BrokerMessages.STOPPED()); + } + + private void unbind() + { + synchronized (_acceptors) + { + for (InetSocketAddress bindAddress : _acceptors.keySet()) + { + QpidAcceptor acceptor = _acceptors.get(bindAddress); + + try + { + acceptor.getNetworkDriver().close(); + } + catch (Throwable e) + { + _logger.error("Unable to close network driver due to:" + e.getMessage()); + } + + CurrentActor.get().message(BrokerMessages.SHUTTING_DOWN(acceptor.toString(), bindAddress.getPort())); + } + } + } + + public ServerConfiguration getConfiguration() + { + return _configuration; + } + + public void addAcceptor(InetSocketAddress bindAddress, QpidAcceptor acceptor) + { + synchronized (_acceptors) + { + _acceptors.put(bindAddress, acceptor); + } + } + + public VirtualHostRegistry getVirtualHostRegistry() + { + return _virtualHostRegistry; + } + + public SecurityManager getSecurityManager() + { + return _securityManager; + } + + public ManagedObjectRegistry getManagedObjectRegistry() + { + return _managedObjectRegistry; + } + + public PrincipalDatabaseManager getDatabaseManager() + { + return _databaseManager; + } + + public AuthenticationManager getAuthenticationManager() + { + return _authenticationManager; + } + + public PluginManager getPluginManager() + { + return _pluginManager; + } + + public ConfigurationManager getConfigurationManager() + { + return _configurationManager; + } + + public RootMessageLogger getRootMessageLogger() + { + return _rootMessageLogger; + } + + public RootMessageLogger getCompositeStartupMessageLogger() + { + return _startupMessageLogger; + } + + public UUID getBrokerId() + { + return _brokerId; + } + + public QMFService getQMFService() + { + return _qmfService; + } + + public BrokerConfig getBroker() + { + return _broker; + } + + public void setBroker(final BrokerConfig broker) + { + _broker = broker; + } + + public VirtualHost createVirtualHost(final VirtualHostConfiguration vhostConfig) throws Exception + { + VirtualHostImpl virtualHost = new VirtualHostImpl(this, vhostConfig); + _virtualHostRegistry.registerVirtualHost(virtualHost); + getBroker().addVirtualHost(virtualHost); + return virtualHost; + } + + public void registerMessageDelivered(long messageSize) + { + if (isStatisticsEnabled()) + { + _messagesDelivered.registerEvent(1L); + _dataDelivered.registerEvent(messageSize); + } + } + + public void registerMessageReceived(long messageSize, long timestamp) + { + if (isStatisticsEnabled()) + { + _messagesReceived.registerEvent(1L, timestamp); + _dataReceived.registerEvent(messageSize, timestamp); + } + } + + public StatisticsCounter getMessageReceiptStatistics() + { + return _messagesReceived; + } + + public StatisticsCounter getDataReceiptStatistics() + { + return _dataReceived; + } + + public StatisticsCounter getMessageDeliveryStatistics() + { + return _messagesDelivered; + } + + public StatisticsCounter getDataDeliveryStatistics() + { + return _dataDelivered; + } + + public void resetStatistics() + { + _messagesDelivered.reset(); + _dataDelivered.reset(); + _messagesReceived.reset(); + _dataReceived.reset(); + + for (VirtualHost vhost : _virtualHostRegistry.getVirtualHosts()) + { + vhost.resetStatistics(); + } + } + + public void initialiseStatistics() + { + setStatisticsEnabled(!StatisticsCounter.DISABLE_STATISTICS && + getConfiguration().isStatisticsGenerationBrokerEnabled()); + + _messagesDelivered = new StatisticsCounter("messages-delivered"); + _dataDelivered = new StatisticsCounter("bytes-delivered"); + _messagesReceived = new StatisticsCounter("messages-received"); + _dataReceived = new StatisticsCounter("bytes-received"); + } + + public boolean isStatisticsEnabled() + { + return _statisticsEnabled; + } + + public void setStatisticsEnabled(boolean enabled) + { + _statisticsEnabled = enabled; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/BrokerConfigAdapter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/BrokerConfigAdapter.java new file mode 100644 index 0000000000..4a4253153c --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/BrokerConfigAdapter.java @@ -0,0 +1,161 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.registry; + +import org.apache.qpid.server.configuration.*; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.common.QpidProperties; + +import java.util.UUID; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class BrokerConfigAdapter implements BrokerConfig +{ + private final IApplicationRegistry _instance; + private SystemConfig _system; + + private final Map<UUID, VirtualHostConfig> _vhosts = new ConcurrentHashMap<UUID, VirtualHostConfig>(); + private final long _createTime = System.currentTimeMillis(); + private UUID _id; + private String _federationTag; + + public BrokerConfigAdapter(final IApplicationRegistry instance) + { + _instance = instance; + _id = instance.getConfigStore().createId(); + _federationTag = UUID.randomUUID().toString(); + } + + public void setSystem(final SystemConfig system) + { + _system = system; + } + + public SystemConfig getSystem() + { + return _system; + } + + public Integer getPort() + { + List ports = _instance.getConfiguration().getPorts(); + if(ports.size() > 0) + { + return Integer.valueOf(ports.get(0).toString()); + } + else + { + return 0; + } + } + + public Integer getWorkerThreads() + { + return _instance.getConfiguration().getProcessors(); + } + + public Integer getMaxConnections() + { + return 0; + } + + public Integer getConnectionBacklogLimit() + { + return 0; + } + + public Long getStagingThreshold() + { + return 0L; + } + + public Integer getManagementPublishInterval() + { + return 10000; + } + + public String getVersion() + { + return QpidProperties.getReleaseVersion() + " [Build: " + QpidProperties.getBuildVersion() + "]"; + } + + public String getDataDirectory() + { + return _instance.getConfiguration().getQpidWork(); + } + + public void addVirtualHost(final VirtualHostConfig virtualHost) + { + virtualHost.setBroker(this); + _vhosts.put(virtualHost.getId(), virtualHost); + getConfigStore().addConfiguredObject(virtualHost); + + } + + private ConfigStore getConfigStore() + { + return _instance.getConfigStore(); + } + + public long getCreateTime() + { + return _createTime; + } + + public void createBrokerConnection(final String transport, + final String host, + final int port, + final boolean durable, + final String authMechanism, + final String username, + final String password) + { + VirtualHost vhost = _instance.getVirtualHostRegistry().getDefaultVirtualHost(); + vhost.createBrokerConnection(transport, host, port, "", durable, authMechanism, username, password); + } + + public UUID getId() + { + return _id; + } + + public BrokerConfigType getConfigType() + { + return BrokerConfigType.getInstance(); + } + + public ConfiguredObject getParent() + { + return _system; + } + + public boolean isDurable() + { + return false; + } + + public String getFederationTag() + { + return _federationTag; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java new file mode 100644 index 0000000000..ff2a8c959b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/ConfigurationFileApplicationRegistry.java @@ -0,0 +1,69 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.registry; + +import java.io.File; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.AMQException; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.logging.actors.BrokerActor; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.management.JMXManagedObjectRegistry; +import org.apache.qpid.server.management.NoopManagedObjectRegistry; + +public class ConfigurationFileApplicationRegistry extends ApplicationRegistry +{ + public ConfigurationFileApplicationRegistry(File configurationURL) throws ConfigurationException + { + super(new ServerConfiguration(configurationURL)); + } + + @Override + public void close() + { + //Set the Actor for Broker Shutdown + CurrentActor.set(new BrokerActor(_rootMessageLogger)); + try + { + super.close(); + } + finally + { + CurrentActor.remove(); + } + } + + + @Override + protected void initialiseManagedObjectRegistry() throws AMQException + { + if (_configuration.getManagementEnabled()) + { + _managedObjectRegistry = new JMXManagedObjectRegistry(); + } + else + { + _managedObjectRegistry = new NoopManagedObjectRegistry(); + } + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java new file mode 100644 index 0000000000..0ef55097ce --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/registry/IApplicationRegistry.java @@ -0,0 +1,103 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.registry; + +import java.net.InetSocketAddress; +import java.util.UUID; + +import org.apache.qpid.qmf.QMFService; +import org.apache.qpid.server.configuration.BrokerConfig; +import org.apache.qpid.server.configuration.ConfigStore; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.configuration.ConfigurationManager; +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.management.ManagedObjectRegistry; +import org.apache.qpid.server.plugins.PluginManager; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.security.auth.database.PrincipalDatabaseManager; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.stats.StatisticsGatherer; +import org.apache.qpid.server.transport.QpidAcceptor; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; + +public interface IApplicationRegistry extends StatisticsGatherer +{ + /** + * Initialise the application registry. All initialisation must be done in this method so that any components + * that need access to the application registry itself for initialisation are able to use it. Attempting to + * initialise in the constructor will lead to failures since the registry reference will not have been set. + * @param instanceID the instanceID that we can use to identify this AR. + */ + void initialise(int instanceID) throws Exception; + + /** + * Shutdown this Registry + */ + void close(); + + /** + * Get the low level configuration. For use cases where the configured object approach is not required + * you can get the complete configuration information. + * @return a Commons Configuration instance + */ + ServerConfiguration getConfiguration(); + + ManagedObjectRegistry getManagedObjectRegistry(); + + PrincipalDatabaseManager getDatabaseManager(); + + AuthenticationManager getAuthenticationManager(); + + VirtualHostRegistry getVirtualHostRegistry(); + + SecurityManager getSecurityManager(); + + PluginManager getPluginManager(); + + ConfigurationManager getConfigurationManager(); + + RootMessageLogger getRootMessageLogger(); + + /** + * Register any acceptors for this registry + * @param bindAddress The address that the acceptor has been bound with + * @param acceptor The acceptor in use + */ + void addAcceptor(InetSocketAddress bindAddress, QpidAcceptor acceptor); + + public UUID getBrokerId(); + + QMFService getQMFService(); + + void setBroker(BrokerConfig broker); + + BrokerConfig getBroker(); + + VirtualHost createVirtualHost(VirtualHostConfiguration vhostConfig) throws Exception; + + ConfigStore getConfigStore(); + + void setConfigStore(ConfigStore store); + + void initialiseStatisticsReporting(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/AbstractPlugin.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/AbstractPlugin.java new file mode 100644 index 0000000000..ff80499bc2 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/AbstractPlugin.java @@ -0,0 +1,53 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.log4j.Logger; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.security.access.ObjectProperties; +import org.apache.qpid.server.security.access.ObjectType; +import org.apache.qpid.server.security.access.Operation; + +/** + * This is intended as the parent for all simple plugins. + */ +public abstract class AbstractPlugin implements SecurityPlugin +{ + protected final Logger _logger = Logger.getLogger(getClass()); + + protected ConfigurationPlugin _config; + + public Result getDefault() + { + return Result.ABSTAIN; + } + + public abstract Result access(ObjectType object, Object instance); + + public abstract Result authorise(Operation operation, ObjectType object, ObjectProperties properties); + + public void configure(ConfigurationPlugin config) + { + _config = config; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/AbstractProxyPlugin.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/AbstractProxyPlugin.java new file mode 100644 index 0000000000..8b5ff6781d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/AbstractProxyPlugin.java @@ -0,0 +1,132 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security; + +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.server.security.access.ObjectProperties; +import org.apache.qpid.server.security.access.ObjectType; +import org.apache.qpid.server.security.access.Operation; + +/** + * This {@link SecurityPlugin} proxies the authorise calls to a serries of methods, one per {@link Operation}. + * + * Plugins that extend this class should override the relevant authorise method and implement their own + * {@link #setConfiguration(Configuration)} method. + */ +public abstract class AbstractProxyPlugin extends AbstractPlugin +{ + public Result authoriseConsume(ObjectType object, ObjectProperties properties) + { + return getDefault(); + } + + public Result authorisePublish(ObjectType object, ObjectProperties properties) + { + return getDefault(); + } + + public Result authoriseCreate(ObjectType object, ObjectProperties properties) + { + return getDefault(); + } + + public Result authoriseAccess(ObjectType object, ObjectProperties properties) + { + return getDefault(); + } + + public Result authoriseBind(ObjectType object, ObjectProperties properties) + { + return getDefault(); + } + + public Result authoriseUnbind(ObjectType object, ObjectProperties properties) + { + return getDefault(); + } + + public Result authoriseDelete(ObjectType object, ObjectProperties properties) + { + return getDefault(); + } + + public Result authorisePurge(ObjectType object, ObjectProperties properties) + { + return getDefault(); + } + + public Result authoriseExecute(ObjectType object, ObjectProperties properties) + { + return getDefault(); + } + + public Result authoriseUpdate(ObjectType object, ObjectProperties properties) + { + return getDefault(); + } + + public Result accessVirtualhost(Object instance) + { + return getDefault(); + } + + @Override + public Result access(ObjectType objectType, Object instance) + { + switch (objectType) + { + case VIRTUALHOST: + return accessVirtualhost(instance); + } + + return getDefault(); + } + + @Override + public Result authorise(Operation operation, ObjectType objectType, ObjectProperties properties) + { + switch (operation) + { + case CONSUME: + return authoriseConsume(objectType, properties); + case PUBLISH: + return authorisePublish(objectType, properties); + case CREATE: + return authoriseCreate(objectType, properties); + case ACCESS: + return authoriseAccess(objectType, properties); + case BIND: + return authoriseBind(objectType, properties); + case UNBIND: + return authoriseUnbind(objectType, properties); + case DELETE: + return authoriseDelete(objectType, properties); + case PURGE: + return authorisePurge(objectType, properties); + case EXECUTE: + return authoriseExecute(objectType, properties); + case UPDATE: + return authoriseUpdate(objectType, properties); + } + + return getDefault(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/PrincipalHolder.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/PrincipalHolder.java new file mode 100755 index 0000000000..7e93623cab --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/PrincipalHolder.java @@ -0,0 +1,29 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security; + +import java.security.Principal; + +public interface PrincipalHolder +{ + /** @return a Principal that was used to authorized this session */ + Principal getPrincipal(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/Result.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/Result.java new file mode 100644 index 0000000000..f79721799e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/Result.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security; + +/** + * The result of a security plugin decision, normally {@link #ALLOWED} or {@link #DENIED}. + */ +public enum Result +{ + /** + * The request is allowed. + */ + ALLOWED, + + /** + * The request is denied. + */ + DENIED, + + /** + * Indicates that a plugin does not handle a particular type of request. + */ + ABSTAIN, + + /** + * A plugin instance cannot make a decision on a request it is able to handle, + * and another instance of the plugin should be checked. + */ + DEFER; +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/SecurityManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/SecurityManager.java new file mode 100755 index 0000000000..f18c327692 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/SecurityManager.java @@ -0,0 +1,416 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security; + +import static org.apache.qpid.server.security.access.ObjectType.*; +import static org.apache.qpid.server.security.access.Operation.*; + +import java.net.SocketAddress; +import java.security.Principal; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.log4j.Logger; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.plugins.PluginManager; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.security.access.ObjectProperties; +import org.apache.qpid.server.security.access.Operation; +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; + +/** + * The security manager contains references to all loaded {@link SecurityPlugin}s and delegates security decisions to them based + * on virtual host name. The plugins can be external <em>OSGi</em> .jar files that export the required classes or just internal + * objects for simpler plugins. + * + * @see SecurityPlugin + */ +public class SecurityManager +{ + private static final Logger _logger = Logger.getLogger(SecurityManager.class); + + /** Container for the {@link Principal} that is using to this thread. */ + private static final ThreadLocal<Principal> _principal = new ThreadLocal<Principal>(); + + private PluginManager _pluginManager; + private Map<String, SecurityPluginFactory> _pluginFactories = new HashMap<String, SecurityPluginFactory>(); + private Map<String, SecurityPlugin> _globalPlugins = new HashMap<String, SecurityPlugin>(); + private Map<String, SecurityPlugin> _hostPlugins = new HashMap<String, SecurityPlugin>(); + + public static class SecurityConfiguration extends ConfigurationPlugin + { + public static final ConfigurationPluginFactory FACTORY = new ConfigurationPluginFactory() + { + public ConfigurationPlugin newInstance(String path, Configuration config) throws ConfigurationException + { + ConfigurationPlugin instance = new SecurityConfiguration(); + instance.setConfiguration(path, config); + return instance; + } + + public List<String> getParentPaths() + { + return Arrays.asList("security", "virtualhosts.virtualhost.security"); + } + }; + + @Override + public String[] getElementsProcessed() + { + return new String[]{"security"}; + } + + public void validateConfiguration() throws ConfigurationException + { + if (_configuration.isEmpty()) + { + throw new ConfigurationException("security section is incomplete, no elements found."); + } + } + } + + + public SecurityManager(SecurityManager parent) throws ConfigurationException + { + _pluginManager = parent._pluginManager; + _pluginFactories = parent._pluginFactories; + + // our global plugins are the parent's host plugins + _globalPlugins = parent._hostPlugins; + } + + public SecurityManager(ConfigurationPlugin configuration, PluginManager manager) throws ConfigurationException + { + this(configuration, manager, null); + } + + public SecurityManager(ConfigurationPlugin configuration, PluginManager manager, SecurityPluginFactory plugin) throws ConfigurationException + { + _pluginManager = manager; + if (manager == null) // No plugin manager, no plugins + { + return; + } + + _pluginFactories = _pluginManager.getSecurityPlugins(); + if (plugin != null) + { + _pluginFactories.put(plugin.getPluginName(), plugin); + } + + configureHostPlugins(configuration); + } + + public static Principal getThreadPrincipal() + { + return _principal.get(); + } + + public static void setThreadPrincipal(Principal principal) + { + _principal.set(principal); + } + + public static void setThreadPrincipal(String authId) + { + setThreadPrincipal(new UsernamePrincipal(authId)); + } + + public void configureHostPlugins(ConfigurationPlugin hostConfig) throws ConfigurationException + { + _hostPlugins = configurePlugins(hostConfig); + } + + public void configureGlobalPlugins(ConfigurationPlugin configuration) throws ConfigurationException + { + _globalPlugins = configurePlugins(configuration); + } + + public Map<String, SecurityPlugin> configurePlugins(ConfigurationPlugin hostConfig) throws ConfigurationException + { + Map<String, SecurityPlugin> plugins = new HashMap<String, SecurityPlugin>(); + SecurityConfiguration securityConfig = hostConfig.getConfiguration(SecurityConfiguration.class.getName()); + + // If we have no security Configuration then there is nothing to configure. + if (securityConfig != null) + { + for (SecurityPluginFactory<?> factory : _pluginFactories.values()) + { + SecurityPlugin plugin = factory.newInstance(securityConfig); + if (plugin != null) + { + plugins.put(factory.getPluginName(), plugin); + } + } + } + return plugins; + } + + public void addHostPlugin(SecurityPlugin plugin) + { + _hostPlugins.put(plugin.getClass().getName(), plugin); + } + + public static Logger getLogger() + { + return _logger; + } + + private abstract class AccessCheck + { + abstract Result allowed(SecurityPlugin plugin); + } + + private boolean checkAllPlugins(AccessCheck checker) + { + HashMap<String, SecurityPlugin> remainingPlugins = new HashMap<String, SecurityPlugin>(_globalPlugins); + + for (Entry<String, SecurityPlugin> hostEntry : _hostPlugins.entrySet()) + { + // Create set of global only plugins + SecurityPlugin globalPlugin = remainingPlugins.get(hostEntry.getKey()); + if (globalPlugin != null) + { + remainingPlugins.remove(hostEntry.getKey()); + } + + Result host = checker.allowed(hostEntry.getValue()); + + if (host == Result.DENIED) + { + // Something vetoed the access, we're done + return false; + } + + // host allow overrides global allow, so only check global on abstain or defer + if (host != Result.ALLOWED) + { + if (globalPlugin == null) + { + if (host == Result.DEFER) + { + host = hostEntry.getValue().getDefault(); + } + if (host == Result.DENIED) + { + return false; + } + } + else + { + Result global = checker.allowed(globalPlugin); + if (global == Result.DEFER) + { + global = globalPlugin.getDefault(); + } + if (global == Result.ABSTAIN && host == Result.DEFER) + { + global = hostEntry.getValue().getDefault(); + } + if (global == Result.DENIED) + { + return false; + } + } + } + } + + for (SecurityPlugin plugin : remainingPlugins.values()) + { + Result remaining = checker.allowed(plugin); + if (remaining == Result.DEFER) + { + remaining = plugin.getDefault(); + } + if (remaining == Result.DENIED) + { + return false; + } + } + + // getting here means either allowed or abstained from all plugins + return true; + } + + public boolean authoriseBind(final Exchange exch, final AMQQueue queue, final AMQShortString routingKey) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(SecurityPlugin plugin) + { + return plugin.authorise(BIND, EXCHANGE, new ObjectProperties(exch, queue, routingKey)); + } + }); + } + + // TODO not implemented yet, awaiting consensus + public boolean authoriseObject(final String packageName, final String className) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(SecurityPlugin plugin) + { + ObjectProperties properties = new ObjectProperties(); + properties.put(ObjectProperties.Property.PACKAGE, packageName); + properties.put(ObjectProperties.Property.CLASS, className); + return plugin.authorise(ACCESS, OBJECT, properties); + } + }); + } + + public boolean authoriseMethod(final Operation operation, final String componentName, final String methodName) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(SecurityPlugin plugin) + { + ObjectProperties properties = new ObjectProperties(); + properties.setName(methodName); + if (componentName != null) + { + // Only set the property if there is a component name + properties.put(ObjectProperties.Property.COMPONENT, componentName); + } + return plugin.authorise(operation, METHOD, properties); + } + }); + } + + public boolean accessVirtualhost(final String vhostname, final SocketAddress remoteAddress) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(SecurityPlugin plugin) + { + return plugin.access(VIRTUALHOST, remoteAddress); + } + }); + } + + public boolean authoriseConsume(final AMQQueue queue) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(SecurityPlugin plugin) + { + return plugin.authorise(CONSUME, QUEUE, new ObjectProperties(queue)); + } + }); + } + + public boolean authoriseConsume(final boolean exclusive, final boolean noAck, final boolean noLocal, final boolean nowait, final AMQQueue queue) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(SecurityPlugin plugin) + { + return plugin.authorise(CONSUME, QUEUE, new ObjectProperties(exclusive, noAck, noLocal, nowait, queue)); + } + }); + } + + public boolean authoriseCreateExchange(final Boolean autoDelete, final Boolean durable, final AMQShortString exchangeName, + final Boolean internal, final Boolean nowait, final Boolean passive, final AMQShortString exchangeType) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(SecurityPlugin plugin) + { + return plugin.authorise(CREATE, EXCHANGE, new ObjectProperties(autoDelete, durable, exchangeName, + internal, nowait, passive, exchangeType)); + } + }); + } + + public boolean authoriseCreateQueue(final Boolean autoDelete, final Boolean durable, final Boolean exclusive, + final Boolean nowait, final Boolean passive, final AMQShortString queueName, final String owner) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(SecurityPlugin plugin) + { + return plugin.authorise(CREATE, QUEUE, new ObjectProperties(autoDelete, durable, exclusive, nowait, passive, queueName, owner)); + } + }); + } + + public boolean authoriseDelete(final AMQQueue queue) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(SecurityPlugin plugin) + { + return plugin.authorise(DELETE, QUEUE, new ObjectProperties(queue)); + } + }); + } + + public boolean authoriseDelete(final Exchange exchange) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(SecurityPlugin plugin) + { + return plugin.authorise(DELETE, EXCHANGE, new ObjectProperties(exchange.getName())); + } + }); + } + + public boolean authorisePublish(final boolean immediate, final String routingKey, final String exchangeName) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(SecurityPlugin plugin) + { + return plugin.authorise(PUBLISH, EXCHANGE, new ObjectProperties(exchangeName, routingKey, immediate)); + } + }); + } + + public boolean authorisePurge(final AMQQueue queue) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(SecurityPlugin plugin) + { + return plugin.authorise(PURGE, QUEUE, new ObjectProperties(queue)); + } + }); + } + + public boolean authoriseUnbind(final Exchange exch, final AMQShortString routingKey, final AMQQueue queue) + { + return checkAllPlugins(new AccessCheck() + { + Result allowed(SecurityPlugin plugin) + { + return plugin.authorise(UNBIND, EXCHANGE, new ObjectProperties(exch, queue, routingKey)); + } + }); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/SecurityPlugin.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/SecurityPlugin.java new file mode 100644 index 0000000000..c3c06bf206 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/SecurityPlugin.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security; + +import org.apache.qpid.server.plugins.Plugin; +import org.apache.qpid.server.security.access.ObjectProperties; +import org.apache.qpid.server.security.access.ObjectType; +import org.apache.qpid.server.security.access.Operation; + +/** + * The two methods, {@link #access(ObjectType, Object)} and {@link #authorise(Operation, ObjectType, ObjectProperties)}, + * return the {@link Result} of the security decision, which may be to {@link Result#ABSTAIN} if no decision is made + * by this plugin. + */ +public interface SecurityPlugin extends Plugin +{ + /** + * Default result for {@link #access(ObjectType, Object)} or {@link #authorise(Operation, ObjectType, ObjectProperties)}. + */ + Result getDefault(); + + /** + * Authorise access granted to an object instance. + */ + Result access(ObjectType objectType, Object instance); + + /** + * Authorise an operation on an object defined by a set of properties. + */ + Result authorise(Operation operation, ObjectType objectType, ObjectProperties properties); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/SecurityPluginActivator.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/SecurityPluginActivator.java new file mode 100644 index 0000000000..5ee7833c4c --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/SecurityPluginActivator.java @@ -0,0 +1,74 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +/** + * An OSGi {@link BundleActivator} that loads a {@link SecurityPluginFactory}. + */ +public abstract class SecurityPluginActivator implements BundleActivator +{ + private static final Logger _logger = Logger.getLogger(SecurityPluginActivator.class); + + private SecurityPluginFactory _factory; + private ConfigurationPluginFactory _config; + private BundleContext _ctx; + private String _bundleName; + + /** Implement this to return the factory this plugin activates. */ + public abstract SecurityPluginFactory getFactory(); + + /** Implement this to return the factory this plugin activates. */ + public abstract ConfigurationPluginFactory getConfigurationFactory(); + + /** + * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext) + */ + public void start(BundleContext ctx) throws Exception + { + _ctx = ctx; + _factory = getFactory(); + _config = getConfigurationFactory(); + _bundleName = ctx.getBundle().getSymbolicName(); + + // register the service + _logger.info("Registering security plugin: " + _bundleName); + _ctx.registerService(SecurityPluginFactory.class.getName(), _factory, null); + _ctx.registerService(ConfigurationPluginFactory.class.getName(), _config, null); + } + + /** + * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) + */ + public void stop(BundleContext context) throws Exception + { + _logger.info("Stopping security plugin: " + _bundleName); + + // null object references + _factory = null; + _config = null; + _ctx = null; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/SecurityPluginFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/SecurityPluginFactory.java new file mode 100644 index 0000000000..fe81cba282 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/SecurityPluginFactory.java @@ -0,0 +1,30 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security; + +import org.apache.qpid.server.plugins.PluginFactory; + +/** + * The factory that generates instances of security plugins. Usually implemented as a static member class in the plugin itself. + */ +public interface SecurityPluginFactory<S extends SecurityPlugin> extends PluginFactory<S> +{ +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectProperties.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectProperties.java new file mode 100644 index 0000000000..70a9ea5356 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectProperties.java @@ -0,0 +1,318 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.access; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.queue.AMQQueue; + +/** + * An set of properties for an access control v2 rule {@link ObjectType}. + * + * The {@link #matches(ObjectProperties)} method is intended to be used when determining precedence of rules, and + * {@link #equals(Object)} and {@link #hashCode()} are intended for use in maps. This is due to the wildcard matching + * described above. + */ +public class ObjectProperties extends HashMap<ObjectProperties.Property, String> +{ + /** serialVersionUID */ + private static final long serialVersionUID = -1356019341374170495L; + + public static final String STAR= "*"; + + public static final ObjectProperties EMPTY = new ObjectProperties(); + + public enum Property + { + ROUTING_KEY, + NAME, + QUEUE_NAME, + OWNER, + TYPE, + ALTERNATE, + IMMEDIATE, + INTERNAL, + NO_WAIT, + NO_LOCAL, + NO_ACK, + PASSIVE, + DURABLE, + EXCLUSIVE, + TEMPORARY, + AUTO_DELETE, + COMPONENT, + PACKAGE, + CLASS; + + public static Property parse(String text) + { + for (Property property : values()) + { + if (property.getName().equalsIgnoreCase(text)) + { + return property; + } + } + throw new IllegalArgumentException("Not a valid property: " + text); + } + + public String getName() + { + return StringUtils.remove(name(), '_').toLowerCase(); + } + + public static List<String> getPropertyNames() + { + List<String> properties = new ArrayList<String>(); + for (Property property : values()) + { + properties.add(property.getName()); + } + return properties; + } + } + + public static List<String> getAllPropertyNames() + { + List<String> properties = new ArrayList<String>(); + for (Property property : Property.values()) + { + properties.add(StringUtils.remove(property.name(), '_').toLowerCase()); + } + return properties; + } + + public ObjectProperties() + { + super(); + } + + public ObjectProperties(ObjectProperties copy) + { + super(); + + putAll(copy); + } + + public ObjectProperties(String name) + { + super(); + + setName(name); + } + + + public ObjectProperties(AMQShortString name) + { + super(); + + setName(name); + } + + public ObjectProperties(AMQQueue queue) + { + super(); + + setName(queue.getName()); + + put(Property.AUTO_DELETE, queue.isAutoDelete()); + put(Property.TEMPORARY, queue.isAutoDelete()); + put(Property.DURABLE, queue.isDurable()); + put(Property.EXCLUSIVE, queue.isExclusive()); + if (queue.getAlternateExchange() != null) + { + put(Property.ALTERNATE, queue.getAlternateExchange().getName()); + } + if (queue.getOwner() != null) + { + put(Property.OWNER, queue.getOwner()); + } + else if (queue.getPrincipalHolder() != null) + { + put(Property.OWNER, queue.getPrincipalHolder().getPrincipal().getName()); + } + } + + public ObjectProperties(Exchange exch, AMQQueue queue, AMQShortString routingKey) + { + this(queue); + + setName(exch.getName()); + + put(Property.QUEUE_NAME, queue.getName()); + put(Property.ROUTING_KEY, routingKey); + } + + public ObjectProperties(Exchange exch, AMQShortString routingKey) + { + this(exch.getName(), routingKey.asString()); + } + + public ObjectProperties(String exchangeName, String routingKey, Boolean immediate) + { + this(exchangeName, routingKey); + + put(Property.IMMEDIATE, immediate); + } + + public ObjectProperties(String exchangeName, String routingKey) + { + super(); + + setName(exchangeName); + + put(Property.ROUTING_KEY, routingKey); + } + + public ObjectProperties(Boolean autoDelete, Boolean durable, AMQShortString exchangeName, + Boolean internal, Boolean nowait, Boolean passive, AMQShortString exchangeType) + { + super(); + + setName(exchangeName); + + put(Property.AUTO_DELETE, autoDelete); + put(Property.TEMPORARY, autoDelete); + put(Property.DURABLE, durable); + put(Property.INTERNAL, internal); + put(Property.NO_WAIT, nowait); + put(Property.PASSIVE, passive); + put(Property.TYPE, exchangeType); + } + + public ObjectProperties(Boolean autoDelete, Boolean durable, Boolean exclusive, Boolean nowait, Boolean passive, + AMQShortString queueName, String owner) + { + super(); + + setName(queueName); + + put(Property.AUTO_DELETE, autoDelete); + put(Property.TEMPORARY, autoDelete); + put(Property.DURABLE, durable); + put(Property.EXCLUSIVE, exclusive); + put(Property.NO_WAIT, nowait); + put(Property.PASSIVE, passive); + put(Property.OWNER, owner); + } + + public ObjectProperties(Boolean exclusive, Boolean noAck, Boolean noLocal, Boolean nowait, AMQQueue queue) + { + this(queue); + + put(Property.NO_LOCAL, noLocal); + put(Property.NO_ACK, noAck); + put(Property.EXCLUSIVE, exclusive); + put(Property.NO_WAIT, nowait); + } + + public List<String> getPropertyNames() + { + List<String> properties = new ArrayList<String>(); + for (Property property : keySet()) + { + properties.add(property.getName()); + } + return properties; + } + + public Boolean isSet(Property key) + { + return containsKey(key) && Boolean.valueOf(get(key)); + } + + public String getName() + { + return get(Property.NAME); + } + + public void setName(String name) + { + put(Property.NAME, name); + } + + public void setName(AMQShortString name) + { + put(Property.NAME, name); + } + + public String put(Property key, AMQShortString value) + { + return put(key, value == null ? "" : value.asString()); + } + + @Override + public String put(Property key, String value) + { + return super.put(key, value == null ? "" : value.trim()); + } + + public void put(Property key, Boolean value) + { + if (value != null) + { + super.put(key, Boolean.toString(value)); + } + } + + public boolean matches(ObjectProperties properties) + { + if (properties.keySet().isEmpty()) + { + return true; + } + + if (!keySet().containsAll(properties.keySet())) + { + return false; + } + + for (Map.Entry<Property,String> entry : properties.entrySet()) + { + Property key = entry.getKey(); + String ruleValue = entry.getValue(); + + String thisValue = get(key); + + if (!valueMatches(thisValue, ruleValue)) + { + return false; + } + } + + return true; + } + + private boolean valueMatches(String thisValue, String ruleValue) + { + return (StringUtils.isEmpty(ruleValue) + || StringUtils.equals(thisValue, ruleValue)) + || ruleValue.equals(STAR) + || (ruleValue.endsWith(STAR) + && thisValue != null + && thisValue.length() > ruleValue.length() + && thisValue.startsWith(ruleValue.substring(0, ruleValue.length() - 2))); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectType.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectType.java new file mode 100644 index 0000000000..7103b30283 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/ObjectType.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.access; + +import static org.apache.qpid.server.security.access.Operation.*; + +import java.util.EnumSet; +import java.util.Set; + +/** + * An enumeration of all possible object types that can form part of an access control v2 rule. + * + * Each object type is valid only for a certain set of {@link Operation}s, which are passed as a list to + * the constructor, and can be checked using the {@link #isAllowed(Operation)} method. + */ +public enum ObjectType +{ + ALL(Operation.ALL), + VIRTUALHOST(ACCESS), + QUEUE(CREATE, DELETE, PURGE, CONSUME), + TOPIC(CREATE, DELETE, PURGE, CONSUME), + EXCHANGE(ACCESS, CREATE, DELETE, BIND, UNBIND, PUBLISH), + LINK, // Not allowed in the Java broker + ROUTE, // Not allowed in the Java broker + METHOD(Operation.ALL, ACCESS, UPDATE, EXECUTE), + OBJECT(ACCESS); + + private EnumSet<Operation> _actions; + + private ObjectType() + { + _actions = EnumSet.noneOf(Operation.class); + } + + private ObjectType(Operation operation) + { + if (operation == Operation.ALL) + { + _actions = EnumSet.allOf(Operation.class); + } + else + { + _actions = EnumSet.of(operation); + } + } + + private ObjectType(Operation first, Operation...rest) + { + _actions = EnumSet.of(first, rest); + } + + public Set<Operation> getActions() + { + return _actions; + } + + public boolean isAllowed(Operation operation) + { + return _actions.contains(operation); + } + + public static ObjectType parse(String text) + { + for (ObjectType object : values()) + { + if (object.name().equalsIgnoreCase(text)) + { + return object; + } + } + throw new IllegalArgumentException("Not a valid object type: " + text); + } + + public String toString() + { + String name = name(); + return name.charAt(0) + name.substring(1).toLowerCase(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Operation.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Operation.java new file mode 100644 index 0000000000..06d7f8fd0c --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Operation.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.access; + +/** + * An enumeration of all possible actions that can form part of an access control v2 rule. + */ +public enum Operation +{ + ALL, + CONSUME, + PUBLISH, + CREATE, + ACCESS, + BIND, + UNBIND, + DELETE, + PURGE, + UPDATE, + EXECUTE; + + public static Operation parse(String text) + { + for (Operation operation : values()) + { + if (operation.name().equalsIgnoreCase(text)) + { + return operation; + } + } + throw new IllegalArgumentException("Not a valid operation: " + text); + } + + public String toString() + { + String name = name(); + return name.charAt(0) + name.substring(1).toLowerCase(); + } +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Permission.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Permission.java new file mode 100644 index 0000000000..49b3a331f9 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/Permission.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.access; + +import org.apache.commons.lang.StringUtils; + +/** + * An enumeration of all possible permissions that can be applied to an access control v2 rule. + */ +public enum Permission +{ + ALLOW, + ALLOW_LOG, + DENY, + DENY_LOG; + + public static Permission parse(String text) + { + + for (Permission permission : values()) + { + if (permission.name().equalsIgnoreCase(StringUtils.replaceChars(text, '-', '_'))) + { + return permission; + } + } + throw new IllegalArgumentException("Not a valid permission: " + text); + } +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AllowAll.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AllowAll.java new file mode 100644 index 0000000000..db18a89231 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/AllowAll.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.access.plugins; + +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory; +import org.apache.qpid.server.security.Result; +import org.apache.qpid.server.security.SecurityPluginFactory; + +/** Always allow. */ +public class AllowAll extends BasicPlugin +{ + public static class AllowAllConfiguration extends ConfigurationPlugin { + public static final ConfigurationPluginFactory FACTORY = new ConfigurationPluginFactory() + { + public List<String> getParentPaths() + { + return Arrays.asList("security.allow-all", "virtualhosts.virtualhost.security.allow-all"); + } + + public ConfigurationPlugin newInstance(String path, Configuration config) throws ConfigurationException + { + ConfigurationPlugin instance = new AllowAllConfiguration(); + instance.setConfiguration(path, config); + return instance; + } + }; + + public String[] getElementsProcessed() + { + return new String[] { "" }; + } + + public void validateConfiguration() throws ConfigurationException + { +// if (!_configuration.isEmpty()) +// { +// throw new ConfigurationException("allow-all section takes no elements."); +// } + } + + } + + public static final SecurityPluginFactory<AllowAll> FACTORY = new SecurityPluginFactory<AllowAll>() + { + public AllowAll newInstance(ConfigurationPlugin config) throws ConfigurationException + { + AllowAllConfiguration configuration = config.getConfiguration(AllowAllConfiguration.class.getName()); + + // If there is no configuration for this plugin then don't load it. + if (configuration == null) + { + return null; + } + + AllowAll plugin = new AllowAll(); + plugin.configure(configuration); + return plugin; + } + + public String getPluginName() + { + return AllowAll.class.getName(); + } + + public Class<AllowAll> getPluginClass() + { + return AllowAll.class; + } + }; + + @Override + public Result getDefault() + { + return Result.ALLOWED; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/BasicPlugin.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/BasicPlugin.java new file mode 100644 index 0000000000..f3161551dc --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/BasicPlugin.java @@ -0,0 +1,45 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.access.plugins; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.security.AbstractPlugin; +import org.apache.qpid.server.security.Result; +import org.apache.qpid.server.security.SecurityPlugin; +import org.apache.qpid.server.security.access.ObjectProperties; +import org.apache.qpid.server.security.access.ObjectType; +import org.apache.qpid.server.security.access.Operation; + +/** + * This {@link SecurityPlugin} simply abstains from all authorisation requests and ignores configuration. + */ +public abstract class BasicPlugin extends AbstractPlugin +{ + public Result access(ObjectType objectType, Object instance) + { + return getDefault(); + } + + public Result authorise(Operation operation, ObjectType objectType, ObjectProperties properties) + { + return getDefault(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/DenyAll.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/DenyAll.java new file mode 100644 index 0000000000..6c0fb1eaa4 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/DenyAll.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.access.plugins; + +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory; +import org.apache.qpid.server.security.Result; +import org.apache.qpid.server.security.SecurityPluginFactory; + +/** Always Deny. */ +public class DenyAll extends BasicPlugin +{ + public static class DenyAllConfiguration extends ConfigurationPlugin { + public static final ConfigurationPluginFactory FACTORY = new ConfigurationPluginFactory() + { + public List<String> getParentPaths() + { + return Arrays.asList("security.deny-all", "virtualhosts.virtualhost.security.deny-all"); + } + + public ConfigurationPlugin newInstance(String path, Configuration config) throws ConfigurationException + { + ConfigurationPlugin instance = new DenyAllConfiguration(); + instance.setConfiguration(path, config); + return instance; + } + }; + + public String[] getElementsProcessed() + { + return new String[] { "" }; + } + + public void validateConfiguration() throws ConfigurationException + { + if (!_configuration.isEmpty()) + { + throw new ConfigurationException("deny-all section takes no elements."); + } + } + + } + + public static final SecurityPluginFactory<DenyAll> FACTORY = new SecurityPluginFactory<DenyAll>() + { + public DenyAll newInstance(ConfigurationPlugin config) throws ConfigurationException + { + DenyAllConfiguration configuration = config.getConfiguration(DenyAllConfiguration.class.getName()); + + // If there is no configuration for this plugin then don't load it. + if (configuration == null) + { + return null; + } + + DenyAll plugin = new DenyAll(); + plugin.configure(configuration); + return plugin; + } + + public String getPluginName() + { + return DenyAll.class.getName(); + } + + public Class<DenyAll> getPluginClass() + { + return DenyAll.class; + } + }; + + @Override + public Result getDefault() + { + return Result.DENIED; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/LegacyAccess.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/LegacyAccess.java new file mode 100644 index 0000000000..bd99cdd1fa --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/access/plugins/LegacyAccess.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.access.plugins; + +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory; +import org.apache.qpid.server.security.SecurityPluginFactory; + +/** Always Abstain. */ +public class LegacyAccess extends BasicPlugin +{ + public static class LegacyAccessConfiguration extends ConfigurationPlugin { + public static final ConfigurationPluginFactory FACTORY = new ConfigurationPluginFactory() + { + public List<String> getParentPaths() + { + return Arrays.asList("security.jmx", "virtualhosts.virtualhost.security.jmx", + "security.msg-auth", "virtualhosts.virtualhost.security.msg-auth", + "security.principal-databases", "virtualhosts.virtualhost.security.principal-databases"); + } + + public ConfigurationPlugin newInstance(String path, Configuration config) throws ConfigurationException + { + ConfigurationPlugin instance = new LegacyAccessConfiguration(); + instance.setConfiguration(path, config); + return instance; + } + }; + + public String[] getElementsProcessed() + { + return new String[] { "" }; + } + } + + public static final SecurityPluginFactory<LegacyAccess> FACTORY = new SecurityPluginFactory<LegacyAccess>() + { + public LegacyAccess newInstance(ConfigurationPlugin config) throws ConfigurationException + { + LegacyAccessConfiguration configuration = config.getConfiguration(LegacyAccessConfiguration.class.getName()); + + // If there is no configuration for this plugin then don't load it. + if (configuration == null) + { + return null; + } + + LegacyAccess plugin = new LegacyAccess(); + plugin.configure(configuration); + return plugin; + } + + public String getPluginName() + { + return LegacyAccess.class.getName(); + } + + public Class<LegacyAccess> getPluginClass() + { + return LegacyAccess.class; + } + }; + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java new file mode 100644 index 0000000000..62967ef7eb --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/AuthenticationResult.java @@ -0,0 +1,61 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth; + +public class AuthenticationResult +{ + public enum AuthenticationStatus + { + SUCCESS, CONTINUE, ERROR + } + + public AuthenticationStatus status; + public byte[] challenge; + + private Exception cause; + + public AuthenticationResult(AuthenticationStatus status) + { + this(null, status, null); + } + + public AuthenticationResult(byte[] challenge, AuthenticationStatus status) + { + this(challenge, status, null); + } + + public AuthenticationResult(AuthenticationStatus error, Exception cause) + { + this(null, error, cause); + } + + public AuthenticationResult(byte[] challenge, AuthenticationStatus status, Exception cause) + { + this.status = status; + this.challenge = challenge; + this.cause = cause; + } + + public Exception getCause() + { + return cause; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java new file mode 100644 index 0000000000..5a92b33e43 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/Base64MD5PasswordFilePrincipalDatabase.java @@ -0,0 +1,567 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.database; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.security.auth.management.AMQUserManagementMBean; +import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HexInitialiser; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5HashedInitialiser; +import org.apache.qpid.util.FileUtils; + +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintStream; +import java.security.Principal; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.locks.ReentrantLock; +import java.util.regex.Pattern; + +/** + * Represents a user database where the account information is stored in a simple flat file. + * + * The file is expected to be in the form: username:password username1:password1 ... usernamen:passwordn + * + * where a carriage return separates each username/password pair. Passwords are assumed to be in plain text. + */ +public class Base64MD5PasswordFilePrincipalDatabase implements PrincipalDatabase +{ + private static final Logger _logger = Logger.getLogger(Base64MD5PasswordFilePrincipalDatabase.class); + + private File _passwordFile; + + private Pattern _regexp = Pattern.compile(":"); + + private Map<String, AuthenticationProviderInitialiser> _saslServers; + + AMQUserManagementMBean _mbean; + public static final String DEFAULT_ENCODING = "utf-8"; + private Map<String, HashedUser> _users = new HashMap<String, HashedUser>(); + private ReentrantLock _userUpdate = new ReentrantLock(); + + public Base64MD5PasswordFilePrincipalDatabase() + { + _saslServers = new HashMap<String, AuthenticationProviderInitialiser>(); + + /** + * Create Authenticators for MD5 Password file. + */ + + // Accept Plain incomming and hash it for comparison to the file. + CRAMMD5HashedInitialiser cram = new CRAMMD5HashedInitialiser(); + cram.initialise(this); + _saslServers.put(cram.getMechanismName(), cram); + + //Add the Hex initialiser + CRAMMD5HexInitialiser cramHex = new CRAMMD5HexInitialiser(); + cramHex.initialise(this); + _saslServers.put(cramHex.getMechanismName(), cramHex); + + //fixme The PDs should setup a PD Mangement MBean +// try +// { +// _mbean = new AMQUserManagementMBean(); +// _mbean.setPrincipalDatabase(this); +// } +// catch (JMException e) +// { +// _logger.warn("User management disabled as unable to create MBean:" + e); +// } + } + + public void setPasswordFile(String passwordFile) throws IOException + { + File f = new File(passwordFile); + _logger.info("PasswordFilePrincipalDatabase using file " + f.getAbsolutePath()); + _passwordFile = f; + if (!f.exists()) + { + throw new FileNotFoundException("Cannot find password file " + f); + } + if (!f.canRead()) + { + throw new FileNotFoundException("Cannot read password file " + f + + ". Check permissions."); + } + + loadPasswordFile(); + } + + /** + * SASL Callback Mechanism - sets the Password in the PasswordCallback based on the value in the PasswordFile + * If you want to change the password for a user, use updatePassword instead. + * + * @param principal The Principal to set the password for + * @param callback The PasswordCallback to call setPassword on + * + * @throws AccountNotFoundException If the Principal cannont be found in this Database + */ + public void setPassword(Principal principal, PasswordCallback callback) throws AccountNotFoundException + { + if (_passwordFile == null) + { + throw new AccountNotFoundException("Unable to locate principal since no password file was specified during initialisation"); + } + if (principal == null) + { + throw new IllegalArgumentException("principal must not be null"); + } + + char[] pwd = lookupPassword(principal.getName()); + + if (pwd != null) + { + callback.setPassword(pwd); + } + else + { + throw new AccountNotFoundException("No account found for principal " + principal); + } + } + + /** + * Used to verify that the presented Password is correct. Currently only used by Management Console + * + * @param principal The principal to authenticate + * @param password The password to check + * + * @return true if password is correct + * + * @throws AccountNotFoundException if the principal cannot be found + */ + public boolean verifyPassword(String principal, char[] password) throws AccountNotFoundException + { + char[] pwd = lookupPassword(principal); + + if (pwd == null) + { + throw new AccountNotFoundException("Unable to lookup the specfied users password"); + } + + byte[] byteArray = new byte[password.length]; + int index = 0; + for (char c : password) + { + byteArray[index++] = (byte) c; + } + + byte[] MD5byteArray; + try + { + MD5byteArray = HashedUser.getMD5(byteArray); + } + catch (Exception e1) + { + _logger.warn("Unable to hash password for user '" + principal + "' for comparison"); + return false; + } + + char[] hashedPassword = new char[MD5byteArray.length]; + + index = 0; + for (byte c : MD5byteArray) + { + hashedPassword[index++] = (char) c; + } + + return compareCharArray(pwd, hashedPassword); + } + + private boolean compareCharArray(char[] a, char[] b) + { + boolean equal = false; + if (a.length == b.length) + { + equal = true; + int index = 0; + while (equal && index < a.length) + { + equal = a[index] == b[index]; + index++; + } + } + return equal; + } + + /** + * Changes the password for the specified user + * + * @param principal to change the password for + * @param password plaintext password to set the password too + */ + public boolean updatePassword(Principal principal, char[] password) throws AccountNotFoundException + { + HashedUser user = _users.get(principal.getName()); + + if (user == null) + { + throw new AccountNotFoundException(principal.getName()); + } + + try + { + try + { + _userUpdate.lock(); + char[] orig = user.getPassword(); + user.setPassword(password,false); + + try + { + savePasswordFile(); + } + catch (IOException e) + { + _logger.error("Unable to save password file, password change for user'" + + principal + "' will revert at restart"); + //revert the password change + user.setPassword(orig,true); + return false; + } + return true; + } + finally + { + _userUpdate.unlock(); + } + } + catch (Exception e) + { + return false; + } + } + + public boolean createPrincipal(Principal principal, char[] password) + { + if (_users.get(principal.getName()) != null) + { + return false; + } + + HashedUser user; + try + { + user = new HashedUser(principal.getName(), password); + } + catch (Exception e1) + { + _logger.warn("Unable to create new user '" + principal.getName() + "'"); + return false; + } + + + try + { + _userUpdate.lock(); + _users.put(user.getName(), user); + + try + { + savePasswordFile(); + return true; + } + catch (IOException e) + { + //remove the use on failure. + _users.remove(user.getName()); + return false; + } + } + finally + { + _userUpdate.unlock(); + } + } + + public boolean deletePrincipal(Principal principal) throws AccountNotFoundException + { + HashedUser user = _users.get(principal.getName()); + + if (user == null) + { + throw new AccountNotFoundException(principal.getName()); + } + + try + { + _userUpdate.lock(); + user.delete(); + + try + { + savePasswordFile(); + } + catch (IOException e) + { + _logger.warn("Unable to remove user '" + user.getName() + "' from password file."); + return false; + } + + _users.remove(user.getName()); + } + finally + { + _userUpdate.unlock(); + } + + return true; + } + + public Map<String, AuthenticationProviderInitialiser> getMechanisms() + { + return _saslServers; + } + + public List<Principal> getUsers() + { + return new LinkedList<Principal>(_users.values()); + } + + public Principal getUser(String username) + { + if (_users.containsKey(username)) + { + return new UsernamePrincipal(username); + } + return null; + } + + /** + * Looks up the password for a specified user in the password file. Note this code is <b>not</b> secure since it + * creates strings of passwords. It should be modified to create only char arrays which get nulled out. + * + * @param name The principal name to lookup + * + * @return a char[] for use in SASL. + */ + private char[] lookupPassword(String name) + { + HashedUser user = _users.get(name); + if (user == null) + { + return null; + } + else + { + return user.getPassword(); + } + } + + private void loadPasswordFile() throws IOException + { + try + { + _userUpdate.lock(); + _users.clear(); + + BufferedReader reader = null; + try + { + reader = new BufferedReader(new FileReader(_passwordFile)); + String line; + + while ((line = reader.readLine()) != null) + { + String[] result = _regexp.split(line); + if (result == null || result.length < 2 || result[0].startsWith("#")) + { + continue; + } + + HashedUser user = new HashedUser(result); + _logger.info("Created user:" + user); + _users.put(user.getName(), user); + } + } + finally + { + if (reader != null) + { + reader.close(); + } + } + } + finally + { + _userUpdate.unlock(); + } + } + + private void savePasswordFile() throws IOException + { + try + { + _userUpdate.lock(); + + BufferedReader reader = null; + PrintStream writer = null; + + Random r = new Random(); + File tmp; + do + { + tmp = new File(_passwordFile.getPath() + r.nextInt() + ".tmp"); + } + while(tmp.exists()); + + tmp.deleteOnExit(); + + try + { + writer = new PrintStream(tmp); + reader = new BufferedReader(new FileReader(_passwordFile)); + String line; + + while ((line = reader.readLine()) != null) + { + String[] result = _regexp.split(line); + if (result == null || result.length < 2 || result[0].startsWith("#")) + { + writer.write(line.getBytes(DEFAULT_ENCODING)); + writer.println(); + continue; + } + + HashedUser user = _users.get(result[0]); + + if (user == null) + { + writer.write(line.getBytes(DEFAULT_ENCODING)); + writer.println(); + } + else if (!user.isDeleted()) + { + if (!user.isModified()) + { + writer.write(line.getBytes(DEFAULT_ENCODING)); + writer.println(); + } + else + { + try + { + byte[] encodedPassword = user.getEncodedPassword(); + + writer.write((user.getName() + ":").getBytes(DEFAULT_ENCODING)); + writer.write(encodedPassword); + writer.println(); + + user.saved(); + } + catch (Exception e) + { + _logger.warn("Unable to encode new password reverting to old password."); + writer.write(line.getBytes(DEFAULT_ENCODING)); + writer.println(); + } + } + } + } + + for (HashedUser user : _users.values()) + { + if (user.isModified()) + { + byte[] encodedPassword; + try + { + encodedPassword = user.getEncodedPassword(); + writer.write((user.getName() + ":").getBytes(DEFAULT_ENCODING)); + writer.write(encodedPassword); + writer.println(); + user.saved(); + } + catch (Exception e) + { + _logger.warn("Unable to get Encoded password for user'" + user.getName() + "' password not saved"); + } + } + } + } + catch(IOException e) + { + _logger.error("Unable to create the new password file: " + e); + throw new IOException("Unable to create the new password file" + e); + } + finally + { + if (reader != null) + { + reader.close(); + } + + if (writer != null) + { + writer.close(); + } + } + + // Swap temp file to main password file. + File old = new File(_passwordFile.getAbsoluteFile() + ".old"); + if (old.exists()) + { + old.delete(); + } + + if(!_passwordFile.renameTo(old)) + { + //unable to rename the existing file to the backup name + _logger.error("Could not backup the existing password file"); + throw new IOException("Could not backup the existing password file"); + } + + if(!tmp.renameTo(_passwordFile)) + { + //failed to rename the new file to the required filename + + if(!old.renameTo(_passwordFile)) + { + //unable to return the backup to required filename + _logger.error("Could not rename the new password file into place, and unable to restore original file"); + throw new IOException("Could not rename the new password file into place, and unable to restore original file"); + } + + _logger.error("Could not rename the new password file into place"); + throw new IOException("Could not rename the new password file into place"); + } + } + finally + { + _userUpdate.unlock(); + } + } + + public void reload() throws IOException + { + loadPasswordFile(); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/ConfigurationFilePrincipalDatabaseManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/ConfigurationFilePrincipalDatabaseManager.java new file mode 100644 index 0000000000..e9276e1b0e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/ConfigurationFilePrincipalDatabaseManager.java @@ -0,0 +1,194 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.database; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; + +import org.apache.log4j.Logger; + +import org.apache.qpid.configuration.PropertyUtils; +import org.apache.qpid.configuration.PropertyException; +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.security.auth.database.PrincipalDatabaseManager; +import org.apache.qpid.server.security.auth.management.AMQUserManagementMBean; +import org.apache.qpid.AMQException; + +import javax.management.JMException; + +public class ConfigurationFilePrincipalDatabaseManager implements PrincipalDatabaseManager +{ + private static final Logger _logger = Logger.getLogger(ConfigurationFilePrincipalDatabaseManager.class); + + Map<String, PrincipalDatabase> _databases; + + public ConfigurationFilePrincipalDatabaseManager(ServerConfiguration _configuration) throws Exception + { + _logger.info("Initialising PrincipalDatabase authentication manager"); + _databases = initialisePrincipalDatabases(_configuration); + } + + private Map<String, PrincipalDatabase> initialisePrincipalDatabases(ServerConfiguration _configuration) throws Exception + { + List<String> databaseNames = _configuration.getPrincipalDatabaseNames(); + List<String> databaseClasses = _configuration.getPrincipalDatabaseClass(); + Map<String, PrincipalDatabase> databases = new HashMap<String, PrincipalDatabase>(); + + if (databaseNames.size() == 0) + { + _logger.warn("No Principal databases specified. Broker running with NO AUTHENTICATION"); + } + + for (int i = 0; i < databaseNames.size(); i++) + { + Object o; + try + { + o = Class.forName(databaseClasses.get(i)).newInstance(); + } + catch (Exception e) + { + throw new Exception("Error initialising principal database: " + e, e); + } + + if (!(o instanceof PrincipalDatabase)) + { + throw new Exception("Principal databases must implement the PrincipalDatabase interface"); + } + + initialisePrincipalDatabase((PrincipalDatabase) o, _configuration, i); + + String name = databaseNames.get(i); + if ((name == null) || (name.length() == 0)) + { + throw new Exception("Principal database names must have length greater than or equal to one character"); + } + + PrincipalDatabase pd = databases.get(name); + if (pd != null) + { + throw new Exception("Duplicate principal database name not permitted"); + } + + _logger.info("Initialised principal database '" + name + "' successfully"); + databases.put(name, (PrincipalDatabase) o); + } + + return databases; + } + + private void initialisePrincipalDatabase(PrincipalDatabase principalDatabase, ServerConfiguration _configuration, int index) + throws FileNotFoundException, ConfigurationException + { + List<String> argumentNames = _configuration.getPrincipalDatabaseAttributeNames(index); + List<String> argumentValues = _configuration.getPrincipalDatabaseAttributeValues(index); + for (int i = 0; i < argumentNames.size(); i++) + { + String argName = argumentNames.get(i); + if ((argName == null) || (argName.length() == 0)) + { + throw new ConfigurationException("Argument names must have length >= 1 character"); + } + + if (Character.isLowerCase(argName.charAt(0))) + { + argName = Character.toUpperCase(argName.charAt(0)) + argName.substring(1); + } + + String methodName = "set" + argName; + Method method = null; + try + { + method = principalDatabase.getClass().getMethod(methodName, String.class); + } + catch (Exception e) + { + // do nothing.. as on error method will be null + } + + if (method == null) + { + throw new ConfigurationException("No method " + methodName + " found in class " + + principalDatabase.getClass() + + " hence unable to configure principal database. The method must be public and " + + "have a single String argument with a void return type"); + } + + try + { + method.invoke(principalDatabase, PropertyUtils.replaceProperties(argumentValues.get(i))); + } + catch (Exception ite) + { + if (ite instanceof ConfigurationException) + { + throw(ConfigurationException) ite; + } + else + { + throw new ConfigurationException(ite.getMessage(), ite); + } + } + } + } + + public Map<String, PrincipalDatabase> getDatabases() + { + return _databases; + } + + public void initialiseManagement(ServerConfiguration config) throws ConfigurationException + { + try + { + AMQUserManagementMBean _mbean = new AMQUserManagementMBean(); + + List<String> principalDBs = config.getManagementPrincipalDBs(); + if (principalDBs.isEmpty()) + { + throw new ConfigurationException("No principal-database specified for jmx security"); + } + + String databaseName = principalDBs.get(0); + PrincipalDatabase database = getDatabases().get(databaseName); + if (database == null) + { + throw new ConfigurationException("Principal-database '" + databaseName + "' not found"); + } + + _mbean.setPrincipalDatabase(database); + _mbean.register(); + } + catch (JMException e) + { + _logger.warn("User management disabled as unable to create MBean:" + e); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/HashedUser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/HashedUser.java new file mode 100644 index 0000000000..3690e7f92a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/HashedUser.java @@ -0,0 +1,169 @@ +/* +* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.database; + +import org.apache.commons.codec.EncoderException; +import org.apache.commons.codec.binary.Base64; +import org.apache.log4j.Logger; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Principal; + +public class HashedUser implements Principal +{ + private static final Logger _logger = Logger.getLogger(HashedUser.class); + + String _name; + char[] _password; + byte[] _encodedPassword = null; + private boolean _modified = false; + private boolean _deleted = false; + + HashedUser(String[] data) throws UnsupportedEncodingException + { + if (data.length != 2) + { + throw new IllegalArgumentException("User Data should be length 2, username, password"); + } + + _name = data[0]; + + byte[] encoded_password = data[1].getBytes(Base64MD5PasswordFilePrincipalDatabase.DEFAULT_ENCODING); + + Base64 b64 = new Base64(); + byte[] decoded = b64.decode(encoded_password); + + _encodedPassword = encoded_password; + + _password = new char[decoded.length]; + + int index = 0; + for (byte c : decoded) + { + _password[index++] = (char) c; + } + } + + public HashedUser(String name, char[] password) throws UnsupportedEncodingException, NoSuchAlgorithmException + { + _name = name; + setPassword(password,false); + } + + public static byte[] getMD5(byte[] data) throws NoSuchAlgorithmException, UnsupportedEncodingException + { + MessageDigest md = MessageDigest.getInstance("MD5"); + + for (byte b : data) + { + md.update(b); + } + + return md.digest(); + } + + public String getName() + { + return _name; + } + + public String toString() + { + return _name; + } + + char[] getPassword() + { + return _password; + } + + void setPassword(char[] password, boolean alreadyHashed) throws UnsupportedEncodingException, NoSuchAlgorithmException + { + if(alreadyHashed){ + _password = password; + } + else + { + byte[] byteArray = new byte[password.length]; + int index = 0; + for (char c : password) + { + byteArray[index++] = (byte) c; + } + + byte[] MD5byteArray = getMD5(byteArray); + + _password = new char[MD5byteArray.length]; + + index = 0; + for (byte c : MD5byteArray) + { + _password[index++] = (char) c; + } + } + + _modified = true; + _encodedPassword = null; + } + + byte[] getEncodedPassword() throws EncoderException, UnsupportedEncodingException, NoSuchAlgorithmException + { + if (_encodedPassword == null) + { + encodePassword(); + } + return _encodedPassword; + } + + private void encodePassword() throws EncoderException, UnsupportedEncodingException, NoSuchAlgorithmException + { + byte[] byteArray = new byte[_password.length]; + int index = 0; + for (char c : _password) + { + byteArray[index++] = (byte) c; + } + _encodedPassword = (new Base64()).encode(byteArray); + } + + public boolean isModified() + { + return _modified; + } + + public boolean isDeleted() + { + return _deleted; + } + + public void delete() + { + _deleted = true; + } + + public void saved() + { + _modified = false; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabase.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabase.java new file mode 100644 index 0000000000..76ebea0321 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainPasswordFilePrincipalDatabase.java @@ -0,0 +1,503 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.database; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.security.auth.sasl.amqplain.AmqPlainInitialiser; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5Initialiser; +import org.apache.qpid.server.security.auth.sasl.plain.PlainInitialiser; + +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintStream; +import java.security.Principal; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.locks.ReentrantLock; +import java.util.regex.Pattern; + +/** + * Represents a user database where the account information is stored in a simple flat file. + * + * The file is expected to be in the form: username:password username1:password1 ... usernamen:passwordn + * + * where a carriage return separates each username/password pair. Passwords are assumed to be in plain text. + */ +public class PlainPasswordFilePrincipalDatabase implements PrincipalDatabase +{ + public static final String DEFAULT_ENCODING = "utf-8"; + + private static final Logger _logger = Logger.getLogger(PlainPasswordFilePrincipalDatabase.class); + + private File _passwordFile; + + private Pattern _regexp = Pattern.compile(":"); + + private Map<String, AuthenticationProviderInitialiser> _saslServers; + + private Map<String, PlainUser> _users = new HashMap<String, PlainUser>(); + private ReentrantLock _userUpdate = new ReentrantLock(); + + public PlainPasswordFilePrincipalDatabase() + { + _saslServers = new HashMap<String, AuthenticationProviderInitialiser>(); + + /** + * Create Authenticators for Plain Password file. + */ + + // Accept AMQPlain incomming and compare it to the file. + AmqPlainInitialiser amqplain = new AmqPlainInitialiser(); + amqplain.initialise(this); + + // Accept Plain incomming and compare it to the file. + PlainInitialiser plain = new PlainInitialiser(); + plain.initialise(this); + + // Accept MD5 incomming and Hash file value for comparison + CRAMMD5Initialiser cram = new CRAMMD5Initialiser(); + cram.initialise(this); + + _saslServers.put(amqplain.getMechanismName(), amqplain); + _saslServers.put(plain.getMechanismName(), plain); + _saslServers.put(cram.getMechanismName(), cram); + } + + public void setPasswordFile(String passwordFile) throws IOException + { + File f = new File(passwordFile); + _logger.info("PlainPasswordFile using file " + f.getAbsolutePath()); + _passwordFile = f; + if (!f.exists()) + { + throw new FileNotFoundException("Cannot find password file " + f); + } + if (!f.canRead()) + { + throw new FileNotFoundException("Cannot read password file " + f + + ". Check permissions."); + } + + loadPasswordFile(); + } + + /** + * SASL Callback Mechanism - sets the Password in the PasswordCallback based on the value in the PasswordFile + * If you want to change the password for a user, use updatePassword instead. + * + * @param principal The Principal to set the password for + * @param callback The PasswordCallback to call setPassword on + * + * @throws AccountNotFoundException If the Principal cannot be found in this Database + */ + public void setPassword(Principal principal, PasswordCallback callback) throws AccountNotFoundException + { + if (_passwordFile == null) + { + throw new AccountNotFoundException("Unable to locate principal since no password file was specified during initialisation"); + } + if (principal == null) + { + throw new IllegalArgumentException("principal must not be null"); + } + char[] pwd = lookupPassword(principal.getName()); + + if (pwd != null) + { + callback.setPassword(pwd); + } + else + { + throw new AccountNotFoundException("No account found for principal " + principal); + } + } + + /** + * Used to verify that the presented Password is correct. Currently only used by Management Console + * + * @param principal The principal to authenticate + * @param password The plaintext password to check + * + * @return true if password is correct + * + * @throws AccountNotFoundException if the principal cannot be found + */ + public boolean verifyPassword(String principal, char[] password) throws AccountNotFoundException + { + + char[] pwd = lookupPassword(principal); + + if (pwd == null) + { + throw new AccountNotFoundException("Unable to lookup the specfied users password"); + } + + return compareCharArray(pwd, password); + + } + + /** + * Changes the password for the specified user + * + * @param principal to change the password for + * @param password plaintext password to set the password too + */ + public boolean updatePassword(Principal principal, char[] password) throws AccountNotFoundException + { + PlainUser user = _users.get(principal.getName()); + + if (user == null) + { + throw new AccountNotFoundException(principal.getName()); + } + + char[] orig = user.getPassword(); + _userUpdate.lock(); + try + { + user.setPassword(password); + + savePasswordFile(); + + return true; + } + catch (IOException e) + { + _logger.error("Unable to save password file due to '"+e.getMessage() + +"', password change for user '" + principal + "' discarded"); + //revert the password change + user.setPassword(orig); + return false; + } + finally + { + _userUpdate.unlock(); + } + } + + public boolean createPrincipal(Principal principal, char[] password) + { + if (_users.get(principal.getName()) != null) + { + return false; + } + + PlainUser user = new PlainUser(principal.getName(), password); + + try + { + _userUpdate.lock(); + _users.put(user.getName(), user); + + try + { + savePasswordFile(); + return true; + } + catch (IOException e) + { + //remove the use on failure. + _users.remove(user.getName()); + _logger.warn("Unable to create user '" + user.getName()); + return false; + } + } + finally + { + _userUpdate.unlock(); + } + } + + public boolean deletePrincipal(Principal principal) throws AccountNotFoundException + { + PlainUser user = _users.get(principal.getName()); + + if (user == null) + { + throw new AccountNotFoundException(principal.getName()); + } + + try + { + _userUpdate.lock(); + user.delete(); + + try + { + savePasswordFile(); + } + catch (IOException e) + { + _logger.error("Unable to remove user '" + user.getName() + "' from password file."); + return false; + } + + _users.remove(user.getName()); + } + finally + { + _userUpdate.unlock(); + } + + return true; + } + + public Map<String, AuthenticationProviderInitialiser> getMechanisms() + { + return _saslServers; + } + + public List<Principal> getUsers() + { + return new LinkedList<Principal>(_users.values()); + } + + public Principal getUser(String username) + { + if (_users.containsKey(username)) + { + return new UsernamePrincipal(username); + } + return null; + } + + private boolean compareCharArray(char[] a, char[] b) + { + boolean equal = false; + if (a.length == b.length) + { + equal = true; + int index = 0; + while (equal && index < a.length) + { + equal = a[index] == b[index]; + index++; + } + } + return equal; + } + + + /** + * Looks up the password for a specified user in the password file. Note this code is <b>not</b> secure since it + * creates strings of passwords. It should be modified to create only char arrays which get nulled out. + * + * @param name The principal name to lookup + * + * @return a char[] for use in SASL. + */ + private char[] lookupPassword(String name) + { + PlainUser user = _users.get(name); + if (user == null) + { + return null; + } + else + { + return user.getPassword(); + } + } + + private void loadPasswordFile() throws IOException + { + try + { + _userUpdate.lock(); + _users.clear(); + + BufferedReader reader = null; + try + { + reader = new BufferedReader(new FileReader(_passwordFile)); + String line; + + while ((line = reader.readLine()) != null) + { + String[] result = _regexp.split(line); + if (result == null || result.length < 2 || result[0].startsWith("#")) + { + continue; + } + + PlainUser user = new PlainUser(result); + _logger.info("Created user:" + user); + _users.put(user.getName(), user); + } + } + finally + { + if (reader != null) + { + reader.close(); + } + } + } + finally + { + _userUpdate.unlock(); + } + } + + private void savePasswordFile() throws IOException + { + try + { + _userUpdate.lock(); + + BufferedReader reader = null; + PrintStream writer = null; + + Random r = new Random(); + File tmp; + do + { + tmp = new File(_passwordFile.getPath() + r.nextInt() + ".tmp"); + } + while(tmp.exists()); + + tmp.deleteOnExit(); + + try + { + writer = new PrintStream(tmp); + reader = new BufferedReader(new FileReader(_passwordFile)); + String line; + + while ((line = reader.readLine()) != null) + { + String[] result = _regexp.split(line); + if (result == null || result.length < 2 || result[0].startsWith("#")) + { + writer.write(line.getBytes(DEFAULT_ENCODING)); + writer.println(); + continue; + } + + PlainUser user = _users.get(result[0]); + + if (user == null) + { + writer.write(line.getBytes(DEFAULT_ENCODING)); + writer.println(); + } + else if (!user.isDeleted()) + { + if (!user.isModified()) + { + writer.write(line.getBytes(DEFAULT_ENCODING)); + writer.println(); + } + else + { + byte[] password = user.getPasswordBytes(); + + writer.write((user.getName() + ":").getBytes(DEFAULT_ENCODING)); + writer.write(password); + writer.println(); + + user.saved(); + } + } + } + + for (PlainUser user : _users.values()) + { + if (user.isModified()) + { + byte[] password; + password = user.getPasswordBytes(); + writer.write((user.getName() + ":").getBytes(DEFAULT_ENCODING)); + writer.write(password); + writer.println(); + user.saved(); + } + } + } + catch(IOException e) + { + _logger.error("Unable to create the new password file: " + e); + throw new IOException("Unable to create the new password file" + e); + } + finally + { + if (reader != null) + { + reader.close(); + } + + if (writer != null) + { + writer.close(); + } + } + + // Swap temp file to main password file. + File old = new File(_passwordFile.getAbsoluteFile() + ".old"); + if (old.exists()) + { + old.delete(); + } + + if(!_passwordFile.renameTo(old)) + { + //unable to rename the existing file to the backup name + _logger.error("Could not backup the existing password file"); + throw new IOException("Could not backup the existing password file"); + } + + if(!tmp.renameTo(_passwordFile)) + { + //failed to rename the new file to the required filename + + if(!old.renameTo(_passwordFile)) + { + //unable to return the backup to required filename + _logger.error("Could not rename the new password file into place, and unable to restore original file"); + throw new IOException("Could not rename the new password file into place, and unable to restore original file"); + } + + _logger.error("Could not rename the new password file into place"); + throw new IOException("Could not rename the new password file into place"); + } + + } + finally + { + _userUpdate.unlock(); + } + } + + public void reload() throws IOException + { + loadPasswordFile(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainUser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainUser.java new file mode 100644 index 0000000000..46a78a55aa --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PlainUser.java @@ -0,0 +1,106 @@ +/* +* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.database; + +import org.apache.log4j.Logger; + +import java.security.Principal; + +public class PlainUser implements Principal +{ + private String _name; + private char[] _password; + private boolean _modified = false; + private boolean _deleted = false; + + PlainUser(String[] data) + { + if (data.length != 2) + { + throw new IllegalArgumentException("User Data should be length 2, username, password"); + } + + _name = data[0]; + + _password = data[1].toCharArray(); + + } + + public PlainUser(String name, char[] password) + { + _name = name; + _password = password; + _modified = true; + } + + public String getName() + { + return _name; + } + + public String toString() + { + return _name; + } + + char[] getPassword() + { + return _password; + } + + byte[] getPasswordBytes() + { + byte[] byteArray = new byte[_password.length]; + int index = 0; + for (char c : _password) + { + byteArray[index++] = (byte) c; + } + return byteArray; + } + + void setPassword(char[] password) + { + _password = password; + _modified = true; + } + + public boolean isModified() + { + return _modified; + } + + public boolean isDeleted() + { + return _deleted; + } + + public void delete() + { + _deleted = true; + } + + public void saved() + { + _modified = false; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabase.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabase.java new file mode 100644 index 0000000000..ef37e043a6 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabase.java @@ -0,0 +1,105 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.database; + +import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.Principal; +import java.util.Map; +import java.util.List; + +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; + +/** Represents a "user database" which is really a way of storing principals (i.e. usernames) and passwords. */ +public interface PrincipalDatabase +{ + /** + * Set the password for a given principal in the specified callback. This is used for certain SASL providers. The + * user database implementation should look up the password in any way it chooses and set it in the callback by + * calling its setPassword method. + * + * @param principal the principal + * @param callback the password callback that wants to receive the password + * + * @throws AccountNotFoundException if the account for specified principal could not be found + * @throws IOException if there was an error looking up the principal + */ + void setPassword(Principal principal, PasswordCallback callback) + throws IOException, AccountNotFoundException; + + /** + * Used to verify that the presented Password is correct. Currently only used by Management Console + * @param principal The principal to authenticate + * @param password The password to check + * @return true if password is correct + * @throws AccountNotFoundException if the principal cannot be found + */ + boolean verifyPassword(String principal, char[] password) + throws AccountNotFoundException; + + /** + * Update(Change) the password for the given principal + * @param principal Who's password is to be changed + * @param password The new password to use + * @return True if change was successful + * @throws AccountNotFoundException If the given principal doesn't exist in the Database + */ + boolean updatePassword(Principal principal, char[] password) + throws AccountNotFoundException; + + /** + * Create a new principal in the database + * @param principal The principal to create + * @param password The password to set for the principal + * @return True on a successful creation + */ + boolean createPrincipal(Principal principal, char[] password); + + /** + * Delete a principal + * @param principal The principal to delete + * @return True on a successful creation + * @throws AccountNotFoundException If the given principal doesn't exist in the Database + */ + boolean deletePrincipal(Principal principal) + throws AccountNotFoundException; + + /** + * Get the principal from the database with the given username + * @param username of the principal to lookup + * @return The Principal object for the given username or null if not found. + */ + Principal getUser(String username); + + /** + * Reload the database to its ensure contents are up to date + * @throws IOException If there was an error reloading the database + */ + void reload() throws IOException; + + public Map<String, AuthenticationProviderInitialiser> getMechanisms(); + + + List<Principal> getUsers(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabaseManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabaseManager.java new file mode 100644 index 0000000000..f9882f8810 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PrincipalDatabaseManager.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.database; + +import org.apache.qpid.server.configuration.ServerConfiguration; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; + +import java.util.Map; + +public interface PrincipalDatabaseManager +{ + public Map<String, PrincipalDatabase> getDatabases(); + + public void initialiseManagement(ServerConfiguration _configuration) throws ConfigurationException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabase.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabase.java new file mode 100644 index 0000000000..ff8851306f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabase.java @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.database; + +import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; +import org.apache.qpid.server.security.auth.sasl.crammd5.CRAMMD5Initialiser; +import org.apache.qpid.server.security.auth.sasl.plain.PlainInitialiser; + +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; +import java.util.Properties; +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.LinkedList; +import java.security.Principal; +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +public class PropertiesPrincipalDatabase implements PrincipalDatabase +{ + private Properties _users; + + private Map<String, AuthenticationProviderInitialiser> _saslServers; + + public PropertiesPrincipalDatabase(Properties users) + { + _users = users; + + _saslServers = new HashMap<String, AuthenticationProviderInitialiser>(); + + /** + * Create Authenticators for Properties Principal Database. + */ + + // Accept MD5 incomming and use plain comparison with the file + PlainInitialiser cram = new PlainInitialiser(); + cram.initialise(this); + // Accept Plain incomming and hash it for comparison to the file. + CRAMMD5Initialiser plain = new CRAMMD5Initialiser(); + plain.initialise(this, CRAMMD5Initialiser.HashDirection.INCOMMING); + + _saslServers.put(plain.getMechanismName(), cram); + _saslServers.put(cram.getMechanismName(), plain); + } + + public void setPassword(Principal principal, PasswordCallback callback) throws IOException, AccountNotFoundException + { + if (principal == null) + { + throw new IllegalArgumentException("principal must not be null"); + } + + + + final String pwd = _users.getProperty(principal.getName()); + + if (pwd != null) + { + callback.setPassword(pwd.toCharArray()); + } + else + { + throw new AccountNotFoundException("No account found for principal " + principal); + } + } + + public boolean verifyPassword(String principal, char[] password) throws AccountNotFoundException + { + //fixme this is not correct as toCharArray is not safe based on the type of string. + char[] pwd = _users.getProperty(principal).toCharArray(); + + return compareCharArray(pwd, password); + } + + public boolean updatePassword(Principal principal, char[] password) throws AccountNotFoundException + { + return false; // updates denied + } + + public boolean createPrincipal(Principal principal, char[] password) + { + return false; // updates denied + } + + public boolean deletePrincipal(Principal principal) throws AccountNotFoundException + { + return false; // updates denied + } + + private boolean compareCharArray(char[] a, char[] b) + { + boolean equal = false; + if (a.length == b.length) + { + equal = true; + int index = 0; + while (equal && index < a.length) + { + equal = a[index] == b[index]; + index++; + } + } + return equal; + } + + private char[] convertPassword(String password) throws UnsupportedEncodingException + { + byte[] passwdBytes = password.getBytes("utf-8"); + + char[] passwd = new char[passwdBytes.length]; + + int index = 0; + + for (byte b : passwdBytes) + { + passwd[index++] = (char) b; + } + + return passwd; + } + + + public Map<String, AuthenticationProviderInitialiser> getMechanisms() + { + return _saslServers; + } + + public List<Principal> getUsers() + { + return new LinkedList<Principal>(); //todo + } + + public Principal getUser(String username) + { + if (_users.getProperty(username) != null) + { + return new UsernamePrincipal(username); + } + else + { + return null; + } + } + + public void reload() throws IOException + { + //No file to update from, so do nothing. + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabaseManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabaseManager.java new file mode 100644 index 0000000000..8658101cd8 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/database/PropertiesPrincipalDatabaseManager.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.database; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.configuration.ServerConfiguration; + +import java.util.Map; +import java.util.Properties; +import java.util.HashMap; + +public class PropertiesPrincipalDatabaseManager implements PrincipalDatabaseManager +{ + + Map<String, PrincipalDatabase> _databases = new HashMap<String, PrincipalDatabase>(); + + public PropertiesPrincipalDatabaseManager(String name, Properties users) + { + _databases.put(name, new PropertiesPrincipalDatabase(users)); + } + + public Map<String, PrincipalDatabase> getDatabases() + { + return _databases; + } + + public void initialiseManagement(ServerConfiguration _configuration) throws ConfigurationException + { + //todo + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/management/AMQUserManagementMBean.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/management/AMQUserManagementMBean.java new file mode 100644 index 0000000000..a839315bcc --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/management/AMQUserManagementMBean.java @@ -0,0 +1,211 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.management; + +import java.io.IOException; +import java.security.Principal; +import java.util.List; + +import javax.management.JMException; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; +import javax.security.auth.login.AccountNotFoundException; + +import org.apache.log4j.Logger; +import org.apache.qpid.management.common.mbeans.UserManagement; +import org.apache.qpid.management.common.mbeans.annotations.MBeanDescription; +import org.apache.qpid.management.common.mbeans.annotations.MBeanOperation; +import org.apache.qpid.server.management.AMQManagedObject; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; + +/** MBean class for AMQUserManagementMBean. It implements all the management features exposed for managing users. */ +@MBeanDescription("User Management Interface") +public class AMQUserManagementMBean extends AMQManagedObject implements UserManagement +{ + private static final Logger _logger = Logger.getLogger(AMQUserManagementMBean.class); + + private PrincipalDatabase _principalDatabase; + + // Setup for the TabularType + private static final TabularType _userlistDataType; // Datatype for representing User Lists + private static final CompositeType _userDataType; // Composite type for representing User + + static + { + OpenType[] userItemTypes = new OpenType[4]; // User item types. + userItemTypes[0] = SimpleType.STRING; // For Username + userItemTypes[1] = SimpleType.BOOLEAN; // For Rights - Read - No longer in use + userItemTypes[2] = SimpleType.BOOLEAN; // For Rights - Write - No longer in use + userItemTypes[3] = SimpleType.BOOLEAN; // For Rights - Admin - No longer is use + + try + { + _userDataType = + new CompositeType("User", "User Data", COMPOSITE_ITEM_NAMES.toArray(new String[COMPOSITE_ITEM_NAMES.size()]), + COMPOSITE_ITEM_DESCRIPTIONS.toArray(new String[COMPOSITE_ITEM_DESCRIPTIONS.size()]), userItemTypes); + + _userlistDataType = new TabularType("Users", "List of users", _userDataType, TABULAR_UNIQUE_INDEX.toArray(new String[TABULAR_UNIQUE_INDEX.size()])); + } + catch (OpenDataException e) + { + _logger.error("Tabular data setup for viewing users incorrect.", e); + throw new ExceptionInInitializerError("Tabular data setup for viewing users incorrect"); + } + } + + public AMQUserManagementMBean() throws JMException + { + super(UserManagement.class, UserManagement.TYPE); + } + + public String getObjectInstanceName() + { + return UserManagement.TYPE; + } + + public boolean setPassword(String username, String password) + { + return setPassword(username, password.toCharArray()); + } + + public boolean setPassword(String username, char[] password) + { + try + { + //delegate password changes to the Principal Database + return _principalDatabase.updatePassword(new UsernamePrincipal(username), password); + } + catch (AccountNotFoundException e) + { + _logger.warn("Attempt to set password of non-existent user'" + username + "'"); + return false; + } + } + + public boolean setRights(String username, boolean read, boolean write, boolean admin) + { + throw new UnsupportedOperationException("Support for setting access rights no longer supported."); + } + + public boolean createUser(String username, String password) + { + if (_principalDatabase.createPrincipal(new UsernamePrincipal(username), password.toCharArray())) + { + return true; + } + + return false; + } + + public boolean createUser(String username, String password, boolean read, boolean write, boolean admin) + { + if (read || write || admin) + { + throw new UnsupportedOperationException("Support for setting access rights to true no longer supported."); + } + return createUser(username, password); + } + + public boolean createUser(String username, char[] password, boolean read, boolean write, boolean admin) + { + return createUser(username, new String(password), read, write, admin); + } + + public boolean deleteUser(String username) + { + try + { + _principalDatabase.deletePrincipal(new UsernamePrincipal(username)); + } + catch (AccountNotFoundException e) + { + _logger.warn("Attempt to delete user (" + username + ") that doesn't exist"); + return false; + } + + return true; + } + + public boolean reloadData() + { + try + { + _principalDatabase.reload(); + } + catch (IOException e) + { + _logger.warn("Reload failed due to:", e); + return false; + } + // Reload successful + return true; + } + + + @MBeanOperation(name = "viewUsers", description = "All users that are currently available to the system.") + public TabularData viewUsers() + { + List<Principal> users = _principalDatabase.getUsers(); + + TabularDataSupport userList = new TabularDataSupport(_userlistDataType); + + try + { + // Create the tabular list of message header contents + for (Principal user : users) + { + // Create header attributes list + + // Read,Write,Admin items are depcreated and we return always false. + Object[] itemData = {user.getName(), false, false, false}; + CompositeData messageData = new CompositeDataSupport(_userDataType, COMPOSITE_ITEM_NAMES.toArray(new String[COMPOSITE_ITEM_NAMES.size()]), itemData); + userList.put(messageData); + } + } + catch (OpenDataException e) + { + _logger.warn("Unable to create user list due to :", e); + return null; + } + + return userList; + } + + /*** Broker Methods **/ + + /** + * setPrincipalDatabase + * + * @param database set The Database to use for user lookup + */ + public void setPrincipalDatabase(PrincipalDatabase database) + { + _principalDatabase = database; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java new file mode 100644 index 0000000000..39e1e07c57 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/AuthenticationManager.java @@ -0,0 +1,40 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.manager; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.qpid.common.Closeable; +import org.apache.qpid.server.security.auth.AuthenticationResult; + +/** + * The AuthenticationManager class is the entity responsible for + * determining the authenticity of user credentials. + */ +public interface AuthenticationManager extends Closeable +{ + String getMechanisms(); + + SaslServer createSaslServer(String mechanism, String localFQDN) throws SaslException; + + AuthenticationResult authenticate(SaslServer server, byte[] response); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java new file mode 100644 index 0000000000..d10ad2c170 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/manager/PrincipalDatabaseAuthenticationManager.java @@ -0,0 +1,184 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.manager; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.security.auth.sasl.JCAProvider; +import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; +import org.apache.qpid.server.security.auth.AuthenticationResult; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.SaslServerFactory; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslException; +import javax.security.sasl.Sasl; +import java.util.Map; +import java.util.HashMap; +import java.util.TreeMap; +import java.security.Security; + +/** + * Concrete implementation of the AuthenticationManager that determines if supplied + * user credentials match those appearing in a PrincipalDatabase. + * + */ +public class PrincipalDatabaseAuthenticationManager implements AuthenticationManager +{ + private static final Logger _logger = Logger.getLogger(PrincipalDatabaseAuthenticationManager.class); + + /** The list of mechanisms, in the order in which they are configured (i.e. preferred order) */ + private String _mechanisms; + + /** Maps from the mechanism to the callback handler to use for handling those requests */ + private Map<String, CallbackHandler> _callbackHandlerMap = new HashMap<String, CallbackHandler>(); + + /** + * Maps from the mechanism to the properties used to initialise the server. See the method Sasl.createSaslServer for + * details of the use of these properties. This map is populated during initialisation of each provider. + */ + private Map<String, Map<String, ?>> _serverCreationProperties = new HashMap<String, Map<String, ?>>(); + + /** The name for the required SASL Server mechanisms */ + public static final String PROVIDER_NAME= "AMQSASLProvider-Server"; + + public PrincipalDatabaseAuthenticationManager() + { + _logger.info("Initialising PrincipalDatabase authentication manager."); + + Map<String, Class<? extends SaslServerFactory>> providerMap = new TreeMap<String, Class<? extends SaslServerFactory>>(); + + + initialiseAuthenticationMechanisms(providerMap, ApplicationRegistry.getInstance().getDatabaseManager().getDatabases()); + + if (providerMap.size() > 0) + { + // Ensure we are used before the defaults + if (Security.insertProviderAt(new JCAProvider(PROVIDER_NAME, providerMap), 1) == -1) + { + _logger.error("Unable to load custom SASL providers. Qpid custom SASL authenticators unavailable."); + } + else + { + _logger.info("Additional SASL providers successfully registered."); + } + + } + else + { + _logger.warn("No additional SASL providers registered."); + } + } + + private void initialiseAuthenticationMechanisms(Map<String, Class<? extends SaslServerFactory>> providerMap, Map<String, PrincipalDatabase> databases) + { + if (databases.size() > 1) + { + _logger.warn("More than one principle database provided currently authentication mechanism will override each other."); + } + + for (Map.Entry<String, PrincipalDatabase> entry : databases.entrySet()) + { + // fixme As the database now provide the mechanisms they support, they will ... + // overwrite each other in the map. There should only be one database per vhost. + // But currently we must have authentication before vhost definition. + initialiseAuthenticationMechanisms(providerMap, entry.getValue()); + } + } + + private void initialiseAuthenticationMechanisms(Map<String, Class<? extends SaslServerFactory>> providerMap, PrincipalDatabase database) + { + if (database == null || database.getMechanisms().size() == 0) + { + _logger.warn("No Database or no mechanisms to initialise authentication"); + return; + } + + for (Map.Entry<String, AuthenticationProviderInitialiser> mechanism : database.getMechanisms().entrySet()) + { + initialiseAuthenticationMechanism(mechanism.getKey(), mechanism.getValue(), providerMap); + } + } + + private void initialiseAuthenticationMechanism(String mechanism, AuthenticationProviderInitialiser initialiser, + Map<String, Class<? extends SaslServerFactory>> providerMap) + + { + if (_mechanisms == null) + { + _mechanisms = mechanism; + } + else + { + // simple append should be fine since the number of mechanisms is small and this is a one time initialisation + _mechanisms = _mechanisms + " " + mechanism; + } + _callbackHandlerMap.put(mechanism, initialiser.getCallbackHandler()); + _serverCreationProperties.put(mechanism, initialiser.getProperties()); + Class<? extends SaslServerFactory> factory = initialiser.getServerFactoryClassForJCARegistration(); + if (factory != null) + { + providerMap.put(mechanism, factory); + } + _logger.info("Initialised " + mechanism + " SASL provider successfully"); + } + + public String getMechanisms() + { + return _mechanisms; + } + + public SaslServer createSaslServer(String mechanism, String localFQDN) throws SaslException + { + return Sasl.createSaslServer(mechanism, "AMQP", localFQDN, _serverCreationProperties.get(mechanism), + _callbackHandlerMap.get(mechanism)); + } + + public AuthenticationResult authenticate(SaslServer server, byte[] response) + { + try + { + // Process response from the client + byte[] challenge = server.evaluateResponse(response != null ? response : new byte[0]); + + if (server.isComplete()) + { + return new AuthenticationResult(challenge, AuthenticationResult.AuthenticationStatus.SUCCESS); + } + else + { + return new AuthenticationResult(challenge, AuthenticationResult.AuthenticationStatus.CONTINUE); + } + } + catch (SaslException e) + { + return new AuthenticationResult(AuthenticationResult.AuthenticationStatus.ERROR, e); + } + } + + public void close() + { + _mechanisms = null; + Security.removeProvider(PROVIDER_NAME); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticator.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticator.java new file mode 100644 index 0000000000..0cbbccb3b8 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/rmi/RMIPasswordAuthenticator.java @@ -0,0 +1,119 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.rmi; + +import java.util.Collections; + +import javax.management.remote.JMXAuthenticator; +import javax.management.remote.JMXPrincipal; +import javax.security.auth.Subject; +import javax.security.auth.login.AccountNotFoundException; + +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; + +public class RMIPasswordAuthenticator implements JMXAuthenticator +{ + static final String UNABLE_TO_LOOKUP = "The broker was unable to lookup the user details"; + static final String SHOULD_BE_STRING_ARRAY = "User details should be String[]"; + static final String SHOULD_HAVE_2_ELEMENTS = "User details should have 2 elements, username, password"; + static final String SHOULD_BE_NON_NULL = "Supplied username and password should be non-null"; + static final String INVALID_CREDENTIALS = "Invalid user details supplied"; + static final String CREDENTIALS_REQUIRED = "User details are required. " + + "Please ensure you are using an up to date management console to connect."; + + private PrincipalDatabase _db = null; + + public RMIPasswordAuthenticator() + { + } + + public void setPrincipalDatabase(PrincipalDatabase pd) + { + this._db = pd; + } + + public Subject authenticate(Object credentials) throws SecurityException + { + // Verify that credential's are of type String[]. + if (!(credentials instanceof String[])) + { + if (credentials == null) + { + throw new SecurityException(CREDENTIALS_REQUIRED); + } + else + { + throw new SecurityException(SHOULD_BE_STRING_ARRAY); + } + } + + // Verify that required number of credential's. + final String[] userCredentials = (String[]) credentials; + if (userCredentials.length != 2) + { + throw new SecurityException(SHOULD_HAVE_2_ELEMENTS); + } + + String username = (String) userCredentials[0]; + String password = (String) userCredentials[1]; + + // Verify that all required credential's are actually present. + if (username == null || password == null) + { + throw new SecurityException(SHOULD_BE_NON_NULL); + } + + // Verify that a PD has been set. + if (_db == null) + { + throw new SecurityException(UNABLE_TO_LOOKUP); + } + + boolean authenticated = false; + + // Perform authentication + try + { + if (_db.verifyPassword(username, password.toCharArray())) + { + authenticated = true; + } + } + catch (AccountNotFoundException e) + { + throw new SecurityException(INVALID_CREDENTIALS); // XXX + } + + if (authenticated) + { + //credential's check out, return the appropriate JAAS Subject + return new Subject(true, + Collections.singleton(new JMXPrincipal(username)), + Collections.EMPTY_SET, + Collections.EMPTY_SET); + } + else + { + throw new SecurityException(INVALID_CREDENTIALS); + } + } + +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/AuthenticationProviderInitialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/AuthenticationProviderInitialiser.java new file mode 100644 index 0000000000..89e545d6f5 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/AuthenticationProviderInitialiser.java @@ -0,0 +1,76 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl; + +import java.util.Map; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.SaslServerFactory; + +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; + +public interface AuthenticationProviderInitialiser +{ + /** + * @return the mechanism's name. This will be used in the list of mechanism's advertised to the + * client. + */ + String getMechanismName(); + + /** + * Initialise the authentication provider. + * @param baseConfigPath the path in the config file that points to any config options for this provider. Each + * provider can have its own set of configuration options + * @param configuration the Apache Commons Configuration instance used to configure this provider + * @param principalDatabases the set of principal databases that are available + * @throws Exception needs refined Exception is too broad. + */ + void initialise(String baseConfigPath, Configuration configuration, + Map<String, PrincipalDatabase> principalDatabases) throws Exception; + + /** + * Initialise the authentication provider. + * @param db The principal database to initialise with + */ + void initialise(PrincipalDatabase db); + + + /** + * @return the callback handler that should be used to process authentication requests for this mechanism. This will + * be called after initialise and will be stored by the authentication manager. The callback handler <b>must</b> be + * fully threadsafe. + */ + CallbackHandler getCallbackHandler(); + + /** + * Get the properties that must be passed in to the Sasl.createSaslServer method. + * @return the properties, which may be null + */ + Map<String, ?> getProperties(); + + /** + * Get the class that is the server factory. This is used for the JCA registration. + * @return null if no JCA registration is required, otherwise return the class + * that will be used in JCA registration + */ + Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/JCAProvider.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/JCAProvider.java new file mode 100644 index 0000000000..d6a09d8217 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/JCAProvider.java @@ -0,0 +1,46 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl; + +import java.security.Provider; +import java.security.Security; +import java.util.Map; + +import javax.security.sasl.SaslServerFactory; + +public final class JCAProvider extends Provider +{ + public JCAProvider(String name, Map<String, Class<? extends SaslServerFactory>> providerMap) + { + super(name, 1.0, "A JCA provider that registers all " + + "AMQ SASL providers that want to be registered"); + register(providerMap); + } + + private void register(Map<String, Class<? extends SaslServerFactory>> providerMap) + { + for (Map.Entry<String, Class<? extends SaslServerFactory>> me : + providerMap.entrySet()) + { + put("SaslServerFactory." + me.getKey(), me.getValue().getName()); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePasswordInitialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePasswordInitialiser.java new file mode 100644 index 0000000000..5c13e03886 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePasswordInitialiser.java @@ -0,0 +1,123 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl; + +import java.io.IOException; +import java.security.Principal; +import java.util.Map; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.AccountNotFoundException; +import javax.security.sasl.AuthorizeCallback; + +import org.apache.commons.configuration.Configuration; + +import org.apache.log4j.Logger; + +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; +import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal; + +public abstract class UsernamePasswordInitialiser implements AuthenticationProviderInitialiser +{ + protected static final Logger _logger = Logger.getLogger(UsernamePasswordInitialiser.class); + + private ServerCallbackHandler _callbackHandler; + + private static class ServerCallbackHandler implements CallbackHandler + { + private final PrincipalDatabase _principalDatabase; + + protected ServerCallbackHandler(PrincipalDatabase database) + { + _principalDatabase = database; + } + + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException + { + Principal username = null; + for (Callback callback : callbacks) + { + if (callback instanceof NameCallback) + { + username = new UsernamePrincipal(((NameCallback) callback).getDefaultName()); + } + else if (callback instanceof PasswordCallback) + { + try + { + _principalDatabase.setPassword(username, (PasswordCallback) callback); + } + catch (AccountNotFoundException e) + { + // very annoyingly the callback handler does not throw anything more appropriate than + // IOException + IOException ioe = new IOException("Error looking up user " + e); + ioe.initCause(e); + throw ioe; + } + } + else if (callback instanceof AuthorizeCallback) + { + ((AuthorizeCallback) callback).setAuthorized(true); + } + else + { + throw new UnsupportedCallbackException(callback); + } + } + } + } + + public void initialise(String baseConfigPath, Configuration configuration, + Map<String, PrincipalDatabase> principalDatabases) throws Exception + { + String principalDatabaseName = configuration.getString(baseConfigPath + ".principal-database"); + PrincipalDatabase db = principalDatabases.get(principalDatabaseName); + + initialise(db); + } + + public void initialise(PrincipalDatabase db) + { + if (db == null) + { + throw new NullPointerException("Cannot initialise with a null Principal database."); + } + + _callbackHandler = new ServerCallbackHandler(db); + } + + public CallbackHandler getCallbackHandler() + { + return _callbackHandler; + } + + public Map<String, ?> getProperties() + { + // there are no properties required for the CRAM-MD5 implementation + return null; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePrincipal.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePrincipal.java new file mode 100644 index 0000000000..d7c8383690 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/UsernamePrincipal.java @@ -0,0 +1,44 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl; + +import java.security.Principal; + +/** A principal that is just a wrapper for a simple username. */ +public class UsernamePrincipal implements Principal +{ + private String _name; + + public UsernamePrincipal(String name) + { + _name = name; + } + + public String getName() + { + return _name; + } + + public String toString() + { + return _name; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainInitialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainInitialiser.java new file mode 100644 index 0000000000..7acc6322d1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainInitialiser.java @@ -0,0 +1,38 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl.amqplain; + +import javax.security.sasl.SaslServerFactory; + +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; + +public class AmqPlainInitialiser extends UsernamePasswordInitialiser +{ + public String getMechanismName() + { + return "AMQPLAIN"; + } + + public Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration() + { + return AmqPlainSaslServerFactory.class; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServer.java new file mode 100644 index 0000000000..9f56b8521a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServer.java @@ -0,0 +1,132 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl.amqplain; + +import java.io.IOException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.sasl.AuthorizeCallback; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.framing.AMQFrameDecodingException; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.FieldTableFactory; + +public class AmqPlainSaslServer implements SaslServer +{ + public static final String MECHANISM = "AMQPLAIN"; + + private CallbackHandler _cbh; + + private String _authorizationId; + + private boolean _complete = false; + + public AmqPlainSaslServer(CallbackHandler cbh) + { + _cbh = cbh; + } + + public String getMechanismName() + { + return MECHANISM; + } + + public byte[] evaluateResponse(byte[] response) throws SaslException + { + try + { + final FieldTable ft = FieldTableFactory.newFieldTable(ByteBuffer.wrap(response), response.length); + String username = (String) ft.getString("LOGIN"); + // we do not care about the prompt but it throws if null + NameCallback nameCb = new NameCallback("prompt", username); + // we do not care about the prompt but it throws if null + PasswordCallback passwordCb = new PasswordCallback("prompt", false); + // TODO: should not get pwd as a String but as a char array... + String pwd = (String) ft.getString("PASSWORD"); + AuthorizeCallback authzCb = new AuthorizeCallback(username, username); + Callback[] callbacks = new Callback[]{nameCb, passwordCb, authzCb}; + _cbh.handle(callbacks); + String storedPwd = new String(passwordCb.getPassword()); + if (storedPwd.equals(pwd)) + { + _complete = true; + } + if (authzCb.isAuthorized() && _complete) + { + _authorizationId = authzCb.getAuthenticationID(); + return null; + } + else + { + throw new SaslException("Authentication failed"); + } + } + catch (AMQFrameDecodingException e) + { + throw new SaslException("Unable to decode response: " + e, e); + } + catch (IOException e) + { + throw new SaslException("Error processing data: " + e, e); + } + catch (UnsupportedCallbackException e) + { + throw new SaslException("Unable to obtain data from callback handler: " + e, e); + } + } + + public boolean isComplete() + { + return _complete; + } + + public String getAuthorizationID() + { + return _authorizationId; + } + + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException + { + throw new SaslException("Unsupported operation"); + } + + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException + { + throw new SaslException("Unsupported operation"); + } + + public Object getNegotiatedProperty(String propName) + { + return null; + } + + public void dispose() throws SaslException + { + _cbh = null; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServerFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServerFactory.java new file mode 100644 index 0000000000..17d123eb0d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/amqplain/AmqPlainSaslServerFactory.java @@ -0,0 +1,61 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl.amqplain; + +import java.util.Map; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslServerFactory; + +public class AmqPlainSaslServerFactory implements SaslServerFactory +{ + public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map props, + CallbackHandler cbh) throws SaslException + { + if (AmqPlainSaslServer.MECHANISM.equals(mechanism)) + { + return new AmqPlainSaslServer(cbh); + } + else + { + return null; + } + } + + public String[] getMechanismNames(Map props) + { + if (props != null && + (props.containsKey(Sasl.POLICY_NOPLAINTEXT) || + props.containsKey(Sasl.POLICY_NODICTIONARY) || + props.containsKey(Sasl.POLICY_NOACTIVE))) + { + // returned array must be non null according to interface documentation + return new String[0]; + } + else + { + return new String[]{AmqPlainSaslServer.MECHANISM}; + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousInitialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousInitialiser.java new file mode 100644 index 0000000000..4a66b74783 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousInitialiser.java @@ -0,0 +1,39 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl.anonymous; + +import javax.security.sasl.SaslServerFactory; + +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; +import org.apache.qpid.server.security.auth.sasl.amqplain.AmqPlainSaslServerFactory; + +public class AnonymousInitialiser extends UsernamePasswordInitialiser +{ + public String getMechanismName() + { + return "ANONYMOUS"; + } + + public Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration() + { + return AnonymousSaslServerFactory.class; + } +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousSaslServer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousSaslServer.java new file mode 100644 index 0000000000..b4cce15d88 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousSaslServer.java @@ -0,0 +1,88 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl.anonymous; + +import java.io.IOException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.sasl.AuthorizeCallback; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.mina.common.ByteBuffer; +import org.apache.qpid.framing.AMQFrameDecodingException; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.FieldTableFactory; + +public class AnonymousSaslServer implements SaslServer +{ + public static final String MECHANISM = "ANONYMOUS"; + + private boolean _complete = false; + + public AnonymousSaslServer() + { + } + + public String getMechanismName() + { + return MECHANISM; + } + + public byte[] evaluateResponse(byte[] response) throws SaslException + { + _complete = true; + return null; + } + + public boolean isComplete() + { + return _complete; + } + + public String getAuthorizationID() + { + return null; + } + + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException + { + throw new SaslException("Unsupported operation"); + } + + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException + { + throw new SaslException("Unsupported operation"); + } + + public Object getNegotiatedProperty(String propName) + { + return null; + } + + public void dispose() throws SaslException + { + } +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousSaslServerFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousSaslServerFactory.java new file mode 100644 index 0000000000..8a5ff7df2d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/anonymous/AnonymousSaslServerFactory.java @@ -0,0 +1,64 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl.anonymous; + +import org.apache.qpid.server.security.auth.sasl.amqplain.AmqPlainSaslServer; + +import java.util.Map; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslServerFactory; + +public class AnonymousSaslServerFactory implements SaslServerFactory +{ + public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map props, + CallbackHandler cbh) throws SaslException + { + if (AnonymousSaslServer.MECHANISM.equals(mechanism)) + { + return new AnonymousSaslServer(); + } + else + { + return null; + } + } + + public String[] getMechanismNames(Map props) + { + if (props != null && + (props.containsKey(Sasl.POLICY_NOPLAINTEXT) || + props.containsKey(Sasl.POLICY_NODICTIONARY) || + props.containsKey(Sasl.POLICY_NOACTIVE) || + props.containsKey(Sasl.POLICY_NOANONYMOUS))) + { + // returned array must be non null according to interface documentation + return new String[0]; + } + else + { + return new String[]{AnonymousSaslServer.MECHANISM}; + } + } +}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedInitialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedInitialiser.java new file mode 100644 index 0000000000..97f9a4e91a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedInitialiser.java @@ -0,0 +1,50 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl.crammd5; + +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; + +import javax.security.sasl.SaslServerFactory; +import java.util.Map; + +public class CRAMMD5HashedInitialiser extends UsernamePasswordInitialiser +{ + public String getMechanismName() + { + return CRAMMD5HashedSaslServer.MECHANISM; + } + + public Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration() + { + return CRAMMD5HashedServerFactory.class; + } + + public void initialise(PrincipalDatabase passwordFile) + { + super.initialise(passwordFile); + } + + public Map<String, ?> getProperties() + { + return null; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedSaslServer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedSaslServer.java new file mode 100644 index 0000000000..f6cab084ea --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedSaslServer.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl.crammd5; + +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslException; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslServerFactory; +import javax.security.auth.callback.CallbackHandler; +import java.util.Enumeration; +import java.util.Map; + +public class CRAMMD5HashedSaslServer implements SaslServer +{ + public static final String MECHANISM = "CRAM-MD5-HASHED"; + + private SaslServer _realServer; + + public CRAMMD5HashedSaslServer(String mechanism, String protocol, String serverName, Map<String, ?> props, + CallbackHandler cbh) throws SaslException + { + Enumeration factories = Sasl.getSaslServerFactories(); + + while (factories.hasMoreElements()) + { + SaslServerFactory factory = (SaslServerFactory) factories.nextElement(); + + if (factory instanceof CRAMMD5HashedServerFactory) + { + continue; + } + + String[] mechs = factory.getMechanismNames(props); + + for (String mech : mechs) + { + if (mech.equals("CRAM-MD5")) + { + _realServer = factory.createSaslServer("CRAM-MD5", protocol, serverName, props, cbh); + return; + } + } + } + + throw new RuntimeException("No default SaslServer found for mechanism:" + "CRAM-MD5"); + } + + public String getMechanismName() + { + return MECHANISM; + } + + public byte[] evaluateResponse(byte[] response) throws SaslException + { + return _realServer.evaluateResponse(response); + } + + public boolean isComplete() + { + return _realServer.isComplete(); + } + + public String getAuthorizationID() + { + return _realServer.getAuthorizationID(); + } + + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException + { + return _realServer.unwrap(incoming, offset, len); + } + + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException + { + return _realServer.wrap(outgoing, offset, len); + } + + public Object getNegotiatedProperty(String propName) + { + return _realServer.getNegotiatedProperty(propName); + } + + public void dispose() throws SaslException + { + _realServer.dispose(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedServerFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedServerFactory.java new file mode 100644 index 0000000000..5298b5cc63 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HashedServerFactory.java @@ -0,0 +1,61 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl.crammd5; + +import java.util.Map; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslServerFactory; + +public class CRAMMD5HashedServerFactory implements SaslServerFactory +{ + public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map<String, ?> props, + CallbackHandler cbh) throws SaslException + { + if (mechanism.equals(CRAMMD5HashedSaslServer.MECHANISM)) + { + return new CRAMMD5HashedSaslServer(mechanism, protocol, serverName, props, cbh); + } + else + { + return null; + } + } + + public String[] getMechanismNames(Map props) + { + if (props != null) + { + if (props.containsKey(Sasl.POLICY_NOPLAINTEXT) || + props.containsKey(Sasl.POLICY_NODICTIONARY) || + props.containsKey(Sasl.POLICY_NOACTIVE)) + { + // returned array must be non null according to interface documentation + return new String[0]; + } + } + + return new String[]{CRAMMD5HashedSaslServer.MECHANISM}; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexInitialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexInitialiser.java new file mode 100644 index 0000000000..139818735f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexInitialiser.java @@ -0,0 +1,144 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl.crammd5; + +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; +import org.apache.qpid.server.security.auth.sasl.AuthenticationProviderInitialiser; + +import javax.security.sasl.SaslServerFactory; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.AccountNotFoundException; +import java.util.Map; +import java.util.List; +import java.security.Principal; +import java.io.IOException; + +public class CRAMMD5HexInitialiser extends UsernamePasswordInitialiser +{ + public String getMechanismName() + { + return CRAMMD5HexSaslServer.MECHANISM; + } + + public Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration() + { + return CRAMMD5HexServerFactory.class; + } + + public Map<String, ?> getProperties() + { + return null; + } + + public void initialise(PrincipalDatabase db) + { + super.initialise(new HexifyPrincipalDatabase(db)); + + } + + private static class HexifyPrincipalDatabase implements PrincipalDatabase + { + private PrincipalDatabase _realPricipalDatabase; + + HexifyPrincipalDatabase(PrincipalDatabase db) + { + _realPricipalDatabase = db; + } + + private char[] toHex(char[] password) + { + StringBuilder sb = new StringBuilder(); + for (char c : password) + { + //toHexString does not prepend 0 so we have to + if (((byte) c > -1) && (byte) c < 0x10 ) + { + sb.append(0); + } + + sb.append(Integer.toHexString(c & 0xFF)); + } + + //Extract the hex string as char[] + char[] hex = new char[sb.length()]; + + sb.getChars(0, sb.length(), hex, 0); + + return hex; + } + + public void setPassword(Principal principal, PasswordCallback callback) throws IOException, AccountNotFoundException + { + //Let the read DB set the password + _realPricipalDatabase.setPassword(principal, callback); + + //Retrieve the setpassword + char[] plainPassword = callback.getPassword(); + + char[] hexPassword = toHex(plainPassword); + + callback.setPassword(hexPassword); + } + + // Simply delegate to the real PrincipalDB + public boolean verifyPassword(String principal, char[] password) throws AccountNotFoundException + { + return _realPricipalDatabase.verifyPassword(principal, password); + } + + public boolean updatePassword(Principal principal, char[] password) throws AccountNotFoundException + { + return _realPricipalDatabase.updatePassword(principal, password); + } + + public boolean createPrincipal(Principal principal, char[] password) + { + return _realPricipalDatabase.createPrincipal(principal, password); + } + + public boolean deletePrincipal(Principal principal) throws AccountNotFoundException + { + return _realPricipalDatabase.deletePrincipal(principal); + } + + public Principal getUser(String username) + { + return _realPricipalDatabase.getUser(username); + } + + public Map<String, AuthenticationProviderInitialiser> getMechanisms() + { + return _realPricipalDatabase.getMechanisms(); + } + + public List<Principal> getUsers() + { + return _realPricipalDatabase.getUsers(); + } + + public void reload() throws IOException + { + _realPricipalDatabase.reload(); + } + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexSaslServer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexSaslServer.java new file mode 100644 index 0000000000..192ff74bff --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexSaslServer.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl.crammd5; + +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslException; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslServerFactory; +import javax.security.auth.callback.CallbackHandler; +import java.util.Enumeration; +import java.util.Map; + +public class CRAMMD5HexSaslServer implements SaslServer +{ + public static final String MECHANISM = "CRAM-MD5-HEX"; + + private SaslServer _realServer; + + public CRAMMD5HexSaslServer(String mechanism, String protocol, String serverName, Map<String, ?> props, + CallbackHandler cbh) throws SaslException + { + Enumeration factories = Sasl.getSaslServerFactories(); + + while (factories.hasMoreElements()) + { + SaslServerFactory factory = (SaslServerFactory) factories.nextElement(); + + if (factory instanceof CRAMMD5HexServerFactory) + { + continue; + } + + String[] mechs = factory.getMechanismNames(props); + + for (String mech : mechs) + { + if (mech.equals("CRAM-MD5")) + { + _realServer = factory.createSaslServer("CRAM-MD5", protocol, serverName, props, cbh); + return; + } + } + } + + throw new RuntimeException("No default SaslServer found for mechanism:" + "CRAM-MD5"); + } + + public String getMechanismName() + { + return MECHANISM; + } + + public byte[] evaluateResponse(byte[] response) throws SaslException + { + return _realServer.evaluateResponse(response); + } + + public boolean isComplete() + { + return _realServer.isComplete(); + } + + public String getAuthorizationID() + { + return _realServer.getAuthorizationID(); + } + + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException + { + return _realServer.unwrap(incoming, offset, len); + } + + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException + { + return _realServer.wrap(outgoing, offset, len); + } + + public Object getNegotiatedProperty(String propName) + { + return _realServer.getNegotiatedProperty(propName); + } + + public void dispose() throws SaslException + { + _realServer.dispose(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexServerFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexServerFactory.java new file mode 100644 index 0000000000..ce0e19abf9 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5HexServerFactory.java @@ -0,0 +1,61 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl.crammd5; + +import java.util.Map; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslServerFactory; + +public class CRAMMD5HexServerFactory implements SaslServerFactory +{ + public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map<String, ?> props, + CallbackHandler cbh) throws SaslException + { + if (mechanism.equals(CRAMMD5HexSaslServer.MECHANISM)) + { + return new CRAMMD5HexSaslServer(mechanism, protocol, serverName, props, cbh); + } + else + { + return null; + } + } + + public String[] getMechanismNames(Map props) + { + if (props != null) + { + if (props.containsKey(Sasl.POLICY_NOPLAINTEXT) || + props.containsKey(Sasl.POLICY_NODICTIONARY) || + props.containsKey(Sasl.POLICY_NOACTIVE)) + { + // returned array must be non null according to interface documentation + return new String[0]; + } + } + + return new String[]{CRAMMD5HexSaslServer.MECHANISM}; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5Initialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5Initialiser.java new file mode 100644 index 0000000000..264832888d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CRAMMD5Initialiser.java @@ -0,0 +1,71 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl.crammd5; + +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; +import org.apache.qpid.server.security.auth.database.PrincipalDatabase; + +import javax.security.sasl.SaslServerFactory; + +public class CRAMMD5Initialiser extends UsernamePasswordInitialiser +{ + private HashDirection _hashDirection; + + public enum HashDirection + { + INCOMMING, PASSWORD_FILE + } + + + public String getMechanismName() + { + return "CRAM-MD5"; + } + + public Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration() + { + // since the CRAM-MD5 provider is registered as part of the JDK, we do not + // return the factory class here since we do not need to register it ourselves. + if (_hashDirection == HashDirection.PASSWORD_FILE) + { + return null; + } + else + { + //fixme we need a server that will correctly has the incomming plain text for comparison to file. + _logger.warn("we need a server that will correctly convert the incomming plain text for comparison to file."); + return null; + } + } + + public void initialise(PrincipalDatabase passwordFile) + { + initialise(passwordFile, HashDirection.PASSWORD_FILE); + } + + public void initialise(PrincipalDatabase passwordFile, HashDirection direction) + { + super.initialise(passwordFile); + + _hashDirection = direction; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainInitialiser.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainInitialiser.java new file mode 100644 index 0000000000..1d16cd8755 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainInitialiser.java @@ -0,0 +1,38 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl.plain; + +import javax.security.sasl.SaslServerFactory; + +import org.apache.qpid.server.security.auth.sasl.UsernamePasswordInitialiser; + +public class PlainInitialiser extends UsernamePasswordInitialiser +{ + public String getMechanismName() + { + return "PLAIN"; + } + + public Class<? extends SaslServerFactory> getServerFactoryClassForJCARegistration() + { + return PlainSaslServerFactory.class; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainPasswordCallback.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainPasswordCallback.java new file mode 100644 index 0000000000..7230e8ee53 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainPasswordCallback.java @@ -0,0 +1,81 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl.plain; + +import java.util.Arrays; + +import javax.security.auth.callback.PasswordCallback; + +/** + * Custom PasswordCallback for use during the PLAIN authentication process. + * + * To be used in combination with PrincipalDatabase implementations that + * can either set a plain text value in the parent callback, or use the + * setAuthenticated(bool) method after observing the incoming plain text. + * + * isAuthenticated() should then be used to determine the final result. + * + */ +public class PlainPasswordCallback extends PasswordCallback +{ + private char[] _plainPassword; + private boolean _authenticated = false; + + /** + * Constructs a new PlainPasswordCallback with the incoming plain text password. + * + * @throws NullPointerException if the incoming plain text is null + */ + public PlainPasswordCallback(String prompt, boolean echoOn, String plainPassword) + { + super(prompt, echoOn); + + if(plainPassword == null) + { + throw new NullPointerException("Incoming plain text cannot be null"); + } + + _plainPassword = plainPassword.toCharArray(); + } + + public String getPlainPassword() + { + return new String(_plainPassword); + } + + public void setAuthenticated(boolean authenticated) + { + _authenticated = authenticated; + } + + /** + * Method to determine if the incoming plain password is authenticated + * + * @return true if the stored password matches the incoming text, or setAuthenticated(true) has been called + */ + public boolean isAuthenticated() + { + char[] storedPassword = getPassword(); + + return Arrays.equals(_plainPassword, storedPassword) || _authenticated; + } +} + diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServer.java new file mode 100644 index 0000000000..847a3a34ce --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServer.java @@ -0,0 +1,155 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl.plain; + +import java.io.IOException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.sasl.AuthorizeCallback; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +public class PlainSaslServer implements SaslServer +{ + public static final String MECHANISM = "PLAIN"; + + private CallbackHandler _cbh; + + private String _authorizationId; + + private boolean _complete = false; + + public PlainSaslServer(CallbackHandler cbh) + { + _cbh = cbh; + } + + public String getMechanismName() + { + return MECHANISM; + } + + public byte[] evaluateResponse(byte[] response) throws SaslException + { + try + { + int authzidNullPosition = findNullPosition(response, 0); + if (authzidNullPosition < 0) + { + throw new SaslException("Invalid PLAIN encoding, authzid null terminator not found"); + } + int authcidNullPosition = findNullPosition(response, authzidNullPosition + 1); + if (authcidNullPosition < 0) + { + throw new SaslException("Invalid PLAIN encoding, authcid null terminator not found"); + } + + // we do not currently support authcid in any meaningful way + // String authcid = new String(response, 0, authzidNullPosition, "utf8"); + String authzid = new String(response, authzidNullPosition + 1, authcidNullPosition - authzidNullPosition - 1, "utf8"); + + // TODO: should not get pwd as a String but as a char array... + int passwordLen = response.length - authcidNullPosition - 1; + String pwd = new String(response, authcidNullPosition + 1, passwordLen, "utf8"); + + // we do not care about the prompt but it throws if null + NameCallback nameCb = new NameCallback("prompt", authzid); + PlainPasswordCallback passwordCb = new PlainPasswordCallback("prompt", false, pwd); + AuthorizeCallback authzCb = new AuthorizeCallback(authzid, authzid); + + Callback[] callbacks = new Callback[]{nameCb, passwordCb, authzCb}; + _cbh.handle(callbacks); + + if (passwordCb.isAuthenticated()) + { + _complete = true; + } + if (authzCb.isAuthorized() && _complete) + { + _authorizationId = authzCb.getAuthenticationID(); + return null; + } + else + { + throw new SaslException("Authentication failed"); + } + } + catch (IOException e) + { + throw new SaslException("Error processing data: " + e, e); + } + catch (UnsupportedCallbackException e) + { + throw new SaslException("Unable to obtain data from callback handler: " + e, e); + } + } + + + + private int findNullPosition(byte[] response, int startPosition) + { + int position = startPosition; + while (position < response.length) + { + if (response[position] == (byte) 0) + { + return position; + } + position++; + } + return -1; + } + + public boolean isComplete() + { + return _complete; + } + + public String getAuthorizationID() + { + return _authorizationId; + } + + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException + { + throw new SaslException("Unsupported operation"); + } + + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException + { + throw new SaslException("Unsupported operation"); + } + + public Object getNegotiatedProperty(String propName) + { + return null; + } + + public void dispose() throws SaslException + { + _cbh = null; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerFactory.java new file mode 100644 index 0000000000..3144bfbce6 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/security/auth/sasl/plain/PlainSaslServerFactory.java @@ -0,0 +1,61 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.security.auth.sasl.plain; + +import java.util.Map; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; +import javax.security.sasl.SaslServerFactory; + +public class PlainSaslServerFactory implements SaslServerFactory +{ + public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map props, + CallbackHandler cbh) throws SaslException + { + if (PlainSaslServer.MECHANISM.equals(mechanism)) + { + return new PlainSaslServer(cbh); + } + else + { + return null; + } + } + + public String[] getMechanismNames(Map props) + { + if (props != null && + (props.containsKey(Sasl.POLICY_NOPLAINTEXT) || + props.containsKey(Sasl.POLICY_NODICTIONARY) || + props.containsKey(Sasl.POLICY_NOACTIVE))) + { + // returned array must be non null according to interface documentation + return new String[0]; + } + else + { + return new String[]{PlainSaslServer.MECHANISM}; + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/state/AMQState.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/AMQState.java new file mode 100644 index 0000000000..f427cc7206 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/AMQState.java @@ -0,0 +1,36 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.state; + +/** + * States used in the AMQ protocol. Used by the finite state machine to determine + * valid responses. + */ +public enum AMQState +{ + CONNECTION_NOT_STARTED, + CONNECTION_NOT_AUTH, + CONNECTION_NOT_TUNED, + CONNECTION_NOT_OPENED, + CONNECTION_OPEN, + CONNECTION_CLOSING, + CONNECTION_CLOSED +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java new file mode 100644 index 0000000000..6cc5e7b019 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/AMQStateManager.java @@ -0,0 +1,265 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.state; + +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArraySet; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQConnectionException; +import org.apache.qpid.framing.*; +import org.apache.qpid.protocol.AMQMethodEvent; +import org.apache.qpid.protocol.AMQMethodListener; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.handler.BasicAckMethodHandler; +import org.apache.qpid.server.handler.BasicCancelMethodHandler; +import org.apache.qpid.server.handler.BasicConsumeMethodHandler; +import org.apache.qpid.server.handler.BasicGetMethodHandler; +import org.apache.qpid.server.handler.BasicPublishMethodHandler; +import org.apache.qpid.server.handler.BasicQosHandler; +import org.apache.qpid.server.handler.BasicRecoverMethodHandler; +import org.apache.qpid.server.handler.BasicRejectMethodHandler; +import org.apache.qpid.server.handler.ChannelCloseHandler; +import org.apache.qpid.server.handler.ChannelCloseOkHandler; +import org.apache.qpid.server.handler.ChannelFlowHandler; +import org.apache.qpid.server.handler.ChannelOpenHandler; +import org.apache.qpid.server.handler.ConnectionCloseMethodHandler; +import org.apache.qpid.server.handler.ConnectionCloseOkMethodHandler; +import org.apache.qpid.server.handler.ConnectionOpenMethodHandler; +import org.apache.qpid.server.handler.ConnectionSecureOkMethodHandler; +import org.apache.qpid.server.handler.ConnectionStartOkMethodHandler; +import org.apache.qpid.server.handler.ConnectionTuneOkMethodHandler; +import org.apache.qpid.server.handler.ExchangeBoundHandler; +import org.apache.qpid.server.handler.ExchangeDeclareHandler; +import org.apache.qpid.server.handler.ExchangeDeleteHandler; +import org.apache.qpid.server.handler.QueueBindHandler; +import org.apache.qpid.server.handler.QueueDeclareHandler; +import org.apache.qpid.server.handler.QueueDeleteHandler; +import org.apache.qpid.server.handler.QueuePurgeHandler; +import org.apache.qpid.server.handler.TxCommitHandler; +import org.apache.qpid.server.handler.TxRollbackHandler; +import org.apache.qpid.server.handler.TxSelectHandler; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.virtualhost.VirtualHostRegistry; + +/** + * The state manager is responsible for managing the state of the protocol session. <p/> For each AMQProtocolHandler + * there is a separate state manager. + */ +public class AMQStateManager implements AMQMethodListener +{ + private static final Logger _logger = Logger.getLogger(AMQStateManager.class); + + private final VirtualHostRegistry _virtualHostRegistry; + private final AMQProtocolSession _protocolSession; + /** The current state */ + private AMQState _currentState; + + /** + * Maps from an AMQState instance to a Map from Class to StateTransitionHandler. The class must be a subclass of + * AMQFrame. + */ +/* private final EnumMap<AMQState, Map<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>> _state2HandlersMap = + new EnumMap<AMQState, Map<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>>( + AMQState.class); + */ + + + private CopyOnWriteArraySet<StateListener> _stateListeners = new CopyOnWriteArraySet<StateListener>(); + + public AMQStateManager(VirtualHostRegistry virtualHostRegistry, AMQProtocolSession protocolSession) + { + + _virtualHostRegistry = virtualHostRegistry; + _protocolSession = protocolSession; + _currentState = AMQState.CONNECTION_NOT_STARTED; + + } + + /* + protected void registerListeners() + { + Map<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>> frame2handlerMap; + + frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>(); + _state2HandlersMap.put(AMQState.CONNECTION_NOT_STARTED, frame2handlerMap); + + frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>(); + _state2HandlersMap.put(AMQState.CONNECTION_NOT_AUTH, frame2handlerMap); + + frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>(); + _state2HandlersMap.put(AMQState.CONNECTION_NOT_TUNED, frame2handlerMap); + + frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>(); + frame2handlerMap.put(ConnectionOpenBody.class, ConnectionOpenMethodHandler.getInstance()); + _state2HandlersMap.put(AMQState.CONNECTION_NOT_OPENED, frame2handlerMap); + + // + // ConnectionOpen handlers + // + frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>(); + ChannelOpenHandler.getInstance(); + ChannelCloseHandler.getInstance(); + ChannelCloseOkHandler.getInstance(); + ConnectionCloseMethodHandler.getInstance(); + ConnectionCloseOkMethodHandler.getInstance(); + ConnectionTuneOkMethodHandler.getInstance(); + ConnectionSecureOkMethodHandler.getInstance(); + ConnectionStartOkMethodHandler.getInstance(); + ExchangeDeclareHandler.getInstance(); + ExchangeDeleteHandler.getInstance(); + ExchangeBoundHandler.getInstance(); + BasicAckMethodHandler.getInstance(); + BasicRecoverMethodHandler.getInstance(); + BasicConsumeMethodHandler.getInstance(); + BasicGetMethodHandler.getInstance(); + BasicCancelMethodHandler.getInstance(); + BasicPublishMethodHandler.getInstance(); + BasicQosHandler.getInstance(); + QueueBindHandler.getInstance(); + QueueDeclareHandler.getInstance(); + QueueDeleteHandler.getInstance(); + QueuePurgeHandler.getInstance(); + ChannelFlowHandler.getInstance(); + TxSelectHandler.getInstance(); + TxCommitHandler.getInstance(); + TxRollbackHandler.getInstance(); + BasicRejectMethodHandler.getInstance(); + + _state2HandlersMap.put(AMQState.CONNECTION_OPEN, frame2handlerMap); + + frame2handlerMap = new HashMap<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>>(); + + _state2HandlersMap.put(AMQState.CONNECTION_CLOSING, frame2handlerMap); + + } */ + + public AMQState getCurrentState() + { + return _currentState; + } + + public void changeState(AMQState newState) throws AMQException + { + _logger.debug("State changing to " + newState + " from old state " + _currentState); + final AMQState oldState = _currentState; + _currentState = newState; + + for (StateListener l : _stateListeners) + { + l.stateChanged(oldState, newState); + } + } + + public void error(Exception e) + { + _logger.error("State manager received error notification[Current State:" + _currentState + "]: " + e, e); + for (StateListener l : _stateListeners) + { + l.error(e); + } + } + + public <B extends AMQMethodBody> boolean methodReceived(AMQMethodEvent<B> evt) throws AMQException + { + MethodDispatcher dispatcher = _protocolSession.getMethodDispatcher(); + + final int channelId = evt.getChannelId(); + B body = evt.getMethod(); + + if(channelId != 0 && _protocolSession.getChannel(channelId)== null) + { + + if(! ((body instanceof ChannelOpenBody) + || (body instanceof ChannelCloseOkBody) + || (body instanceof ChannelCloseBody))) + { + throw body.getConnectionException(AMQConstant.CHANNEL_ERROR, "channel is closed won't process:" + body); + } + + } + + return body.execute(dispatcher, channelId); + + } + + private <B extends AMQMethodBody> void checkChannel(AMQMethodEvent<B> evt, AMQProtocolSession protocolSession) + throws AMQException + { + if ((evt.getChannelId() != 0) && !(evt.getMethod() instanceof ChannelOpenBody) + && (protocolSession.getChannel(evt.getChannelId()) == null) + && !protocolSession.channelAwaitingClosure(evt.getChannelId())) + { + throw evt.getMethod().getChannelNotFoundException(evt.getChannelId()); + } + } + +/* + protected <B extends AMQMethodBody> StateAwareMethodListener<B> findStateTransitionHandler(AMQState currentState, + B frame) + // throws IllegalStateTransitionException + { + final Map<Class<? extends AMQMethodBody>, StateAwareMethodListener<? extends AMQMethodBody>> classToHandlerMap = + _state2HandlersMap.get(currentState); + + final StateAwareMethodListener<B> handler = + (classToHandlerMap == null) ? null : (StateAwareMethodListener<B>) classToHandlerMap.get(frame.getClass()); + + if (handler == null) + { + _logger.debug("No state transition handler defined for receiving frame " + frame); + + return null; + } + else + { + return handler; + } + } +*/ + + public void addStateListener(StateListener listener) + { + _logger.debug("Adding state listener"); + _stateListeners.add(listener); + } + + public void removeStateListener(StateListener listener) + { + _stateListeners.remove(listener); + } + + public VirtualHostRegistry getVirtualHostRegistry() + { + return _virtualHostRegistry; + } + + public AMQProtocolSession getProtocolSession() + { + SecurityManager.setThreadPrincipal(_protocolSession.getPrincipal()); + return _protocolSession; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/state/IllegalStateTransitionException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/IllegalStateTransitionException.java new file mode 100644 index 0000000000..cec67a8a6d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/IllegalStateTransitionException.java @@ -0,0 +1,52 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.state; + +import org.apache.qpid.AMQException; + +/** + * @todo Not an AMQP exception as no status code. + * + * @todo Not used! Delete. + */ +public class IllegalStateTransitionException extends AMQException +{ + private AMQState _originalState; + + private Class _frame; + + public IllegalStateTransitionException(AMQState originalState, Class frame) + { + super("No valid state transition defined for receiving frame " + frame + " from state " + originalState); + _originalState = originalState; + _frame = frame; + } + + public AMQState getOriginalState() + { + return _originalState; + } + + public Class getFrameClass() + { + return _frame; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/state/StateAwareMethodListener.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/StateAwareMethodListener.java new file mode 100644 index 0000000000..3c11bb8a9c --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/StateAwareMethodListener.java @@ -0,0 +1,35 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.state; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQMethodBody; +import org.apache.qpid.protocol.AMQMethodEvent; + +/** + * A frame listener that is informed of the protocol state when invoked and has + * the opportunity to update state. + * + */ +public interface StateAwareMethodListener<B extends AMQMethodBody> +{ + void methodReceived(AMQStateManager stateManager, B evt, int channelId) throws AMQException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/state/StateListener.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/StateListener.java new file mode 100644 index 0000000000..00fc09867b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/state/StateListener.java @@ -0,0 +1,30 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.state; + +import org.apache.qpid.AMQException; + +public interface StateListener +{ + void stateChanged(AMQState oldState, AMQState newState) throws AMQException; + + void error(Throwable t); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/stats/StatisticsCounter.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/stats/StatisticsCounter.java new file mode 100644 index 0000000000..b732121180 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/stats/StatisticsCounter.java @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.stats; + +import java.util.Date; +import java.util.concurrent.atomic.AtomicLong; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class collects statistics and counts the total, rate per second and + * peak rate per second values for the events that are registered with it. + */ +public class StatisticsCounter +{ + private static final Logger _log = LoggerFactory.getLogger(StatisticsCounter.class); + + public static final long DEFAULT_SAMPLE_PERIOD = Long.getLong("qpid.statistics.samplePeriod", 2000L); // 2s + public static final boolean DISABLE_STATISTICS = Boolean.getBoolean("qpid.statistics.disable"); + + private static final String COUNTER = "counter"; + private static final AtomicLong _counterIds = new AtomicLong(0L); + + private long _peak = 0L; + private long _total = 0L; + private long _temp = 0L; + private long _last = 0L; + private long _rate = 0L; + + private long _start; + + private final long _period; + private final String _name; + + public StatisticsCounter() + { + this(COUNTER); + } + + public StatisticsCounter(String name) + { + this(name, DEFAULT_SAMPLE_PERIOD); + } + + public StatisticsCounter(String name, long period) + { + _period = period; + _name = name + "-" + + _counterIds.incrementAndGet(); + reset(); + } + + public void registerEvent() + { + registerEvent(1L); + } + + public void registerEvent(long value) + { + registerEvent(value, System.currentTimeMillis()); + } + + public void registerEvent(long value, long timestamp) + { + if (DISABLE_STATISTICS) + { + return; + } + + long thisSample = (timestamp / _period); + synchronized (this) + { + if (thisSample > _last) + { + _last = thisSample; + _rate = _temp; + _temp = 0L; + if (_rate > _peak) + { + _peak = _rate; + } + } + + _total += value; + _temp += value; + } + } + + /** + * Update the current rate and peak - may reset rate to zero if a new + * sample period has started. + */ + private void update() + { + registerEvent(0L, System.currentTimeMillis()); + } + + /** + * Reset + */ + public void reset() + { + _log.info("Resetting statistics for counter: " + _name); + _peak = 0L; + _rate = 0L; + _total = 0L; + _start = System.currentTimeMillis(); + _last = _start / _period; + } + + public double getPeak() + { + update(); + return (double) _peak / ((double) _period / 1000.0d); + } + + public double getRate() + { + update(); + return (double) _rate / ((double) _period / 1000.0d); + } + + public long getTotal() + { + return _total; + } + + public long getStart() + { + return _start; + } + + public Date getStartTime() + { + return new Date(_start); + } + + public String getName() + { + return _name; + } + + public long getPeriod() + { + return _period; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/stats/StatisticsGatherer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/stats/StatisticsGatherer.java new file mode 100644 index 0000000000..36fec4025a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/stats/StatisticsGatherer.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.stats; + +/** + * This interface is to be implemented by any broker business object that + * wishes to gather statistics about messages delivered through it. + * + * These statistics are exposed using a separate JMX Mbean interface, which + * calls these methods to retrieve the underlying {@link StatisticsCounter}s + * and return their attributes. This interface gives a standard way for + * parts of the broker to set up and configure statistics generation. + * <p> + * When creating these objects, there should be a parent/child relationship + * between them, such that the lowest level gatherer can record staticics if + * enabled, and pass on the notification to the parent object to allow higher + * level aggregation. When resetting statistics, this works in the opposite + * direction, with higher level gatherers also resetting all of their children. + */ +public interface StatisticsGatherer +{ + /** + * Initialise the statistics gathering for this object. + * + * This method is responsible for creating any {@link StatisticsCounter} + * objects and for determining whether statistics generation should be + * enabled, by checking broker and system configuration. + * + * @see StatisticsCounter#DISABLE_STATISTICS + */ + void initialiseStatistics(); + + /** + * This method is responsible for registering the receipt of a message + * with the counters, and also for passing this notification to any parent + * {@link StatisticsGatherer}s. If statistics generation is not enabled, + * then this method should simple delegate to the parent gatherer. + * + * @param messageSize the size in bytes of the delivered message + * @param timestamp the time the message was delivered + */ + void registerMessageReceived(long messageSize, long timestamp); + + /** + * This method is responsible for registering the delivery of a message + * with the counters. Message delivery is recorded by the counter using + * the current system time, as opposed to the message timestamp. + * + * @param messageSize the size in bytes of the delivered message + * @see #registerMessageReceived(long, long) + */ + void registerMessageDelivered(long messageSize); + + /** + * Gives access to the {@link StatisticsCounter} that is used to count + * delivered message statistics. + * + * @return the {@link StatisticsCounter} that counts delivered messages + */ + StatisticsCounter getMessageDeliveryStatistics(); + + /** + * Gives access to the {@link StatisticsCounter} that is used to count + * received message statistics. + * + * @return the {@link StatisticsCounter} that counts received messages + */ + StatisticsCounter getMessageReceiptStatistics(); + + /** + * Gives access to the {@link StatisticsCounter} that is used to count + * delivered message size statistics. + * + * @return the {@link StatisticsCounter} that counts delivered bytes + */ + StatisticsCounter getDataDeliveryStatistics(); + + /** + * Gives access to the {@link StatisticsCounter} that is used to count + * received message size statistics. + * + * @return the {@link StatisticsCounter} that counts received bytes + */ + StatisticsCounter getDataReceiptStatistics(); + + /** + * Reset the counters for this, and any child {@link StatisticsGatherer}s. + */ + void resetStatistics(); + + /** + * Check if this object has statistics generation enabled. + * + * @return true if statistics generation is enabled + */ + boolean isStatisticsEnabled(); + + /** + * Enable or disable statistics generation for this object. + */ + void setStatisticsEnabled(boolean enabled); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/AbstractMessageStore.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/AbstractMessageStore.java new file mode 100644 index 0000000000..b9adaeacdf --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/AbstractMessageStore.java @@ -0,0 +1,43 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store; + +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.MessageStoreMessages; +import org.apache.qpid.server.logging.subjects.MessageStoreLogSubject; +import org.apache.qpid.server.logging.LogSubject; + +public abstract class AbstractMessageStore implements MessageStore +{ + protected LogSubject _logSubject; + + public void configure(VirtualHost virtualHost) throws Exception + { + _logSubject = new MessageStoreLogSubject(virtualHost, this); + CurrentActor.get().message(_logSubject, MessageStoreMessages.CREATED(this.getClass().getName())); + } + + public void close() throws Exception + { + CurrentActor.get().message(_logSubject,MessageStoreMessages.CLOSED()); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/ConfigurationRecoveryHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/ConfigurationRecoveryHandler.java new file mode 100755 index 0000000000..a883f656be --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/ConfigurationRecoveryHandler.java @@ -0,0 +1,57 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.store; + +import java.nio.ByteBuffer; +import org.apache.qpid.framing.FieldTable; + +public interface ConfigurationRecoveryHandler +{ + QueueRecoveryHandler begin(MessageStore store); + + public static interface QueueRecoveryHandler + { + void queue(String queueName, String owner, boolean exclusive, FieldTable arguments); + ExchangeRecoveryHandler completeQueueRecovery(); + } + + public static interface ExchangeRecoveryHandler + { + void exchange(String exchangeName, String type, boolean autoDelete); + BindingRecoveryHandler completeExchangeRecovery(); + } + + public static interface BindingRecoveryHandler + { + void binding(String exchangeName, String queueName, String bindingKey, ByteBuffer buf); + void completeBindingRecovery(); + } + + public static interface QueueEntryRecoveryHandler + { + void complete(); + + void queueEntry(String queueName, long messageId); + } + + + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/DerbyMessageStore.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/DerbyMessageStore.java new file mode 100644 index 0000000000..2e694b24ea --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/DerbyMessageStore.java @@ -0,0 +1,1846 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.store; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.lang.ref.SoftReference; +import java.nio.ByteBuffer; +import java.sql.Blob; +import java.sql.Connection; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.commons.configuration.Configuration; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.ConfigStoreMessages; +import org.apache.qpid.server.logging.messages.MessageStoreMessages; +import org.apache.qpid.server.logging.messages.TransactionLogMessages; +import org.apache.qpid.server.queue.AMQQueue; + +/** + * An implementation of a {@link MessageStore} that uses Apache Derby as the persistance + * mechanism. + * + * TODO extract the SQL statements into a generic JDBC store + */ +public class DerbyMessageStore implements MessageStore +{ + + private static final Logger _logger = Logger.getLogger(DerbyMessageStore.class); + + public static final String ENVIRONMENT_PATH_PROPERTY = "environment-path"; + + + private static final String SQL_DRIVER_NAME = "org.apache.derby.jdbc.EmbeddedDriver"; + + private static final String DB_VERSION_TABLE_NAME = "QPID_DB_VERSION"; + + private static final String EXCHANGE_TABLE_NAME = "QPID_EXCHANGE"; + private static final String QUEUE_TABLE_NAME = "QPID_QUEUE"; + private static final String BINDINGS_TABLE_NAME = "QPID_BINDINGS"; + private static final String QUEUE_ENTRY_TABLE_NAME = "QPID_QUEUE_ENTRY"; + + private static final String META_DATA_TABLE_NAME = "QPID_META_DATA"; + private static final String MESSAGE_CONTENT_TABLE_NAME = "QPID_MESSAGE_CONTENT"; + + private static final int DB_VERSION = 3; + + + + private static Class<Driver> DRIVER_CLASS; + + private final AtomicLong _messageId = new AtomicLong(0); + private AtomicBoolean _closed = new AtomicBoolean(false); + + private String _connectionURL; + + private static final String TABLE_EXISTANCE_QUERY = "SELECT 1 FROM SYS.SYSTABLES WHERE TABLENAME = ?"; + + private static final String CREATE_DB_VERSION_TABLE = "CREATE TABLE "+DB_VERSION_TABLE_NAME+" ( version int not null )"; + private static final String INSERT_INTO_DB_VERSION = "INSERT INTO "+DB_VERSION_TABLE_NAME+" ( version ) VALUES ( ? )"; + + private static final String CREATE_EXCHANGE_TABLE = "CREATE TABLE "+EXCHANGE_TABLE_NAME+" ( name varchar(255) not null, type varchar(255) not null, autodelete SMALLINT not null, PRIMARY KEY ( name ) )"; + private static final String CREATE_QUEUE_TABLE = "CREATE TABLE "+QUEUE_TABLE_NAME+" ( name varchar(255) not null, owner varchar(255), exclusive SMALLINT not null, arguments blob, PRIMARY KEY ( name ))"; + private static final String CREATE_BINDINGS_TABLE = "CREATE TABLE "+BINDINGS_TABLE_NAME+" ( exchange_name varchar(255) not null, queue_name varchar(255) not null, binding_key varchar(255) not null, arguments blob , PRIMARY KEY ( exchange_name, queue_name, binding_key ) )"; + private static final String SELECT_FROM_QUEUE = "SELECT name, owner, exclusive, arguments FROM " + QUEUE_TABLE_NAME; + private static final String FIND_QUEUE = "SELECT name, owner FROM " + QUEUE_TABLE_NAME + " WHERE name = ?"; + private static final String UPDATE_QUEUE_EXCLUSIVITY = "UPDATE " + QUEUE_TABLE_NAME + " SET exclusive = ? WHERE name = ?"; + private static final String SELECT_FROM_EXCHANGE = "SELECT name, type, autodelete FROM " + EXCHANGE_TABLE_NAME; + private static final String SELECT_FROM_BINDINGS = + "SELECT exchange_name, queue_name, binding_key, arguments FROM " + BINDINGS_TABLE_NAME + " ORDER BY exchange_name"; + private static final String FIND_BINDING = + "SELECT * FROM " + BINDINGS_TABLE_NAME + " WHERE exchange_name = ? AND queue_name = ? AND binding_key = ? "; + private static final String INSERT_INTO_EXCHANGE = "INSERT INTO " + EXCHANGE_TABLE_NAME + " ( name, type, autodelete ) VALUES ( ?, ?, ? )"; + private static final String DELETE_FROM_EXCHANGE = "DELETE FROM " + EXCHANGE_TABLE_NAME + " WHERE name = ?"; + private static final String FIND_EXCHANGE = "SELECT name FROM " + EXCHANGE_TABLE_NAME + " WHERE name = ?"; + private static final String INSERT_INTO_BINDINGS = "INSERT INTO " + BINDINGS_TABLE_NAME + " ( exchange_name, queue_name, binding_key, arguments ) values ( ?, ?, ?, ? )"; + private static final String DELETE_FROM_BINDINGS = "DELETE FROM " + BINDINGS_TABLE_NAME + " WHERE exchange_name = ? AND queue_name = ? AND binding_key = ?"; + private static final String INSERT_INTO_QUEUE = "INSERT INTO " + QUEUE_TABLE_NAME + " (name, owner, exclusive, arguments) VALUES (?, ?, ?, ?)"; + private static final String DELETE_FROM_QUEUE = "DELETE FROM " + QUEUE_TABLE_NAME + " WHERE name = ?"; + + private static final String CREATE_QUEUE_ENTRY_TABLE = "CREATE TABLE "+QUEUE_ENTRY_TABLE_NAME+" ( queue_name varchar(255) not null, message_id bigint not null, PRIMARY KEY (queue_name, message_id) )"; + private static final String INSERT_INTO_QUEUE_ENTRY = "INSERT INTO " + QUEUE_ENTRY_TABLE_NAME + " (queue_name, message_id) values (?,?)"; + private static final String DELETE_FROM_QUEUE_ENTRY = "DELETE FROM " + QUEUE_ENTRY_TABLE_NAME + " WHERE queue_name = ? AND message_id =?"; + private static final String SELECT_FROM_QUEUE_ENTRY = "SELECT queue_name, message_id FROM " + QUEUE_ENTRY_TABLE_NAME + " ORDER BY queue_name, message_id"; + + + private static final String CREATE_META_DATA_TABLE = "CREATE TABLE "+META_DATA_TABLE_NAME+" ( message_id bigint not null, meta_data blob, PRIMARY KEY ( message_id ) )"; + private static final String CREATE_MESSAGE_CONTENT_TABLE = "CREATE TABLE "+MESSAGE_CONTENT_TABLE_NAME+" ( message_id bigint not null, offset int not null, last_byte int not null, content blob , PRIMARY KEY (message_id, offset) )"; + + private static final String INSERT_INTO_MESSAGE_CONTENT = "INSERT INTO " + MESSAGE_CONTENT_TABLE_NAME + "( message_id, offset, last_byte, content ) values (?, ?, ?, ?)"; + private static final String SELECT_FROM_MESSAGE_CONTENT = + "SELECT offset, content FROM " + MESSAGE_CONTENT_TABLE_NAME + " WHERE message_id = ? AND last_byte > ? AND offset < ? ORDER BY message_id, offset"; + private static final String DELETE_FROM_MESSAGE_CONTENT = "DELETE FROM " + MESSAGE_CONTENT_TABLE_NAME + " WHERE message_id = ?"; + + private static final String INSERT_INTO_META_DATA = "INSERT INTO " + META_DATA_TABLE_NAME + "( message_id , meta_data ) values (?, ?)";; + private static final String SELECT_FROM_META_DATA = + "SELECT meta_data FROM " + META_DATA_TABLE_NAME + " WHERE message_id = ?"; + private static final String DELETE_FROM_META_DATA = "DELETE FROM " + META_DATA_TABLE_NAME + " WHERE message_id = ?"; + private static final String SELECT_ALL_FROM_META_DATA = "SELECT message_id, meta_data FROM " + META_DATA_TABLE_NAME; + + private static final String DERBY_SINGLE_DB_SHUTDOWN_CODE = "08006"; + + + private LogSubject _logSubject; + private boolean _configured; + + + private enum State + { + INITIAL, + CONFIGURING, + RECOVERING, + STARTED, + CLOSING, + CLOSED + } + + private State _state = State.INITIAL; + + + public void configureConfigStore(String name, + ConfigurationRecoveryHandler recoveryHandler, + Configuration storeConfiguration, + LogSubject logSubject) throws Exception + { + stateTransition(State.INITIAL, State.CONFIGURING); + _logSubject = logSubject; + CurrentActor.get().message(_logSubject, ConfigStoreMessages.CREATED(this.getClass().getName())); + + if(!_configured) + { + commonConfiguration(name, storeConfiguration, logSubject); + _configured = true; + } + + // this recovers durable exchanges, queues, and bindings + recover(recoveryHandler); + + + stateTransition(State.RECOVERING, State.STARTED); + + } + + + public void configureMessageStore(String name, + MessageStoreRecoveryHandler recoveryHandler, + Configuration storeConfiguration, + LogSubject logSubject) throws Exception + { + CurrentActor.get().message(_logSubject, MessageStoreMessages.CREATED(this.getClass().getName())); + + if(!_configured) + { + + _logSubject = logSubject; + + commonConfiguration(name, storeConfiguration, logSubject); + _configured = true; + } + + recoverMessages(recoveryHandler); + + } + + + + public void configureTransactionLog(String name, + TransactionLogRecoveryHandler recoveryHandler, + Configuration storeConfiguration, + LogSubject logSubject) throws Exception + { + CurrentActor.get().message(_logSubject, TransactionLogMessages.CREATED(this.getClass().getName())); + + if(!_configured) + { + + _logSubject = logSubject; + + commonConfiguration(name, storeConfiguration, logSubject); + _configured = true; + } + + recoverQueueEntries(recoveryHandler); + + } + + + + private void commonConfiguration(String name, Configuration storeConfiguration, LogSubject logSubject) + throws ClassNotFoundException, SQLException + { + initialiseDriver(); + + //Update to pick up QPID_WORK and use that as the default location not just derbyDB + + final String databasePath = storeConfiguration.getString(ENVIRONMENT_PATH_PROPERTY, System.getProperty("QPID_WORK") + + File.separator + "derbyDB"); + + File environmentPath = new File(databasePath); + if (!environmentPath.exists()) + { + if (!environmentPath.mkdirs()) + { + throw new IllegalArgumentException("Environment path " + environmentPath + " could not be read or created. " + + "Ensure the path is correct and that the permissions are correct."); + } + } + + CurrentActor.get().message(_logSubject, MessageStoreMessages.STORE_LOCATION(environmentPath.getAbsolutePath())); + + createOrOpenDatabase(name, databasePath); + } + + private static synchronized void initialiseDriver() throws ClassNotFoundException + { + if(DRIVER_CLASS == null) + { + DRIVER_CLASS = (Class<Driver>) Class.forName(SQL_DRIVER_NAME); + } + } + + private void createOrOpenDatabase(String name, final String environmentPath) throws SQLException + { + //FIXME this the _vhost name should not be added here, but derby wont use an empty directory as was possibly just created. + _connectionURL = "jdbc:derby:" + environmentPath + "/" + name + ";create=true"; + + Connection conn = newAutoCommitConnection(); + + createVersionTable(conn); + createExchangeTable(conn); + createQueueTable(conn); + createBindingsTable(conn); + createQueueEntryTable(conn); + createMetaDataTable(conn); + createMessageContentTable(conn); + + conn.close(); + } + + + + private void createVersionTable(final Connection conn) throws SQLException + { + if(!tableExists(DB_VERSION_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + try + { + stmt.execute(CREATE_DB_VERSION_TABLE); + } + finally + { + stmt.close(); + } + + PreparedStatement pstmt = conn.prepareStatement(INSERT_INTO_DB_VERSION); + try + { + pstmt.setInt(1, DB_VERSION); + pstmt.execute(); + } + finally + { + pstmt.close(); + } + } + + } + + + private void createExchangeTable(final Connection conn) throws SQLException + { + if(!tableExists(EXCHANGE_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + try + { + stmt.execute(CREATE_EXCHANGE_TABLE); + } + finally + { + stmt.close(); + } + } + } + + private void createQueueTable(final Connection conn) throws SQLException + { + if(!tableExists(QUEUE_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + try + { + stmt.execute(CREATE_QUEUE_TABLE); + } + finally + { + stmt.close(); + } + } + } + + private void createBindingsTable(final Connection conn) throws SQLException + { + if(!tableExists(BINDINGS_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + try + { + stmt.execute(CREATE_BINDINGS_TABLE); + } + finally + { + stmt.close(); + } + } + + } + + private void createQueueEntryTable(final Connection conn) throws SQLException + { + if(!tableExists(QUEUE_ENTRY_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + try + { + stmt.execute(CREATE_QUEUE_ENTRY_TABLE); + } + finally + { + stmt.close(); + } + } + + } + + private void createMetaDataTable(final Connection conn) throws SQLException + { + if(!tableExists(META_DATA_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + try + { + stmt.execute(CREATE_META_DATA_TABLE); + } + finally + { + stmt.close(); + } + } + + } + + + private void createMessageContentTable(final Connection conn) throws SQLException + { + if(!tableExists(MESSAGE_CONTENT_TABLE_NAME, conn)) + { + Statement stmt = conn.createStatement(); + try + { + stmt.execute(CREATE_MESSAGE_CONTENT_TABLE); + } + finally + { + stmt.close(); + } + } + + } + + + + private boolean tableExists(final String tableName, final Connection conn) throws SQLException + { + PreparedStatement stmt = conn.prepareStatement(TABLE_EXISTANCE_QUERY); + try + { + stmt.setString(1, tableName); + ResultSet rs = stmt.executeQuery(); + try + { + return rs.next(); + } + finally + { + rs.close(); + } + } + finally + { + stmt.close(); + } + + } + + public void recover(ConfigurationRecoveryHandler recoveryHandler) throws AMQException + { + stateTransition(State.CONFIGURING, State.RECOVERING); + + CurrentActor.get().message(_logSubject,MessageStoreMessages.RECOVERY_START()); + + try + { + ConfigurationRecoveryHandler.QueueRecoveryHandler qrh = recoveryHandler.begin(this); + loadQueues(qrh); + + ConfigurationRecoveryHandler.ExchangeRecoveryHandler erh = qrh.completeQueueRecovery(); + List<String> exchanges = loadExchanges(erh); + ConfigurationRecoveryHandler.BindingRecoveryHandler brh = erh.completeExchangeRecovery(); + recoverBindings(brh, exchanges); + brh.completeBindingRecovery(); + } + catch (SQLException e) + { + + throw new AMQStoreException("Error recovering persistent state: " + e.getMessage(), e); + } + + + } + + private void loadQueues(ConfigurationRecoveryHandler.QueueRecoveryHandler qrh) throws SQLException + { + Connection conn = newAutoCommitConnection(); + try + { + Statement stmt = conn.createStatement(); + try + { + ResultSet rs = stmt.executeQuery(SELECT_FROM_QUEUE); + try + { + + while(rs.next()) + { + String queueName = rs.getString(1); + String owner = rs.getString(2); + boolean exclusive = rs.getBoolean(3); + Blob argumentsAsBlob = rs.getBlob(4); + + byte[] dataAsBytes = argumentsAsBlob.getBytes(1,(int) argumentsAsBlob.length()); + FieldTable arguments; + if(dataAsBytes.length > 0) + { + org.apache.mina.common.ByteBuffer buffer = org.apache.mina.common.ByteBuffer.wrap(dataAsBytes); + + arguments = new FieldTable(buffer,buffer.limit()); + } + else + { + arguments = null; + } + + qrh.queue(queueName, owner, exclusive, arguments); + + } + + } + finally + { + rs.close(); + } + } + finally + { + stmt.close(); + } + } + finally + { + conn.close(); + } + } + + + private List<String> loadExchanges(ConfigurationRecoveryHandler.ExchangeRecoveryHandler erh) throws SQLException + { + + List<String> exchanges = new ArrayList<String>(); + Connection conn = null; + try + { + conn = newAutoCommitConnection(); + + Statement stmt = conn.createStatement(); + try + { + ResultSet rs = stmt.executeQuery(SELECT_FROM_EXCHANGE); + try + { + while(rs.next()) + { + String exchangeName = rs.getString(1); + String type = rs.getString(2); + boolean autoDelete = rs.getShort(3) != 0; + + exchanges.add(exchangeName); + + erh.exchange(exchangeName, type, autoDelete); + + } + return exchanges; + } + finally + { + rs.close(); + } + } + finally + { + stmt.close(); + } + } + finally + { + if(conn != null) + { + conn.close(); + } + } + + } + + private void recoverBindings(ConfigurationRecoveryHandler.BindingRecoveryHandler brh, List<String> exchanges) throws SQLException + { + _logger.info("Recovering bindings..."); + + Connection conn = null; + try + { + conn = newAutoCommitConnection(); + + PreparedStatement stmt = conn.prepareStatement(SELECT_FROM_BINDINGS); + + try + { + ResultSet rs = stmt.executeQuery(); + + try + { + + while(rs.next()) + { + String exchangeName = rs.getString(1); + String queueName = rs.getString(2); + String bindingKey = rs.getString(3); + Blob arguments = rs.getBlob(4); + java.nio.ByteBuffer buf; + + if(arguments != null && arguments.length() != 0) + { + byte[] argumentBytes = arguments.getBytes(1, (int) arguments.length()); + buf = java.nio.ByteBuffer.wrap(argumentBytes); + } + else + { + buf = null; + } + + brh.binding(exchangeName, queueName, bindingKey, buf); + } + } + finally + { + rs.close(); + } + } + finally + { + stmt.close(); + } + + } + finally + { + if(conn != null) + { + conn.close(); + } + } + } + + + + public void close() throws Exception + { + CurrentActor.get().message(_logSubject,MessageStoreMessages.CLOSED()); + _closed.getAndSet(true); + + try + { + Connection conn = DriverManager.getConnection(_connectionURL + ";shutdown=true"); + // Shouldn't reach this point - shutdown=true should throw SQLException + conn.close(); + _logger.error("Unable to shut down the store"); + } + catch (SQLException e) + { + if (e.getSQLState().equalsIgnoreCase(DERBY_SINGLE_DB_SHUTDOWN_CODE)) + { + //expected and represents a clean shutdown of this database only, do nothing. + } + else + { + _logger.error("Exception whilst shutting down the store: " + e); + } + } + } + + public StoredMessage addMessage(StorableMessageMetaData metaData) + { + if(metaData.isPersistent()) + { + return new StoredDerbyMessage(_messageId.incrementAndGet(), metaData); + } + else + { + return new StoredMemoryMessage(_messageId.incrementAndGet(), metaData); + } + } + + public StoredMessage getMessage(long messageNumber) + { + return null; + } + + public void removeMessage(long messageId) + { + try + { + Connection conn = newConnection(); + try + { + PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_META_DATA); + try + { + stmt.setLong(1,messageId); + int results = stmt.executeUpdate(); + stmt.close(); + + if (results == 0) + { + throw new RuntimeException("Message metadata not found for message id " + messageId); + } + + if (_logger.isDebugEnabled()) + { + _logger.debug("Deleted metadata for message " + messageId); + } + + stmt = conn.prepareStatement(DELETE_FROM_MESSAGE_CONTENT); + stmt.setLong(1,messageId); + results = stmt.executeUpdate(); + } + finally + { + stmt.close(); + } + conn.commit(); + } + catch(SQLException e) + { + try + { + conn.rollback(); + } + catch(SQLException t) + { + // ignore - we are re-throwing underlying exception + } + + throw e; + + } + finally + { + conn.close(); + } + } + catch (SQLException e) + { + throw new RuntimeException("Error removing message with id " + messageId + " from database: " + e.getMessage(), e); + } + + } + + public void createExchange(Exchange exchange) throws AMQStoreException + { + if (_state != State.RECOVERING) + { + try + { + Connection conn = newAutoCommitConnection(); + + try + { + + + PreparedStatement stmt = conn.prepareStatement(FIND_EXCHANGE); + try + { + stmt.setString(1, exchange.getNameShortString().toString()); + ResultSet rs = stmt.executeQuery(); + try + { + + // If we don't have any data in the result set then we can add this exchange + if (!rs.next()) + { + + PreparedStatement insertStmt = conn.prepareStatement(INSERT_INTO_EXCHANGE); + try + { + insertStmt.setString(1, exchange.getName().toString()); + insertStmt.setString(2, exchange.getTypeShortString().asString()); + insertStmt.setShort(3, exchange.isAutoDelete() ? (short) 1 : (short) 0); + insertStmt.execute(); + } + finally + { + insertStmt.close(); + } + } + } + finally + { + rs.close(); + } + } + finally + { + stmt.close(); + } + + } + finally + { + conn.close(); + } + } + catch (SQLException e) + { + throw new AMQStoreException("Error writing Exchange with name " + exchange.getNameShortString() + " to database: " + e.getMessage(), e); + } + } + + } + + public void removeExchange(Exchange exchange) throws AMQStoreException + { + + try + { + Connection conn = newAutoCommitConnection(); + try + { + PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_EXCHANGE); + try + { + stmt.setString(1, exchange.getNameShortString().toString()); + int results = stmt.executeUpdate(); + stmt.close(); + if(results == 0) + { + throw new AMQStoreException("Exchange " + exchange.getNameShortString() + " not found"); + } + } + finally + { + stmt.close(); + } + } + finally + { + conn.close(); + } + } + catch (SQLException e) + { + throw new AMQStoreException("Error deleting Exchange with name " + exchange.getNameShortString() + " from database: " + e.getMessage(), e); + } + } + + public void bindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) + throws AMQStoreException + { + if (_state != State.RECOVERING) + { + try + { + Connection conn = newAutoCommitConnection(); + + try + { + + PreparedStatement stmt = conn.prepareStatement(FIND_BINDING); + try + { + stmt.setString(1, exchange.getNameShortString().toString() ); + stmt.setString(2, queue.getNameShortString().toString()); + stmt.setString(3, routingKey == null ? null : routingKey.toString()); + + ResultSet rs = stmt.executeQuery(); + try + { + // If this binding is not already in the store then create it. + if (!rs.next()) + { + PreparedStatement insertStmt = conn.prepareStatement(INSERT_INTO_BINDINGS); + try + { + insertStmt.setString(1, exchange.getNameShortString().toString() ); + insertStmt.setString(2, queue.getNameShortString().toString()); + insertStmt.setString(3, routingKey == null ? null : routingKey.toString()); + if(args != null) + { + /* This would be the Java 6 way of setting a Blob + Blob blobArgs = conn.createBlob(); + blobArgs.setBytes(0, args.getDataAsBytes()); + stmt.setBlob(4, blobArgs); + */ + byte[] bytes = args.getDataAsBytes(); + ByteArrayInputStream bis = new ByteArrayInputStream(bytes); + insertStmt.setBinaryStream(4, bis, bytes.length); + } + else + { + insertStmt.setNull(4, Types.BLOB); + } + + insertStmt.executeUpdate(); + } + finally + { + insertStmt.close(); + } + } + } + finally + { + rs.close(); + } + } + finally + { + stmt.close(); + } + } + finally + { + conn.close(); + } + } + catch (SQLException e) + { + throw new AMQStoreException("Error writing binding for AMQQueue with name " + queue.getNameShortString() + " to exchange " + + exchange.getNameShortString() + " to database: " + e.getMessage(), e); + } + + } + + + } + + public void unbindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) + throws AMQStoreException + { + Connection conn = null; + + try + { + conn = newAutoCommitConnection(); + // exchange_name varchar(255) not null, queue_name varchar(255) not null, binding_key varchar(255), arguments blob + PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_BINDINGS); + stmt.setString(1, exchange.getNameShortString().toString() ); + stmt.setString(2, queue.getNameShortString().toString()); + stmt.setString(3, routingKey == null ? null : routingKey.toString()); + + int result = stmt.executeUpdate(); + stmt.close(); + + if(result != 1) + { + throw new AMQStoreException("Queue binding for queue with name " + queue.getNameShortString() + " to exchange " + + exchange.getNameShortString() + " not found"); + } + } + catch (SQLException e) + { + throw new AMQStoreException("Error removing binding for AMQQueue with name " + queue.getNameShortString() + " to exchange " + + exchange.getNameShortString() + " in database: " + e.getMessage(), e); + } + finally + { + if(conn != null) + { + try + { + conn.close(); + } + catch (SQLException e) + { + _logger.error(e); + } + } + + } + + + } + + public void createQueue(AMQQueue queue) throws AMQStoreException + { + createQueue(queue, null); + } + + public void createQueue(AMQQueue queue, FieldTable arguments) throws AMQStoreException + { + _logger.debug("public void createQueue(AMQQueue queue = " + queue + "): called"); + + if (_state != State.RECOVERING) + { + try + { + Connection conn = newAutoCommitConnection(); + + PreparedStatement stmt = conn.prepareStatement(FIND_QUEUE); + try + { + stmt.setString(1, queue.getNameShortString().toString()); + ResultSet rs = stmt.executeQuery(); + try + { + + // If we don't have any data in the result set then we can add this queue + if (!rs.next()) + { + PreparedStatement insertStmt = conn.prepareStatement(INSERT_INTO_QUEUE); + + try + { + String owner = queue.getOwner() == null ? null : queue.getOwner().toString(); + + insertStmt.setString(1, queue.getNameShortString().toString()); + insertStmt.setString(2, owner); + insertStmt.setBoolean(3,queue.isExclusive()); + + final byte[] underlying; + if(arguments != null) + { + underlying = arguments.getDataAsBytes(); + } + else + { + underlying = new byte[0]; + } + + ByteArrayInputStream bis = new ByteArrayInputStream(underlying); + insertStmt.setBinaryStream(4,bis,underlying.length); + + insertStmt.execute(); + } + finally + { + insertStmt.close(); + } + } + } + finally + { + rs.close(); + } + } + finally + { + stmt.close(); + } + conn.close(); + + } + catch (SQLException e) + { + throw new AMQStoreException("Error writing AMQQueue with name " + queue.getNameShortString() + " to database: " + e.getMessage(), e); + } + } + } + + /** + * Updates the specified queue in the persistent store, IF it is already present. If the queue + * is not present in the store, it will not be added. + * + * NOTE: Currently only updates the exclusivity. + * + * @param queue The queue to update the entry for. + * @throws AMQStoreException If the operation fails for any reason. + */ + public void updateQueue(final AMQQueue queue) throws AMQStoreException + { + if (_state != State.RECOVERING) + { + try + { + Connection conn = newAutoCommitConnection(); + + try + { + PreparedStatement stmt = conn.prepareStatement(FIND_QUEUE); + try + { + stmt.setString(1, queue.getNameShortString().toString()); + + ResultSet rs = stmt.executeQuery(); + try + { + if (rs.next()) + { + PreparedStatement stmt2 = conn.prepareStatement(UPDATE_QUEUE_EXCLUSIVITY); + try + { + stmt2.setBoolean(1,queue.isExclusive()); + stmt2.setString(2, queue.getNameShortString().toString()); + + stmt2.execute(); + } + finally + { + stmt2.close(); + } + } + } + finally + { + rs.close(); + } + } + finally + { + stmt.close(); + } + } + finally + { + conn.close(); + } + } + catch (SQLException e) + { + throw new AMQStoreException("Error updating AMQQueue with name " + queue.getNameShortString() + " to database: " + e.getMessage(), e); + } + } + + } + + /** + * Convenience method to create a new Connection configured for TRANSACTION_READ_COMMITED + * isolation and with auto-commit transactions enabled. + */ + private Connection newAutoCommitConnection() throws SQLException + { + final Connection connection = newConnection(); + try + { + connection.setAutoCommit(true); + } + catch (SQLException sqlEx) + { + + try + { + connection.close(); + } + finally + { + throw sqlEx; + } + } + + return connection; + } + + /** + * Convenience method to create a new Connection configured for TRANSACTION_READ_COMMITED + * isolation and with auto-commit transactions disabled. + */ + private Connection newConnection() throws SQLException + { + final Connection connection = DriverManager.getConnection(_connectionURL); + try + { + connection.setAutoCommit(false); + connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); + } + catch (SQLException sqlEx) + { + try + { + connection.close(); + } + finally + { + throw sqlEx; + } + } + return connection; + } + + public void removeQueue(final AMQQueue queue) throws AMQStoreException + { + AMQShortString name = queue.getNameShortString(); + _logger.debug("public void removeQueue(AMQShortString name = " + name + "): called"); + Connection conn = null; + + try + { + conn = newAutoCommitConnection(); + PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_QUEUE); + stmt.setString(1, name.toString()); + int results = stmt.executeUpdate(); + stmt.close(); + + if (results == 0) + { + throw new AMQStoreException("Queue " + name + " not found"); + } + } + catch (SQLException e) + { + throw new AMQStoreException("Error deleting AMQQueue with name " + name + " from database: " + e.getMessage(), e); + } + finally + { + if(conn != null) + { + try + { + conn.close(); + } + catch (SQLException e) + { + _logger.error(e); + } + } + + } + + + } + + public Transaction newTransaction() + { + return new DerbyTransaction(); + } + + public void enqueueMessage(ConnectionWrapper connWrapper, final TransactionLogResource queue, Long messageId) throws AMQStoreException + { + String name = queue.getResourceName(); + + Connection conn = connWrapper.getConnection(); + + + try + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Enqueuing message " + messageId + " on queue " + name + "[Connection" + conn + "]"); + } + + PreparedStatement stmt = conn.prepareStatement(INSERT_INTO_QUEUE_ENTRY); + try + { + stmt.setString(1,name); + stmt.setLong(2,messageId); + stmt.executeUpdate(); + } + finally + { + stmt.close(); + } + } + catch (SQLException e) + { + _logger.error("Failed to enqueue: " + e.getMessage(), e); + throw new AMQStoreException("Error writing enqueued message with id " + messageId + " for queue " + name + + " to database", e); + } + + } + + public void dequeueMessage(ConnectionWrapper connWrapper, final TransactionLogResource queue, Long messageId) throws AMQStoreException + { + String name = queue.getResourceName(); + + + Connection conn = connWrapper.getConnection(); + + + try + { + PreparedStatement stmt = conn.prepareStatement(DELETE_FROM_QUEUE_ENTRY); + try + { + stmt.setString(1,name); + stmt.setLong(2,messageId); + int results = stmt.executeUpdate(); + + + + if(results != 1) + { + throw new AMQStoreException("Unable to find message with id " + messageId + " on queue " + name); + } + + if (_logger.isDebugEnabled()) + { + _logger.debug("Dequeuing message " + messageId + " on queue " + name );//+ "[Connection" + conn + "]"); + } + } + finally + { + stmt.close(); + } + } + catch (SQLException e) + { + _logger.error("Failed to dequeue: " + e.getMessage(), e); + throw new AMQStoreException("Error deleting enqueued message with id " + messageId + " for queue " + name + + " from database", e); + } + + } + + private static final class ConnectionWrapper + { + private final Connection _connection; + + public ConnectionWrapper(Connection conn) + { + _connection = conn; + } + + public Connection getConnection() + { + return _connection; + } + } + + + public void commitTran(ConnectionWrapper connWrapper) throws AMQStoreException + { + + try + { + Connection conn = connWrapper.getConnection(); + conn.commit(); + + if (_logger.isDebugEnabled()) + { + _logger.debug("commit tran completed"); + } + + conn.close(); + } + catch (SQLException e) + { + throw new AMQStoreException("Error commit tx: " + e.getMessage(), e); + } + finally + { + + } + } + + public StoreFuture commitTranAsync(ConnectionWrapper connWrapper) throws AMQStoreException + { + commitTran(connWrapper); + return new StoreFuture() + { + public boolean isComplete() + { + return true; + } + + public void waitForCompletion() + { + + } + }; + + } + + public void abortTran(ConnectionWrapper connWrapper) throws AMQStoreException + { + if (connWrapper == null) + { + throw new AMQStoreException("Fatal internal error: transactional context is empty at abortTran"); + } + + if (_logger.isDebugEnabled()) + { + _logger.debug("abort tran called: " + connWrapper.getConnection()); + } + + try + { + Connection conn = connWrapper.getConnection(); + conn.rollback(); + conn.close(); + } + catch (SQLException e) + { + throw new AMQStoreException("Error aborting transaction: " + e.getMessage(), e); + } + + } + + public Long getNewMessageId() + { + return _messageId.incrementAndGet(); + } + + + private void storeMetaData(Connection conn, long messageId, StorableMessageMetaData metaData) + throws SQLException + { + if(_logger.isDebugEnabled()) + { + _logger.debug("Adding metadata for message " +messageId); + } + + PreparedStatement stmt = conn.prepareStatement(INSERT_INTO_META_DATA); + try + { + stmt.setLong(1,messageId); + + final int bodySize = 1 + metaData.getStorableSize(); + byte[] underlying = new byte[bodySize]; + underlying[0] = (byte) metaData.getType().ordinal(); + java.nio.ByteBuffer buf = java.nio.ByteBuffer.wrap(underlying); + buf.position(1); + buf = buf.slice(); + + metaData.writeToBuffer(0, buf); + ByteArrayInputStream bis = new ByteArrayInputStream(underlying); + try + { + stmt.setBinaryStream(2,bis,underlying.length); + int result = stmt.executeUpdate(); + + if(result == 0) + { + throw new RuntimeException("Unable to add meta data for message " +messageId); + } + } + finally + { + try + { + bis.close(); + } + catch (IOException e) + { + + throw new SQLException(e); + } + } + + } + finally + { + stmt.close(); + } + + } + + + + + private void recoverMessages(MessageStoreRecoveryHandler recoveryHandler) throws SQLException + { + Connection conn = newAutoCommitConnection(); + try + { + MessageStoreRecoveryHandler.StoredMessageRecoveryHandler messageHandler = recoveryHandler.begin(); + + Statement stmt = conn.createStatement(); + try + { + ResultSet rs = stmt.executeQuery(SELECT_ALL_FROM_META_DATA); + try + { + + long maxId = 0; + + while(rs.next()) + { + + long messageId = rs.getLong(1); + Blob dataAsBlob = rs.getBlob(2); + + if(messageId > maxId) + { + maxId = messageId; + } + + byte[] dataAsBytes = dataAsBlob.getBytes(1,(int) dataAsBlob.length()); + java.nio.ByteBuffer buf = java.nio.ByteBuffer.wrap(dataAsBytes); + buf.position(1); + buf = buf.slice(); + MessageMetaDataType type = MessageMetaDataType.values()[dataAsBytes[0]]; + StorableMessageMetaData metaData = type.getFactory().createMetaData(buf); + StoredDerbyMessage message = new StoredDerbyMessage(messageId, metaData, false); + messageHandler.message(message); + } + + _messageId.set(maxId); + + messageHandler.completeMessageRecovery(); + } + finally + { + rs.close(); + } + } + finally + { + stmt.close(); + } + } + finally + { + conn.close(); + } + } + + + + private void recoverQueueEntries(TransactionLogRecoveryHandler recoveryHandler) throws SQLException + { + Connection conn = newAutoCommitConnection(); + try + { + TransactionLogRecoveryHandler.QueueEntryRecoveryHandler queueEntryHandler = recoveryHandler.begin(this); + + Statement stmt = conn.createStatement(); + try + { + ResultSet rs = stmt.executeQuery(SELECT_FROM_QUEUE_ENTRY); + try + { + while(rs.next()) + { + + String queueName = rs.getString(1); + long messageId = rs.getLong(2); + queueEntryHandler.queueEntry(queueName,messageId); + } + } + finally + { + rs.close(); + } + } + finally + { + stmt.close(); + } + + queueEntryHandler.completeQueueEntryRecovery(); + } + finally + { + conn.close(); + } + } + + StorableMessageMetaData getMetaData(long messageId) throws SQLException + { + + Connection conn = newAutoCommitConnection(); + try + { + PreparedStatement stmt = conn.prepareStatement(SELECT_FROM_META_DATA); + try + { + stmt.setLong(1,messageId); + ResultSet rs = stmt.executeQuery(); + try + { + + if(rs.next()) + { + Blob dataAsBlob = rs.getBlob(1); + + byte[] dataAsBytes = dataAsBlob.getBytes(1,(int) dataAsBlob.length()); + java.nio.ByteBuffer buf = java.nio.ByteBuffer.wrap(dataAsBytes); + buf.position(1); + buf = buf.slice(); + MessageMetaDataType type = MessageMetaDataType.values()[dataAsBytes[0]]; + StorableMessageMetaData metaData = type.getFactory().createMetaData(buf); + + return metaData; + } + else + { + throw new RuntimeException("Meta data not found for message with id " + messageId); + } + } + finally + { + rs.close(); + } + } + finally + { + stmt.close(); + } + } + finally + { + conn.close(); + } + } + + + private void addContent(Connection conn, long messageId, int offset, ByteBuffer src) + { + if(_logger.isDebugEnabled()) + { + _logger.debug("Adding content chunk offset " + offset + " for message " +messageId); + } + + try + { + src = src.slice(); + + byte[] chunkData = new byte[src.limit()]; + src.duplicate().get(chunkData); + + PreparedStatement stmt = conn.prepareStatement(INSERT_INTO_MESSAGE_CONTENT); + stmt.setLong(1,messageId); + stmt.setInt(2, offset); + stmt.setInt(3, offset+chunkData.length); + + + /* this would be the Java 6 way of doing things + Blob dataAsBlob = conn.createBlob(); + dataAsBlob.setBytes(1L, chunkData); + stmt.setBlob(3, dataAsBlob); + */ + ByteArrayInputStream bis = new ByteArrayInputStream(chunkData); + stmt.setBinaryStream(4, bis, chunkData.length); + stmt.executeUpdate(); + stmt.close(); + } + catch (SQLException e) + { + if(conn != null) + { + try + { + conn.close(); + } + catch (SQLException e1) + { + + } + } + + throw new RuntimeException("Error adding content chunk offset " + offset + " for message " + messageId + ": " + e.getMessage(), e); + } + + } + + + public int getContent(long messageId, int offset, ByteBuffer dst) + { + Connection conn = null; + + + try + { + conn = newAutoCommitConnection(); + + PreparedStatement stmt = conn.prepareStatement(SELECT_FROM_MESSAGE_CONTENT); + stmt.setLong(1,messageId); + stmt.setInt(2, offset); + stmt.setInt(3, offset+dst.remaining()); + ResultSet rs = stmt.executeQuery(); + + int written = 0; + + while(rs.next()) + { + int offsetInMessage = rs.getInt(1); + Blob dataAsBlob = rs.getBlob(2); + + final int size = (int) dataAsBlob.length(); + byte[] dataAsBytes = dataAsBlob.getBytes(1, size); + + int posInArray = offset + written - offsetInMessage; + int count = size - posInArray; + if(count > dst.remaining()) + { + count = dst.remaining(); + } + dst.put(dataAsBytes,posInArray,count); + written+=count; + + if(dst.remaining() == 0) + { + break; + } + } + + stmt.close(); + conn.close(); + return written; + + } + catch (SQLException e) + { + if(conn != null) + { + try + { + conn.close(); + } + catch (SQLException e1) + { + + } + } + + throw new RuntimeException("Error retrieving content from offset " + offset + " for message " + messageId + ": " + e.getMessage(), e); + } + + + + } + + public boolean isPersistent() + { + return true; + } + + + private synchronized void stateTransition(State requiredState, State newState) throws AMQStoreException + { + if (_state != requiredState) + { + throw new AMQStoreException("Cannot transition to the state: " + newState + "; need to be in state: " + requiredState + + "; currently in state: " + _state); + } + + _state = newState; + } + + + private class DerbyTransaction implements Transaction + { + private final ConnectionWrapper _connWrapper; + + + private DerbyTransaction() + { + try + { + _connWrapper = new ConnectionWrapper(newConnection()); + } + catch (SQLException e) + { + throw new RuntimeException(e); + } + } + + public void enqueueMessage(TransactionLogResource queue, Long messageId) throws AMQStoreException + { + DerbyMessageStore.this.enqueueMessage(_connWrapper, queue, messageId); + } + + public void dequeueMessage(TransactionLogResource queue, Long messageId) throws AMQStoreException + { + DerbyMessageStore.this.dequeueMessage(_connWrapper, queue, messageId); + + } + + public void commitTran() throws AMQStoreException + { + DerbyMessageStore.this.commitTran(_connWrapper); + } + + public StoreFuture commitTranAsync() throws AMQStoreException + { + return DerbyMessageStore.this.commitTranAsync(_connWrapper); + } + + public void abortTran() throws AMQStoreException + { + DerbyMessageStore.this.abortTran(_connWrapper); + } + } + + private class StoredDerbyMessage implements StoredMessage + { + + private final long _messageId; + private volatile SoftReference<StorableMessageMetaData> _metaDataRef; + private Connection _conn; + + StoredDerbyMessage(long messageId, StorableMessageMetaData metaData) + { + this(messageId, metaData, true); + } + + + StoredDerbyMessage(long messageId, + StorableMessageMetaData metaData, boolean persist) + { + try + { + _messageId = messageId; + + _metaDataRef = new SoftReference<StorableMessageMetaData>(metaData); + if(persist) + { + _conn = newConnection(); + storeMetaData(_conn, messageId, metaData); + } + } + catch (SQLException e) + { + throw new RuntimeException(e); + } + + } + + public StorableMessageMetaData getMetaData() + { + StorableMessageMetaData metaData = _metaDataRef.get(); + if(metaData == null) + { + try + { + metaData = DerbyMessageStore.this.getMetaData(_messageId); + } + catch (SQLException e) + { + throw new RuntimeException(e); + } + _metaDataRef = new SoftReference<StorableMessageMetaData>(metaData); + } + + return metaData; + } + + public long getMessageNumber() + { + return _messageId; + } + + public void addContent(int offsetInMessage, java.nio.ByteBuffer src) + { + DerbyMessageStore.this.addContent(_conn, _messageId, offsetInMessage, src); + } + + public int getContent(int offsetInMessage, java.nio.ByteBuffer dst) + { + return DerbyMessageStore.this.getContent(_messageId, offsetInMessage, dst); + } + + public StoreFuture flushToStore() + { + try + { + if(_conn != null) + { + if(_logger.isDebugEnabled()) + { + _logger.debug("Flushing message " + _messageId + " to store"); + } + + _conn.commit(); + _conn.close(); + } + } + catch (SQLException e) + { + if(_logger.isDebugEnabled()) + { + _logger.debug("Error when trying to flush message " + _messageId + " to store: " + e); + } + throw new RuntimeException(e); + } + finally + { + _conn = null; + } + return IMMEDIATE_FUTURE; + } + + public void remove() + { + flushToStore(); + DerbyMessageStore.this.removeMessage(_messageId); + } + } + + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/DurableConfigurationStore.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/DurableConfigurationStore.java new file mode 100755 index 0000000000..5fb23653cb --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/DurableConfigurationStore.java @@ -0,0 +1,131 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store; + +import org.apache.commons.configuration.Configuration; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.queue.AMQQueue; + +public interface DurableConfigurationStore +{ + + public static interface Source + { + DurableConfigurationStore getDurableConfigurationStore(); + } + + /** + * Called after instantiation in order to configure the message store. A particular implementation can define + * whatever parameters it wants. + * + * @param name The name to be used by this storem + * @param recoveryHandler Handler to be called as the store recovers on start up + * @param config The apache commons configuration object. + * + * @throws Exception If any error occurs that means the store is unable to configure itself. + */ + void configureConfigStore(String name, + ConfigurationRecoveryHandler recoveryHandler, + Configuration config, + LogSubject logSubject) throws Exception; + /** + * Makes the specified exchange persistent. + * + * @param exchange The exchange to persist. + * + * @throws AMQStoreException If the operation fails for any reason. + */ + void createExchange(Exchange exchange) throws AMQStoreException; + + /** + * Removes the specified persistent exchange. + * + * @param exchange The exchange to remove. + * + * @throws AMQStoreException If the operation fails for any reason. + */ + void removeExchange(Exchange exchange) throws AMQStoreException; + + /** + * Binds the specified queue to an exchange with a routing key. + * + * @param exchange The exchange to bind to. + * @param routingKey The routing key to bind by. + * @param queue The queue to bind. + * @param args Additional parameters. + * + * @throws AMQStoreException if the operation fails for any reason. + */ + void bindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQStoreException; + + /** + * Unbinds the specified from an exchange under a particular routing key. + * + * @param exchange The exchange to unbind from. + * @param routingKey The routing key to unbind. + * @param queue The queue to unbind. + * @param args Additonal parameters. + * + * @throws AMQStoreException If the operation fails for any reason. + */ + void unbindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQStoreException; + + /** + * Makes the specified queue persistent. + * + * @param queue The queue to store. + * + * @throws AMQStoreException If the operation fails for any reason. + */ + void createQueue(AMQQueue queue) throws AMQStoreException; + + /** + * Makes the specified queue persistent. + * + * @param queue The queue to store. + * @param arguments The additional arguments to the binding + * + * @throws AMQStoreException If the operation fails for any reason. + */ + void createQueue(AMQQueue queue, FieldTable arguments) throws AMQStoreException; + + /** + * Removes the specified queue from the persistent store. + * + * @param queue The queue to remove. + * + * @throws AMQStoreException If the operation fails for any reason. + */ + void removeQueue(AMQQueue queue) throws AMQStoreException; + + /** + * Updates the specified queue in the persistent store, IF it is already present. If the queue + * is not present in the store, it will not be added. + * + * @param queue The queue to update the entry for. + * @throws AMQStoreException If the operation fails for any reason. + */ + void updateQueue(AMQQueue queue) throws AMQStoreException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java new file mode 100644 index 0000000000..d008d42fa0 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MemoryMessageStore.java @@ -0,0 +1,196 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.commons.configuration.Configuration; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.ConfigStoreMessages; +import org.apache.qpid.server.logging.messages.MessageStoreMessages; +import org.apache.qpid.server.queue.AMQQueue; + +/** A simple message store that stores the messages in a threadsafe structure in memory. */ +public class MemoryMessageStore implements MessageStore +{ + private static final Logger _log = Logger.getLogger(MemoryMessageStore.class); + + private static final int DEFAULT_HASHTABLE_CAPACITY = 50000; + + private static final String HASHTABLE_CAPACITY_CONFIG = "hashtable-capacity"; + + + private final AtomicLong _messageId = new AtomicLong(1); + private AtomicBoolean _closed = new AtomicBoolean(false); + private LogSubject _logSubject; + + private static final Transaction IN_MEMORY_TRANSACTION = new Transaction() + { + public void enqueueMessage(TransactionLogResource queue, Long messageId) throws AMQStoreException + { + } + + public void dequeueMessage(TransactionLogResource queue, Long messageId) throws AMQStoreException + { + } + + public void commitTran() throws AMQStoreException + { + } + + public StoreFuture commitTranAsync() throws AMQStoreException + { + return IMMEDIATE_FUTURE; + } + + public void abortTran() throws AMQStoreException + { + } + + }; + + public void configureConfigStore(String name, ConfigurationRecoveryHandler handler, Configuration configuration, LogSubject logSubject) throws Exception + { + _logSubject = logSubject; + CurrentActor.get().message(_logSubject, ConfigStoreMessages.CREATED(this.getClass().getName())); + + + } + + public void configureMessageStore(String name, + MessageStoreRecoveryHandler recoveryHandler, + Configuration config, + LogSubject logSubject) throws Exception + { + if(_logSubject == null) + { + _logSubject = logSubject; + } + int hashtableCapacity = config.getInt(name + "." + HASHTABLE_CAPACITY_CONFIG, DEFAULT_HASHTABLE_CAPACITY); + _log.info("Using capacity " + hashtableCapacity + " for hash tables"); + CurrentActor.get().message(_logSubject, MessageStoreMessages.CREATED(this.getClass().getName())); + } + + public void close() throws Exception + { + _closed.getAndSet(true); + CurrentActor.get().message(_logSubject,MessageStoreMessages.CLOSED()); + + } + + public StoredMessage addMessage(StorableMessageMetaData metaData) + { + final long id = _messageId.getAndIncrement(); + StoredMemoryMessage message = new StoredMemoryMessage(id, metaData); + + return message; + } + + + public void createExchange(Exchange exchange) throws AMQStoreException + { + + } + + public void removeExchange(Exchange exchange) throws AMQStoreException + { + + } + + public void bindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQStoreException + { + + } + + public void unbindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQStoreException + { + + } + + + public void createQueue(AMQQueue queue) throws AMQStoreException + { + // Not requred to do anything + } + + public void createQueue(AMQQueue queue, FieldTable arguments) throws AMQStoreException + { + // Not required to do anything + } + + public void removeQueue(final AMQQueue queue) throws AMQStoreException + { + // Not required to do anything + } + + public void updateQueue(final AMQQueue queue) throws AMQStoreException + { + // Not required to do anything + } + + public void configureTransactionLog(String name, + TransactionLogRecoveryHandler recoveryHandler, + Configuration storeConfiguration, + LogSubject logSubject) throws Exception + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public Transaction newTransaction() + { + return IN_MEMORY_TRANSACTION; + } + + + public List<AMQQueue> createQueues() throws AMQException + { + return null; + } + + public Long getNewMessageId() + { + return _messageId.getAndIncrement(); + } + + public boolean isPersistent() + { + return false; + } + + private void checkNotClosed() throws MessageStoreClosedException + { + if (_closed.get()) + { + throw new MessageStoreClosedException(); + } + } + + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageMetaDataType.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageMetaDataType.java new file mode 100755 index 0000000000..428bb1e41b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageMetaDataType.java @@ -0,0 +1,41 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store; + +import org.apache.qpid.server.message.MessageMetaData; +import org.apache.qpid.server.message.MessageMetaData_0_10; + +import java.nio.ByteBuffer; + +public enum MessageMetaDataType +{ + META_DATA_0_8 { public Factory<MessageMetaData> getFactory() { return MessageMetaData.FACTORY; } }, + META_DATA_0_10 { public Factory<MessageMetaData_0_10> getFactory() { return MessageMetaData_0_10.FACTORY; } }; + + + public static interface Factory<M extends StorableMessageMetaData> + { + M createMetaData(ByteBuffer buf); + } + + abstract public Factory<? extends StorableMessageMetaData> getFactory(); + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStore.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStore.java new file mode 100644 index 0000000000..e2fca2f9c7 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStore.java @@ -0,0 +1,80 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store; + +import org.apache.qpid.server.logging.LogSubject; +import org.apache.commons.configuration.Configuration; + +/** + * MessageStore defines the interface to a storage area, which can be used to preserve the state of messages. + * + */ +public interface MessageStore extends DurableConfigurationStore, TransactionLog +{ + StoreFuture IMMEDIATE_FUTURE = new StoreFuture() + { + public boolean isComplete() + { + return true; + } + + public void waitForCompletion() + { + + } + }; + + + /** + * Called after instantiation in order to configure the message store. A particular implementation can define + * whatever parameters it wants. + * + * @param name The name to be used by this storem + * @param recoveryHandler Handler to be called as the store recovers on start up + * @param config The apache commons configuration object. + * + * @throws Exception If any error occurs that means the store is unable to configure itself. + */ + void configureMessageStore(String name, + MessageStoreRecoveryHandler recoveryHandler, + Configuration config, + LogSubject logSubject) throws Exception; + + /** + * Called to close and cleanup any resources used by the message store. + * + * @throws Exception If the close fails. + */ + void close() throws Exception; + + + public <T extends StorableMessageMetaData> StoredMessage<T> addMessage(T metaData); + + + /** + * Is this store capable of persisting the data + * + * @return true if this store is capable of persisting data + */ + boolean isPersistent(); + + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStoreClosedException.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStoreClosedException.java new file mode 100644 index 0000000000..3d1538c7eb --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStoreClosedException.java @@ -0,0 +1,36 @@ +package org.apache.qpid.server.store; + +import org.apache.qpid.AMQException;/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ + +/** + * NOTE: this class currently extends AMQException but + * we should be using AMQExceptions internally in the code base for Protocol errors hence + * the message store interface should throw a different super class which this should be + * moved to reflect + */ +public class MessageStoreClosedException extends AMQException +{ + public MessageStoreClosedException() + { + super("Message store closed"); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStoreRecoveryHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStoreRecoveryHandler.java new file mode 100755 index 0000000000..ba65b8e1ec --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/MessageStoreRecoveryHandler.java @@ -0,0 +1,33 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.store; + +public interface MessageStoreRecoveryHandler +{ + StoredMessageRecoveryHandler begin(); + + public static interface StoredMessageRecoveryHandler + { + void message(StoredMessage message); + + void completeMessageRecovery(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/StorableMessageMetaData.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/StorableMessageMetaData.java new file mode 100755 index 0000000000..12d2a6a6c7 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/StorableMessageMetaData.java @@ -0,0 +1,36 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store; + +import java.nio.ByteBuffer; + +public interface StorableMessageMetaData +{ + MessageMetaDataType getType(); + + int getStorableSize(); + + int writeToBuffer(int offsetInMetaData, ByteBuffer dest); + + int getContentSize(); + + boolean isPersistent(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/StoreContext.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/StoreContext.java new file mode 100644 index 0000000000..88cc68bc71 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/StoreContext.java @@ -0,0 +1,73 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store; + +import org.apache.log4j.Logger; + +/** + * A context that the store can use to associate with a transactional context. For example, it could store + * some kind of txn id. + * + * @author Apache Software Foundation + */ +public class StoreContext +{ + private static final Logger _logger = Logger.getLogger(StoreContext.class); + + private String _name; + private Object _payload; + + + public StoreContext() + { + _name = "StoreContext"; + } + + public StoreContext(String name) + { + _name = name; + } + + public Object getPayload() + { + return _payload; + } + + public void setPayload(Object payload) + { + if(_logger.isDebugEnabled()) + { + _logger.debug("public void setPayload(Object payload = " + payload + "): called"); + } + _payload = payload; + } + + /** + * Prints out the transactional context as a string, mainly for debugging purposes. + * + * @return The transactional context as a string. + */ + public String toString() + { + return "<_name = " + _name + ", _payload = " + _payload + ">"; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/StoredMemoryMessage.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/StoredMemoryMessage.java new file mode 100755 index 0000000000..1f5b027b80 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/StoredMemoryMessage.java @@ -0,0 +1,80 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store; + +import java.nio.ByteBuffer; + +public class StoredMemoryMessage implements StoredMessage +{ + private final long _messageNumber; + private final ByteBuffer _content; + private final StorableMessageMetaData _metaData; + + public StoredMemoryMessage(long messageNumber, StorableMessageMetaData metaData) + { + _messageNumber = messageNumber; + _metaData = metaData; + _content = ByteBuffer.allocate(metaData.getContentSize()); + + } + + public long getMessageNumber() + { + return _messageNumber; + } + + public void addContent(int offsetInMessage, ByteBuffer src) + { + src = src.duplicate(); + ByteBuffer dst = _content.duplicate(); + dst.position(offsetInMessage); + dst.put(src); + } + + public int getContent(int offset, ByteBuffer dst) + { + ByteBuffer src = _content.duplicate(); + src.position(offset); + src = src.slice(); + if(dst.remaining() < src.limit()) + { + src.limit(dst.remaining()); + } + dst.put(src); + return src.limit(); + } + + public TransactionLog.StoreFuture flushToStore() + { + return MessageStore.IMMEDIATE_FUTURE; + } + + + public StorableMessageMetaData getMetaData() + { + return _metaData; + } + + public void remove() + { + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/StoredMessage.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/StoredMessage.java new file mode 100755 index 0000000000..0bc45c6718 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/StoredMessage.java @@ -0,0 +1,38 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.store; + +import java.nio.ByteBuffer; + +public interface StoredMessage<M extends StorableMessageMetaData> +{ + M getMetaData(); + + public long getMessageNumber(); + + void addContent(int offsetInMessage, ByteBuffer src); + + int getContent(int offsetInMessage, ByteBuffer dst); + + TransactionLog.StoreFuture flushToStore(); + + void remove(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/TransactionLog.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/TransactionLog.java new file mode 100755 index 0000000000..d196a91930 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/TransactionLog.java @@ -0,0 +1,91 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.store; + +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.AMQStoreException; +import org.apache.commons.configuration.Configuration; + +public interface TransactionLog +{ + + public static interface Transaction + { + /** + * Places a message onto a specified queue, in a given transactional context. + * + * @param queue The queue to place the message on. + * @param messageId The message to enqueue. + * @throws AMQStoreException If the operation fails for any reason. + */ + void enqueueMessage(TransactionLogResource queue, Long messageId) throws AMQStoreException; + + /** + * Extracts a message from a specified queue, in a given transactional context. + * + * @param queue The queue to place the message on. + * @param messageId The message to dequeue. + * @throws AMQStoreException If the operation fails for any reason, or if the specified message does not exist. + */ + void dequeueMessage(TransactionLogResource queue, Long messageId) throws AMQStoreException; + + + /** + * Commits all operations performed within a given transactional context. + * + * @throws AMQStoreException If the operation fails for any reason. + */ + void commitTran() throws AMQStoreException; + + /** + * Commits all operations performed within a given transactional context. + * + * @throws AMQStoreException If the operation fails for any reason. + */ + StoreFuture commitTranAsync() throws AMQStoreException; + + /** + * Abandons all operations performed within a given transactional context. + * + * @throws AMQStoreException If the operation fails for any reason. + */ + void abortTran() throws AMQStoreException; + + + + } + + public void configureTransactionLog(String name, + TransactionLogRecoveryHandler recoveryHandler, + Configuration storeConfiguration, + LogSubject logSubject) throws Exception; + + Transaction newTransaction(); + + + + public static interface StoreFuture + { + boolean isComplete(); + + void waitForCompletion(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/TransactionLogRecoveryHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/TransactionLogRecoveryHandler.java new file mode 100755 index 0000000000..7781c52df3 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/TransactionLogRecoveryHandler.java @@ -0,0 +1,33 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.store; + +public interface TransactionLogRecoveryHandler +{ + QueueEntryRecoveryHandler begin(TransactionLog log); + + public static interface QueueEntryRecoveryHandler + { + void queueEntry(String queuename, long messageId); + + void completeQueueEntryRecovery(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/store/TransactionLogResource.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/TransactionLogResource.java new file mode 100755 index 0000000000..0d81dd151d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/store/TransactionLogResource.java @@ -0,0 +1,26 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.store; + +public interface TransactionLogResource +{ + public String getResourceName(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/ClientDeliveryMethod.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/ClientDeliveryMethod.java new file mode 100644 index 0000000000..fbc8b3af7d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/ClientDeliveryMethod.java @@ -0,0 +1,29 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.subscription; + +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.AMQException; + +public interface ClientDeliveryMethod +{ + void deliverToClient(final Subscription sub, final QueueEntry entry, final long deliveryTag) throws AMQException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/ExplicitAcceptDispositionChangeListener.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/ExplicitAcceptDispositionChangeListener.java new file mode 100755 index 0000000000..b49b12fb79 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/ExplicitAcceptDispositionChangeListener.java @@ -0,0 +1,93 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.subscription; + +import org.apache.qpid.server.transport.ServerSession; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.log4j.Logger; + + +class ExplicitAcceptDispositionChangeListener implements ServerSession.MessageDispositionChangeListener +{ + private static final Logger _logger = Logger.getLogger(ExplicitAcceptDispositionChangeListener.class); + + + private final QueueEntry _entry; + private final Subscription_0_10 _sub; + + public ExplicitAcceptDispositionChangeListener(QueueEntry entry, Subscription_0_10 subscription_0_10) + { + _entry = entry; + _sub = subscription_0_10; + } + + public void onAccept() + { + final Subscription_0_10 subscription = getSubscription(); + if(subscription != null && _entry.isAcquiredBy(_sub)) + { + subscription.getSession().acknowledge(subscription, _entry); + } + else + { + _logger.warn("MessageAccept received for message which has not been acquired (likely client error)"); + } + + } + + public void onRelease() + { + final Subscription_0_10 subscription = getSubscription(); + if(subscription != null && _entry.isAcquiredBy(_sub)) + { + subscription.release(_entry); + } + else + { + _logger.warn("MessageRelease received for message which has not been acquired (likely client error)"); + } + } + + public void onReject() + { + final Subscription_0_10 subscription = getSubscription(); + if(subscription != null && _entry.isAcquiredBy(_sub)) + { + subscription.reject(_entry); + } + else + { + _logger.warn("MessageReject received for message which has not been acquired (likely client error)"); + } + + } + + public boolean acquire() + { + return _entry.acquire(getSubscription()); + } + + + private Subscription_0_10 getSubscription() + { + return _sub; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/ImplicitAcceptDispositionChangeListener.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/ImplicitAcceptDispositionChangeListener.java new file mode 100755 index 0000000000..b5bb2014b5 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/ImplicitAcceptDispositionChangeListener.java @@ -0,0 +1,86 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.subscription; + +import org.apache.qpid.server.transport.ServerSession; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.log4j.Logger; + +class ImplicitAcceptDispositionChangeListener implements ServerSession.MessageDispositionChangeListener +{ + private static final Logger _logger = Logger.getLogger(ImplicitAcceptDispositionChangeListener.class); + + + private final QueueEntry _entry; + private Subscription_0_10 _sub; + + public ImplicitAcceptDispositionChangeListener(QueueEntry entry, Subscription_0_10 subscription_0_10) + { + _entry = entry; + _sub = subscription_0_10; + } + + public void onAccept() + { + _logger.warn("MessageAccept received for message which is using NONE as the accept mode (likely client error)"); + } + + public void onRelease() + { + if(_entry.isAcquiredBy(_sub)) + { + getSubscription().release(_entry); + } + else + { + _logger.warn("MessageRelease received for message which has not been acquired (likely client error)"); + } + } + + public void onReject() + { + if(_entry.isAcquiredBy(_sub)) + { + getSubscription().reject(_entry); + } + else + { + _logger.warn("MessageReject received for message which has not been acquired (likely client error)"); + } + + } + + public boolean acquire() + { + boolean acquired = _entry.acquire(getSubscription()); + //TODO - why acknowledge here??? seems bizarre... + // getSubscription().getSession().acknowledge(getSubscription(), _entry); + return acquired; + + } + + public Subscription_0_10 getSubscription() + { + return _sub; + } + + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/MessageAcceptCompletionListener.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/MessageAcceptCompletionListener.java new file mode 100755 index 0000000000..8a2a370236 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/MessageAcceptCompletionListener.java @@ -0,0 +1,57 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.subscription; + +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.transport.ServerSession; +import org.apache.qpid.transport.Method; + +public class MessageAcceptCompletionListener implements Method.CompletionListener +{ + private final Subscription_0_10 _sub; + private final QueueEntry _entry; + private final ServerSession _session; + private boolean _restoreCredit; + + public MessageAcceptCompletionListener(Subscription_0_10 sub, ServerSession session, QueueEntry entry, boolean restoreCredit) + { + super(); + _sub = sub; + _entry = entry; + _session = session; + _restoreCredit = restoreCredit; + } + + public void onComplete(Method method) + { + if(_restoreCredit) + { + _sub.restoreCredit(_entry); + } + if(_entry.isAcquiredBy(_sub)) + { + _session.acknowledge(_sub, _entry); + } + + _session.removeDispositionListener(method); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/RecordDeliveryMethod.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/RecordDeliveryMethod.java new file mode 100644 index 0000000000..e2ed4104de --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/RecordDeliveryMethod.java @@ -0,0 +1,28 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.subscription; + +import org.apache.qpid.server.queue.QueueEntry; + +public interface RecordDeliveryMethod +{ + void recordMessageDelivery(final Subscription sub, final QueueEntry entry, final long deliveryTag); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/Subscription.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/Subscription.java new file mode 100644 index 0000000000..0a3576ff42 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/Subscription.java @@ -0,0 +1,109 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.subscription; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueEntry; + +public interface Subscription +{ + LogActor getLogActor(); + + boolean isTransient(); + + public static enum State + { + ACTIVE, + SUSPENDED, + CLOSED + } + + public static interface StateListener + { + public void stateChange(Subscription sub, State oldState, State newState); + } + + AMQQueue getQueue(); + + QueueEntry.SubscriptionAcquiredState getOwningState(); + QueueEntry.SubscriptionAssignedState getAssignedState(); + + + void setQueue(AMQQueue queue, boolean exclusive); + + void setNoLocal(boolean noLocal); + + AMQShortString getConsumerTag(); + + long getSubscriptionID(); + + boolean isSuspended(); + + boolean hasInterest(QueueEntry msg); + + boolean isAutoClose(); + + boolean isClosed(); + + boolean acquires(); + + boolean seesRequeues(); + + void close(); + + void send(QueueEntry msg) throws AMQException; + + void queueDeleted(AMQQueue queue); + + + boolean wouldSuspend(QueueEntry msg); + + void getSendLock(); + + void releaseSendLock(); + + void onDequeue(final QueueEntry queueEntry); + + void restoreCredit(final QueueEntry queueEntry); + + void setStateListener(final StateListener listener); + + public State getState(); + + AMQQueue.Context getQueueContext(); + + void setQueueContext(AMQQueue.Context queueContext); + + + boolean isActive(); + + void confirmAutoClose(); + + public void set(String key, Object value); + + public Object get(String key); + + boolean isSessionTransactional(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionFactory.java new file mode 100644 index 0000000000..ce0362d73f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionFactory.java @@ -0,0 +1,59 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.subscription; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.flow.FlowCreditManager; +import org.apache.qpid.server.subscription.Subscription; +import org.apache.qpid.server.AMQChannel; + +/** + * Allows the customisation of the creation of a subscription. This is typically done within an AMQQueue. This factory + * primarily assists testing although in future more sophisticated subscribers may need a different subscription + * implementation. + * + * @see org.apache.qpid.server.queue.AMQQueue + */ +public interface SubscriptionFactory +{ + Subscription createSubscription(int channel, + AMQProtocolSession protocolSession, + AMQShortString consumerTag, + boolean acks, + FieldTable filters, + boolean noLocal, FlowCreditManager creditManager) throws AMQException; + + + Subscription createSubscription(AMQChannel channel, + AMQProtocolSession protocolSession, + AMQShortString consumerTag, + boolean acks, + FieldTable filters, + boolean noLocal, + FlowCreditManager creditManager, + ClientDeliveryMethod clientMethod, + RecordDeliveryMethod recordMethod + ) + throws AMQException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionFactoryImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionFactoryImpl.java new file mode 100644 index 0000000000..1bba2529c6 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionFactoryImpl.java @@ -0,0 +1,95 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.subscription; + +import org.apache.qpid.AMQException; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.flow.FlowCreditManager; +import org.apache.qpid.server.protocol.AMQProtocolSession; + +public class SubscriptionFactoryImpl implements SubscriptionFactory +{ + public Subscription createSubscription(int channelId, AMQProtocolSession protocolSession, + AMQShortString consumerTag, boolean acks, FieldTable filters, + boolean noLocal, FlowCreditManager creditManager) throws AMQException + { + AMQChannel channel = protocolSession.getChannel(channelId); + if (channel == null) + { + throw new AMQException(AMQConstant.NOT_FOUND, "channel :" + channelId + " not found in protocol session"); + } + ClientDeliveryMethod clientMethod = channel.getClientDeliveryMethod(); + RecordDeliveryMethod recordMethod = channel.getRecordDeliveryMethod(); + + + return createSubscription(channel, protocolSession, consumerTag, acks, filters, + noLocal, + creditManager, + clientMethod, + recordMethod + ); + } + + public Subscription createSubscription(final AMQChannel channel, + final AMQProtocolSession protocolSession, + final AMQShortString consumerTag, + final boolean acks, + final FieldTable filters, + final boolean noLocal, + final FlowCreditManager creditManager, + final ClientDeliveryMethod clientMethod, + final RecordDeliveryMethod recordMethod + ) + throws AMQException + { + boolean isBrowser; + + if (filters != null) + { + Boolean isBrowserObj = (Boolean) filters.get(AMQPFilterTypes.NO_CONSUME.getValue()); + isBrowser = (isBrowserObj != null) && isBrowserObj.booleanValue(); + } + else + { + isBrowser = false; + } + + if(isBrowser) + { + return new SubscriptionImpl.BrowserSubscription(channel, protocolSession, consumerTag, filters, noLocal, creditManager, clientMethod, recordMethod); + } + else if(acks) + { + return new SubscriptionImpl.AckSubscription(channel, protocolSession, consumerTag, filters, noLocal, creditManager, clientMethod, recordMethod); + } + else + { + return new SubscriptionImpl.NoAckSubscription(channel, protocolSession, consumerTag, filters, noLocal, creditManager, clientMethod, recordMethod); + } + } + + + public static final SubscriptionFactoryImpl INSTANCE = new SubscriptionFactoryImpl(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionImpl.java new file mode 100644 index 0000000000..d8f44c9f7f --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionImpl.java @@ -0,0 +1,785 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.subscription; + +import org.apache.log4j.Logger; + +import org.apache.qpid.AMQException; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.common.ClientProperties; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.AMQChannel; +import org.apache.qpid.server.configuration.ConfigStore; +import org.apache.qpid.server.configuration.ConfiguredObject; +import org.apache.qpid.server.configuration.SessionConfig; +import org.apache.qpid.server.configuration.SubscriptionConfig; +import org.apache.qpid.server.configuration.SubscriptionConfigType; +import org.apache.qpid.server.filter.FilterManager; +import org.apache.qpid.server.filter.FilterManagerFactory; +import org.apache.qpid.server.flow.FlowCreditManager; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.SubscriptionActor; +import org.apache.qpid.server.logging.messages.SubscriptionMessages; +import org.apache.qpid.server.logging.subjects.SubscriptionLogSubject; +import org.apache.qpid.server.message.AMQMessage; +import org.apache.qpid.server.output.ProtocolOutputConverter; +import org.apache.qpid.server.protocol.AMQProtocolSession; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueEntry; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Encapsulation of a supscription to a queue. <p/> Ties together the protocol session of a subscriber, the consumer tag + * that was given out by the broker and the channel id. <p/> + */ +public abstract class SubscriptionImpl implements Subscription, FlowCreditManager.FlowCreditManagerListener, + SubscriptionConfig +{ + + private StateListener _stateListener = new StateListener() + { + + public void stateChange(Subscription sub, State oldState, State newState) + { + + } + }; + + + private final AtomicReference<State> _state = new AtomicReference<State>(State.ACTIVE); + private AMQQueue.Context _queueContext; + + private final ClientDeliveryMethod _deliveryMethod; + private final RecordDeliveryMethod _recordMethod; + + private final QueueEntry.SubscriptionAcquiredState _owningState = new QueueEntry.SubscriptionAcquiredState(this); + private final QueueEntry.SubscriptionAssignedState _assignedState = new QueueEntry.SubscriptionAssignedState(this); + + private final Map<String, Object> _properties = new ConcurrentHashMap<String, Object>(); + + private final Lock _stateChangeLock; + + private static final AtomicLong idGenerator = new AtomicLong(0); + // Create a simple ID that increments for ever new Subscription + private final long _subscriptionID = idGenerator.getAndIncrement(); + private LogSubject _logSubject; + private LogActor _logActor; + private UUID _id; + private final AtomicLong _deliveredCount = new AtomicLong(0); + private long _createTime = System.currentTimeMillis(); + + + static final class BrowserSubscription extends SubscriptionImpl + { + public BrowserSubscription(AMQChannel channel, AMQProtocolSession protocolSession, + AMQShortString consumerTag, FieldTable filters, + boolean noLocal, FlowCreditManager creditManager, + ClientDeliveryMethod deliveryMethod, + RecordDeliveryMethod recordMethod) + throws AMQException + { + super(channel, protocolSession, consumerTag, filters, noLocal, creditManager, deliveryMethod, recordMethod); + } + + + public boolean isBrowser() + { + return true; + } + + /** + * This method can be called by each of the publisher threads. As a result all changes to the channel object must be + * thread safe. + * + * @param msg The message to send + * @throws AMQException + */ + @Override + public void send(QueueEntry msg) throws AMQException + { + // We don't decrement the reference here as we don't want to consume the message + // but we do want to send it to the client. + + synchronized (getChannel()) + { + long deliveryTag = getChannel().getNextDeliveryTag(); + sendToClient(msg, deliveryTag); + } + + } + + @Override + public boolean wouldSuspend(QueueEntry msg) + { + return false; + } + + } + + public static class NoAckSubscription extends SubscriptionImpl + { + public NoAckSubscription(AMQChannel channel, AMQProtocolSession protocolSession, + AMQShortString consumerTag, FieldTable filters, + boolean noLocal, FlowCreditManager creditManager, + ClientDeliveryMethod deliveryMethod, + RecordDeliveryMethod recordMethod) + throws AMQException + { + super(channel, protocolSession, consumerTag, filters, noLocal, creditManager, deliveryMethod, recordMethod); + } + + + public boolean isBrowser() + { + return false; + } + + @Override + public boolean isExplicitAcknowledge() + { + return false; + } + + /** + * This method can be called by each of the publisher threads. As a result all changes to the channel object must be + * thread safe. + * + * @param entry The message to send + * @throws AMQException + */ + @Override + public void send(QueueEntry entry) throws AMQException + { + // if we do not need to wait for client acknowledgements + // we can decrement the reference count immediately. + + // By doing this _before_ the send we ensure that it + // doesn't get sent if it can't be dequeued, preventing + // duplicate delivery on recovery. + + // The send may of course still fail, in which case, as + // the message is unacked, it will be lost. + entry.dequeue(); + + + synchronized (getChannel()) + { + long deliveryTag = getChannel().getNextDeliveryTag(); + + sendToClient(entry, deliveryTag); + + } + entry.dispose(); + + + } + + @Override + public boolean wouldSuspend(QueueEntry msg) + { + return false; + } + + } + + static final class AckSubscription extends SubscriptionImpl + { + public AckSubscription(AMQChannel channel, AMQProtocolSession protocolSession, + AMQShortString consumerTag, FieldTable filters, + boolean noLocal, FlowCreditManager creditManager, + ClientDeliveryMethod deliveryMethod, + RecordDeliveryMethod recordMethod) + throws AMQException + { + super(channel, protocolSession, consumerTag, filters, noLocal, creditManager, deliveryMethod, recordMethod); + } + + + public boolean isBrowser() + { + return false; + } + + + /** + * This method can be called by each of the publisher threads. As a result all changes to the channel object must be + * thread safe. + * + * @param entry The message to send + * @throws AMQException + */ + @Override + public void send(QueueEntry entry) throws AMQException + { + + // if we do not need to wait for client acknowledgements + // we can decrement the reference count immediately. + + // By doing this _before_ the send we ensure that it + // doesn't get sent if it can't be dequeued, preventing + // duplicate delivery on recovery. + + // The send may of course still fail, in which case, as + // the message is unacked, it will be lost. + + synchronized (getChannel()) + { + long deliveryTag = getChannel().getNextDeliveryTag(); + + + recordMessageDelivery(entry, deliveryTag); + sendToClient(entry, deliveryTag); + + + } + } + + + + } + + + private static final Logger _logger = Logger.getLogger(SubscriptionImpl.class); + + private final AMQChannel _channel; + + private final AMQShortString _consumerTag; + + + private boolean _noLocal; + + private final FlowCreditManager _creditManager; + + private FilterManager _filters; + + private final Boolean _autoClose; + + + private static final String CLIENT_PROPERTIES_INSTANCE = ClientProperties.instance.toString(); + + private AMQQueue _queue; + private final AtomicBoolean _deleted = new AtomicBoolean(false); + + + + + public SubscriptionImpl(AMQChannel channel , AMQProtocolSession protocolSession, + AMQShortString consumerTag, FieldTable arguments, + boolean noLocal, FlowCreditManager creditManager, + ClientDeliveryMethod deliveryMethod, + RecordDeliveryMethod recordMethod) + throws AMQException + { + + _channel = channel; + _consumerTag = consumerTag; + + _creditManager = creditManager; + creditManager.addStateListener(this); + + _noLocal = noLocal; + + + _filters = FilterManagerFactory.createManager(arguments); + + _deliveryMethod = deliveryMethod; + _recordMethod = recordMethod; + + + _stateChangeLock = new ReentrantLock(); + + + if (arguments != null) + { + Object autoClose = arguments.get(AMQPFilterTypes.AUTO_CLOSE.getValue()); + if (autoClose != null) + { + _autoClose = (Boolean) autoClose; + } + else + { + _autoClose = false; + } + } + else + { + _autoClose = false; + } + + } + + public ConfigStore getConfigStore() + { + return getQueue().getConfigStore(); + } + + public Long getDelivered() + { + return _deliveredCount.get(); + } + + public synchronized void setQueue(AMQQueue queue, boolean exclusive) + { + if(getQueue() != null) + { + throw new IllegalStateException("Attempt to set queue for subscription " + this + " to " + queue + "when already set to " + getQueue()); + } + _queue = queue; + + _id = getConfigStore().createId(); + getConfigStore().addConfiguredObject(this); + + _logSubject = new SubscriptionLogSubject(this); + _logActor = new SubscriptionActor(CurrentActor.get().getRootMessageLogger(), this); + + if (CurrentActor.get().getRootMessageLogger(). + isMessageEnabled(CurrentActor.get(), _logSubject, SubscriptionMessages.CREATE_LOG_HIERARCHY)) + { + // Get the string value of the filters + String filterLogString = null; + if (_filters != null && _filters.hasFilters()) + { + filterLogString = _filters.toString(); + } + + if (isAutoClose()) + { + if (filterLogString == null) + { + filterLogString = ""; + } + else + { + filterLogString += ","; + } + filterLogString += "AutoClose"; + } + + if (isBrowser()) + { + // We do not need to check for null here as all Browsers are AutoClose + filterLogString +=",Browser"; + } + + CurrentActor.get(). + message(_logSubject, + SubscriptionMessages.CREATE(filterLogString, + queue.isDurable() && exclusive, + filterLogString != null)); + } + } + + public String toString() + { + String subscriber = "[channel=" + _channel + + ", consumerTag=" + _consumerTag + + ", session=" + getProtocolSession().getKey() ; + + return subscriber + "]"; + } + + /** + * This method can be called by each of the publisher threads. As a result all changes to the channel object must be + * thread safe. + * + * @param msg The message to send + * @throws AMQException + */ + abstract public void send(QueueEntry msg) throws AMQException; + + + public boolean isSuspended() + { + return !isActive() || _channel.isSuspended() || _deleted.get(); + } + + /** + * Callback indicating that a queue has been deleted. + * + * @param queue The queue to delete + */ + public void queueDeleted(AMQQueue queue) + { + _deleted.set(true); +// _channel.queueDeleted(queue); + } + + public boolean filtersMessages() + { + return _filters != null || _noLocal; + } + + public boolean hasInterest(QueueEntry entry) + { + + + + + //check that the message hasn't been rejected + if (entry.isRejectedBy(this)) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Subscription:" + this + " rejected message:" + entry); + } +// return false; + } + + if (_noLocal) + { + + AMQMessage message = (AMQMessage) entry.getMessage(); + + //todo - client id should be recorded so we don't have to handle + // the case where this is null. + final Object publisher = message.getPublisherIdentifier(); + + // We don't want local messages so check to see if message is one we sent + Object localInstance = getProtocolSession(); + + if(publisher.equals(localInstance)) + { + return false; + } + + + } + + + if (_logger.isDebugEnabled()) + { + _logger.debug("(" + this + ") checking filters for message (" + entry); + } + return checkFilters(entry); + + } + + private String id = String.valueOf(System.identityHashCode(this)); + + private String debugIdentity() + { + return id; + } + + private boolean checkFilters(QueueEntry msg) + { + return (_filters == null) || _filters.allAllow(msg); + } + + public boolean isAutoClose() + { + return _autoClose; + } + + public FlowCreditManager getCreditManager() + { + return _creditManager; + } + + + public void close() + { + boolean closed = false; + State state = getState(); + + _stateChangeLock.lock(); + try + { + while(!closed && state != State.CLOSED) + { + closed = _state.compareAndSet(state, State.CLOSED); + if(!closed) + { + state = getState(); + } + else + { + _stateListener.stateChange(this,state, State.CLOSED); + } + } + _creditManager.removeListener(this); + } + finally + { + _stateChangeLock.unlock(); + } + getConfigStore().removeConfiguredObject(this); + + //Log Subscription closed + CurrentActor.get().message(_logSubject, SubscriptionMessages.CLOSE()); + } + + public boolean isClosed() + { + return getState() == State.CLOSED; + } + + + public boolean wouldSuspend(QueueEntry msg) + { + return !_creditManager.useCreditForMessage(msg.getMessage());//_channel.wouldSuspend(msg.getMessage()); + } + + public void getSendLock() + { + _stateChangeLock.lock(); + } + + public void releaseSendLock() + { + _stateChangeLock.unlock(); + } + + public AMQChannel getChannel() + { + return _channel; + } + + public AMQShortString getConsumerTag() + { + return _consumerTag; + } + + public long getSubscriptionID() + { + return _subscriptionID; + } + + public AMQProtocolSession getProtocolSession() + { + return _channel.getProtocolSession(); + } + + public LogActor getLogActor() + { + return _logActor; + } + + public AMQQueue getQueue() + { + return _queue; + } + + public void onDequeue(final QueueEntry queueEntry) + { + restoreCredit(queueEntry); + } + + public void restoreCredit(final QueueEntry queueEntry) + { + _creditManager.restoreCredit(1, queueEntry.getSize()); + } + + + + public void creditStateChanged(boolean hasCredit) + { + + if(hasCredit) + { + if(_state.compareAndSet(State.SUSPENDED, State.ACTIVE)) + { + _stateListener.stateChange(this, State.SUSPENDED, State.ACTIVE); + } + else + { + // this is a hack to get round the issue of increasing bytes credit + _stateListener.stateChange(this, State.ACTIVE, State.ACTIVE); + } + } + else + { + if(_state.compareAndSet(State.ACTIVE, State.SUSPENDED)) + { + _stateListener.stateChange(this, State.ACTIVE, State.SUSPENDED); + } + } + CurrentActor.get().message(_logSubject,SubscriptionMessages.STATE(_state.get().toString())); + } + + public State getState() + { + return _state.get(); + } + + + public void setStateListener(final StateListener listener) + { + _stateListener = listener; + } + + + public AMQQueue.Context getQueueContext() + { + return _queueContext; + } + + public void setQueueContext(AMQQueue.Context context) + { + _queueContext = context; + } + + + protected void sendToClient(final QueueEntry entry, final long deliveryTag) + throws AMQException + { + _deliveryMethod.deliverToClient(this,entry,deliveryTag); + _deliveredCount.incrementAndGet(); + } + + + protected void recordMessageDelivery(final QueueEntry entry, final long deliveryTag) + { + _recordMethod.recordMessageDelivery(this,entry,deliveryTag); + } + + + public boolean isActive() + { + return getState() == State.ACTIVE; + } + + public QueueEntry.SubscriptionAcquiredState getOwningState() + { + return _owningState; + } + + public QueueEntry.SubscriptionAssignedState getAssignedState() + { + return _assignedState; + } + + + public void confirmAutoClose() + { + ProtocolOutputConverter converter = getChannel().getProtocolSession().getProtocolOutputConverter(); + converter.confirmConsumerAutoClose(getChannel().getChannelId(), getConsumerTag()); + } + + public boolean acquires() + { + return !isBrowser(); + } + + public boolean seesRequeues() + { + return !isBrowser(); + } + + public boolean isTransient() + { + return false; + } + + public void set(String key, Object value) + { + _properties.put(key, value); + } + + public Object get(String key) + { + return _properties.get(key); + } + + + public void setNoLocal(boolean noLocal) + { + _noLocal = noLocal; + } + + abstract boolean isBrowser(); + + public String getCreditMode() + { + return "WINDOW"; + } + + public SessionConfig getSessionConfig() + { + return getChannel(); + } + + public boolean isBrowsing() + { + return isBrowser(); + } + + public boolean isExplicitAcknowledge() + { + return true; + } + + public UUID getId() + { + return _id; + } + + public boolean isDurable() + { + return false; + } + + public SubscriptionConfigType getConfigType() + { + return SubscriptionConfigType.getInstance(); + } + + public boolean isExclusive() + { + return getQueue().hasExclusiveSubscriber(); + } + + public ConfiguredObject getParent() + { + return getSessionConfig(); + } + + public String getName() + { + return String.valueOf(_consumerTag); + } + + public Map<String, Object> getArguments() + { + return null; + } + + public boolean isSessionTransactional() + { + return _channel.isTransactional(); + } + + public long getCreateTime() + { + return _createTime; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionList.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionList.java new file mode 100644 index 0000000000..9ea81660c6 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/SubscriptionList.java @@ -0,0 +1,245 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.subscription; + +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.subscription.Subscription; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.nio.ByteBuffer; + +public class SubscriptionList +{ + + private final SubscriptionNode _head = new SubscriptionNode(); + + private AtomicReference<SubscriptionNode> _tail = new AtomicReference<SubscriptionNode>(_head); + private AtomicInteger _size = new AtomicInteger(); + + + public final class SubscriptionNode + { + private final AtomicBoolean _deleted = new AtomicBoolean(); + private final AtomicReference<SubscriptionNode> _next = new AtomicReference<SubscriptionNode>(); + private final Subscription _sub; + + + public SubscriptionNode() + { + + _sub = null; + _deleted.set(true); + } + + public SubscriptionNode(final Subscription sub) + { + _sub = sub; + } + + + public SubscriptionNode getNext() + { + + SubscriptionNode next = nextNode(); + while(next != null && next.isDeleted()) + { + + final SubscriptionNode newNext = next.nextNode(); + if(newNext != null) + { + _next.compareAndSet(next, newNext); + next = nextNode(); + } + else + { + next = null; + } + + } + return next; + } + + private SubscriptionNode nextNode() + { + return _next.get(); + } + + public boolean isDeleted() + { + return _deleted.get(); + } + + + public boolean delete() + { + if(_deleted.compareAndSet(false,true)) + { + _size.decrementAndGet(); + advanceHead(); + return true; + } + else + { + return false; + } + } + + + public Subscription getSubscription() + { + return _sub; + } + } + + + public SubscriptionList(AMQQueue queue) + { + } + + private void advanceHead() + { + SubscriptionNode head = _head.nextNode(); + while(head._next.get() != null && head.isDeleted()) + { + + final SubscriptionNode newhead = head.nextNode(); + if(newhead != null) + { + _head._next.compareAndSet(head, newhead); + } + head = _head.nextNode(); + } + } + + + public SubscriptionNode add(Subscription sub) + { + SubscriptionNode node = new SubscriptionNode(sub); + for (;;) + { + SubscriptionNode tail = _tail.get(); + SubscriptionNode next = tail.nextNode(); + if (tail == _tail.get()) + { + if (next == null) + { + if (tail._next.compareAndSet(null, node)) + { + _tail.compareAndSet(tail, node); + _size.incrementAndGet(); + return node; + } + } + else + { + _tail.compareAndSet(tail, next); + } + } + } + + } + + public boolean remove(Subscription sub) + { + SubscriptionNode node = _head.getNext(); + while(node != null) + { + if(sub.equals(node._sub) && node.delete()) + { + return true; + } + node = node.getNext(); + } + return false; + } + + + public static class SubscriptionNodeIterator + { + + private SubscriptionNode _lastNode; + + SubscriptionNodeIterator(SubscriptionNode startNode) + { + _lastNode = startNode; + } + + + public boolean atTail() + { + return _lastNode.nextNode() == null; + } + + public SubscriptionNode getNode() + { + + return _lastNode; + + } + + public boolean advance() + { + + if(!atTail()) + { + SubscriptionNode nextNode = _lastNode.nextNode(); + while(nextNode.isDeleted() && nextNode.nextNode() != null) + { + nextNode = nextNode.nextNode(); + } + _lastNode = nextNode; + return true; + + } + else + { + return false; + } + + } + + } + + + public SubscriptionNodeIterator iterator() + { + return new SubscriptionNodeIterator(_head); + } + + + public SubscriptionNode getHead() + { + return _head; + } + + public int size() + { + return _size.get(); + } + + + +} + + + diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/Subscription_0_10.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/Subscription_0_10.java new file mode 100644 index 0000000000..68e47fd86a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/subscription/Subscription_0_10.java @@ -0,0 +1,957 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.subscription; + +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.SUBSCRIPTION_FORMAT; +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.QUEUE_FORMAT; + +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.configuration.ConfigStore; +import org.apache.qpid.server.configuration.ConfiguredObject; +import org.apache.qpid.server.configuration.SessionConfig; +import org.apache.qpid.server.configuration.SubscriptionConfig; +import org.apache.qpid.server.configuration.SubscriptionConfigType; +import org.apache.qpid.server.flow.FlowCreditManager; +import org.apache.qpid.server.flow.CreditCreditManager; +import org.apache.qpid.server.flow.WindowCreditManager; +import org.apache.qpid.server.flow.FlowCreditManager_0_10; +import org.apache.qpid.server.filter.FilterManager; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.GenericActor; +import org.apache.qpid.server.logging.messages.SubscriptionMessages; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.SubscriptionActor; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.message.MessageTransferMessage; +import org.apache.qpid.server.message.AMQMessage; +import org.apache.qpid.server.transport.ServerSession; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.txn.AutoCommitTransaction; +import org.apache.qpid.transport.DeliveryProperties; +import org.apache.qpid.transport.Header; +import org.apache.qpid.transport.MessageAcceptMode; +import org.apache.qpid.transport.MessageAcquireMode; +import org.apache.qpid.transport.MessageCreditUnit; +import org.apache.qpid.transport.MessageDeliveryPriority; +import org.apache.qpid.transport.MessageFlowMode; +import org.apache.qpid.transport.MessageProperties; +import org.apache.qpid.transport.MessageTransfer; +import org.apache.qpid.transport.Method; +import org.apache.qpid.transport.Struct; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.AMQException; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.ConcurrentHashMap; +import java.nio.ByteBuffer; + +public class Subscription_0_10 implements Subscription, FlowCreditManager.FlowCreditManagerListener, SubscriptionConfig, LogSubject +{ + + private static final AtomicLong idGenerator = new AtomicLong(0); + // Create a simple ID that increments for ever new Subscription + private final long _subscriptionID = idGenerator.getAndIncrement(); + + private final QueueEntry.SubscriptionAcquiredState _owningState = new QueueEntry.SubscriptionAcquiredState(this); + private final QueueEntry.SubscriptionAssignedState _assignedState = new QueueEntry.SubscriptionAssignedState(this); + + private final Lock _stateChangeLock = new ReentrantLock(); + + private final AtomicReference<State> _state = new AtomicReference<State>(State.ACTIVE); + private AMQQueue.Context _queueContext; + private final AtomicBoolean _deleted = new AtomicBoolean(false); + + + private FlowCreditManager_0_10 _creditManager; + + private StateListener _stateListener = new StateListener() + { + + public void stateChange(Subscription sub, State oldState, State newState) + { + CurrentActor.get().message(SubscriptionMessages.STATE(newState.toString())); + } + }; + private AMQQueue _queue; + private final String _destination; + private boolean _noLocal; + private final FilterManager _filters; + private final MessageAcceptMode _acceptMode; + private final MessageAcquireMode _acquireMode; + private MessageFlowMode _flowMode; + private final ServerSession _session; + private AtomicBoolean _stopped = new AtomicBoolean(true); + private ConcurrentHashMap<Integer, QueueEntry> _sentMap = new ConcurrentHashMap<Integer, QueueEntry>(); + private static final Struct[] EMPTY_STRUCT_ARRAY = new Struct[0]; + + private LogActor _logActor; + private Map<String, Object> _properties = new ConcurrentHashMap<String, Object>(); + private UUID _id; + private String _traceExclude; + private String _trace; + private long _createTime = System.currentTimeMillis(); + private final AtomicLong _deliveredCount = new AtomicLong(0); + private final Map<String, Object> _arguments; + + + public Subscription_0_10(ServerSession session, String destination, MessageAcceptMode acceptMode, + MessageAcquireMode acquireMode, + MessageFlowMode flowMode, + FlowCreditManager_0_10 creditManager, + FilterManager filters,Map<String, Object> arguments) + { + _session = session; + _destination = destination; + _acceptMode = acceptMode; + _acquireMode = acquireMode; + _creditManager = creditManager; + _flowMode = flowMode; + _filters = filters; + _creditManager.addStateListener(this); + _arguments = arguments == null ? Collections.<String, Object> emptyMap() : + Collections.<String, Object> unmodifiableMap(arguments); + _state.set(_creditManager.hasCredit() ? State.ACTIVE : State.SUSPENDED); + + } + + public void setNoLocal(boolean noLocal) + { + _noLocal = noLocal; + } + + public AMQQueue getQueue() + { + return _queue; + } + + public QueueEntry.SubscriptionAcquiredState getOwningState() + { + return _owningState; + } + + public QueueEntry.SubscriptionAssignedState getAssignedState() + { + return _assignedState; + } + + public void setQueue(AMQQueue queue, boolean exclusive) + { + if(getQueue() != null) + { + throw new IllegalStateException("Attempt to set queue for subscription " + this + " to " + queue + "when already set to " + getQueue()); + } + _queue = queue; + Map<String, Object> arguments = queue.getArguments() == null ? Collections.EMPTY_MAP : queue.getArguments(); + _traceExclude = (String) arguments.get("qpid.trace.exclude"); + _trace = (String) arguments.get("qpid.trace.id"); + _id = getConfigStore().createId(); + getConfigStore().addConfiguredObject(this); + String filterLogString = null; + + _logActor = GenericActor.getInstance(this); + if (CurrentActor.get().getRootMessageLogger().isMessageEnabled(_logActor, this, SubscriptionMessages.CREATE_LOG_HIERARCHY)) + { + filterLogString = getFilterLogString(); + CurrentActor.get().message(this, SubscriptionMessages.CREATE(filterLogString, queue.isDurable() && exclusive, + filterLogString.length() > 0)); + } + + } + + public AMQShortString getConsumerTag() + { + return new AMQShortString(_destination); + } + + public boolean isSuspended() + { + return !isActive() || _deleted.get(); // TODO check for Session suspension + } + + public boolean hasInterest(QueueEntry entry) + { + + + + //check that the message hasn't been rejected + if (entry.isRejectedBy(this)) + { + + return false; + } + + + + if (_noLocal + && (entry.getMessage() instanceof MessageTransferMessage) + && ((MessageTransferMessage)entry.getMessage()).getSession() == _session) + { + return false; + } + + + return checkFilters(entry); + + + } + + private boolean checkFilters(QueueEntry entry) + { + return (_filters == null) || _filters.allAllow(entry); + } + + public boolean isAutoClose() + { + // no such thing in 0-10 + return false; + } + + public boolean isClosed() + { + return getState() == State.CLOSED; + } + + public boolean isBrowser() + { + return _acquireMode == MessageAcquireMode.NOT_ACQUIRED; + } + + public boolean seesRequeues() + { + return _acquireMode != MessageAcquireMode.NOT_ACQUIRED || _acceptMode == MessageAcceptMode.EXPLICIT; + } + + public void close() + { + boolean closed = false; + State state = getState(); + + _stateChangeLock.lock(); + try + { + while(!closed && state != State.CLOSED) + { + closed = _state.compareAndSet(state, State.CLOSED); + if(!closed) + { + state = getState(); + } + else + { + _stateListener.stateChange(this,state, State.CLOSED); + } + } + _creditManager.removeListener(this); + getConfigStore().removeConfiguredObject(this); + CurrentActor.get().message(getLogSubject(), SubscriptionMessages.CLOSE()); + } + finally + { + _stateChangeLock.unlock(); + } + + + + } + + public ConfigStore getConfigStore() + { + return getQueue().getConfigStore(); + } + + public Long getDelivered() + { + return _deliveredCount.get(); + } + + public void creditStateChanged(boolean hasCredit) + { + + if(hasCredit) + { + if(_state.compareAndSet(State.SUSPENDED, State.ACTIVE)) + { + _stateListener.stateChange(this, State.SUSPENDED, State.ACTIVE); + } + else + { + // this is a hack to get round the issue of increasing bytes credit + _stateListener.stateChange(this, State.ACTIVE, State.ACTIVE); + } + } + else + { + if(_state.compareAndSet(State.ACTIVE, State.SUSPENDED)) + { + _stateListener.stateChange(this, State.ACTIVE, State.SUSPENDED); + } + } + } + + + private class AddMessageDispositionListnerAction implements Runnable + { + public MessageTransfer _xfr; + public ServerSession.MessageDispositionChangeListener _action; + + public void run() + { + if(_action != null) + { + _session.onMessageDispositionChange(_xfr, _action); + } + } + } + + private final AddMessageDispositionListnerAction _postIdSettingAction = new AddMessageDispositionListnerAction(); + + public void send(final QueueEntry entry) throws AMQException + { + ServerMessage serverMsg = entry.getMessage(); + + + MessageTransfer xfr; + + DeliveryProperties deliveryProps; + MessageProperties messageProps = null; + + if(serverMsg instanceof MessageTransferMessage) + { + + MessageTransferMessage msg = (MessageTransferMessage) serverMsg; + + + Struct[] headers; + if(msg.getHeader() == null) + { + headers = EMPTY_STRUCT_ARRAY; + } + else + { + headers = msg.getHeader().getStructs(); + } + + ArrayList<Struct> newHeaders = new ArrayList<Struct>(headers.length); + DeliveryProperties origDeliveryProps = null; + for(Struct header : headers) + { + if(header instanceof DeliveryProperties) + { + origDeliveryProps = (DeliveryProperties) header; + } + else + { + if(header instanceof MessageProperties) + { + messageProps = (MessageProperties) header; + } + newHeaders.add(header); + } + } + + deliveryProps = new DeliveryProperties(); + if(origDeliveryProps != null) + { + if(origDeliveryProps.hasDeliveryMode()) + { + deliveryProps.setDeliveryMode(origDeliveryProps.getDeliveryMode()); + } + if(origDeliveryProps.hasExchange()) + { + deliveryProps.setExchange(origDeliveryProps.getExchange()); + } + if(origDeliveryProps.hasExpiration()) + { + deliveryProps.setExpiration(origDeliveryProps.getExpiration()); + } + if(origDeliveryProps.hasPriority()) + { + deliveryProps.setPriority(origDeliveryProps.getPriority()); + } + if(origDeliveryProps.hasRoutingKey()) + { + deliveryProps.setRoutingKey(origDeliveryProps.getRoutingKey()); + } + if(origDeliveryProps.hasTimestamp()) + { + deliveryProps.setTimestamp(origDeliveryProps.getTimestamp()); + } + + + } + + deliveryProps.setRedelivered(entry.isRedelivered()); + + newHeaders.add(deliveryProps); + + if(_trace != null && messageProps == null) + { + messageProps = new MessageProperties(); + newHeaders.add(messageProps); + } + + Header header = new Header(newHeaders); + + xfr = new MessageTransfer(_destination,_acceptMode,_acquireMode,header,msg.getBody()); + } + else if(serverMsg instanceof AMQMessage) + { + AMQMessage message_0_8 = (AMQMessage) serverMsg; + deliveryProps = new DeliveryProperties(); + messageProps = new MessageProperties(); + + int size = (int) message_0_8.getSize(); + ByteBuffer body = ByteBuffer.allocate(size); + message_0_8.getContent(body, 0); + body.flip(); + + Struct[] headers = new Struct[] { deliveryProps, messageProps }; + + BasicContentHeaderProperties properties = + (BasicContentHeaderProperties) message_0_8.getContentHeaderBody().getProperties(); + final AMQShortString exchange = message_0_8.getMessagePublishInfo().getExchange(); + if(exchange != null) + { + deliveryProps.setExchange(exchange.toString()); + } + deliveryProps.setExpiration(message_0_8.getExpiration()); + deliveryProps.setImmediate(message_0_8.isImmediate()); + deliveryProps.setPriority(MessageDeliveryPriority.get(properties.getPriority())); + deliveryProps.setRedelivered(entry.isRedelivered()); + deliveryProps.setRoutingKey(message_0_8.getRoutingKey()); + deliveryProps.setTimestamp(properties.getTimestamp()); + + messageProps.setContentEncoding(properties.getEncodingAsString()); + messageProps.setContentLength(size); + if(properties.getAppId() != null) + { + messageProps.setAppId(properties.getAppId().getBytes()); + } + messageProps.setContentType(properties.getContentTypeAsString()); + if(properties.getCorrelationId() != null) + { + messageProps.setCorrelationId(properties.getCorrelationId().getBytes()); + } + + // TODO - ReplyTo + + if(properties.getUserId() != null) + { + messageProps.setUserId(properties.getUserId().getBytes()); + } + + FieldTable fieldTable = properties.getHeaders(); + + final Map<String, Object> appHeaders = FieldTable.convertToMap(fieldTable); + + + messageProps.setApplicationHeaders(appHeaders); + + Header header = new Header(headers); + xfr = new MessageTransfer(_destination,_acceptMode,_acquireMode,header, body); + } + else + { + + deliveryProps = new DeliveryProperties(); + messageProps = new MessageProperties(); + + int size = (int) serverMsg.getSize(); + ByteBuffer body = ByteBuffer.allocate(size); + serverMsg.getContent(body, 0); + body.flip(); + + Struct[] headers = new Struct[] { deliveryProps, messageProps }; + + + deliveryProps.setExpiration(serverMsg.getExpiration()); + deliveryProps.setImmediate(serverMsg.isImmediate()); + deliveryProps.setPriority(MessageDeliveryPriority.get(serverMsg.getMessageHeader().getPriority())); + deliveryProps.setRedelivered(entry.isRedelivered()); + deliveryProps.setRoutingKey(serverMsg.getRoutingKey()); + deliveryProps.setTimestamp(serverMsg.getMessageHeader().getTimestamp()); + + messageProps.setContentEncoding(serverMsg.getMessageHeader().getEncoding()); + messageProps.setContentLength(size); + messageProps.setContentType(serverMsg.getMessageHeader().getMimeType()); + if(serverMsg.getMessageHeader().getCorrelationId() != null) + { + messageProps.setCorrelationId(serverMsg.getMessageHeader().getCorrelationId().getBytes()); + } + + + // TODO - ReplyTo + + + final Map<String, Object> appHeaders = new HashMap<String, Object>(); + + /*properties.getHeaders().processOverElements( + new FieldTable.FieldTableElementProcessor() + { + + public boolean processElement(String propertyName, AMQTypedValue value) + { + Object val = value.getValue(); + if(val instanceof AMQShortString) + { + val = val.toString(); + } + appHeaders.put(propertyName, val); + return true; + } + + public Object getResult() + { + return appHeaders; + } + }); + + + messageProps.setApplicationHeaders(appHeaders); +*/ + Header header = new Header(headers); + xfr = new MessageTransfer(_destination,_acceptMode,_acquireMode,header, body); + } + + boolean excludeDueToFederation = false; + + if(_trace != null) + { + if(!messageProps.hasApplicationHeaders()) + { + messageProps.setApplicationHeaders(new HashMap<String,Object>()); + } + Map<String,Object> appHeaders = messageProps.getApplicationHeaders(); + String trace = (String) appHeaders.get("x-qpid.trace"); + if(trace == null) + { + trace = _trace; + } + else + { + if(_traceExclude != null) + { + excludeDueToFederation = Arrays.asList(trace.split(",")).contains(_traceExclude); + } + trace+=","+_trace; + } + appHeaders.put("x-qpid.trace",trace); + } + + if(!excludeDueToFederation) + { + if(_acceptMode == MessageAcceptMode.NONE && _acquireMode != MessageAcquireMode.PRE_ACQUIRED) + { + xfr.setCompletionListener(new MessageAcceptCompletionListener(this, _session, entry, _flowMode == MessageFlowMode.WINDOW)); + } + else if(_flowMode == MessageFlowMode.WINDOW) + { + xfr.setCompletionListener(new Method.CompletionListener() + { + public void onComplete(Method method) + { + restoreCredit(entry); + } + }); + } + + + _postIdSettingAction._xfr = xfr; + if(_acceptMode == MessageAcceptMode.EXPLICIT) + { + _postIdSettingAction._action = new ExplicitAcceptDispositionChangeListener(entry, this); + } + else if(_acquireMode != MessageAcquireMode.PRE_ACQUIRED) + { + _postIdSettingAction._action = new ImplicitAcceptDispositionChangeListener(entry, this); + } + else + { + _postIdSettingAction._action = null; + } + + _session.sendMessage(xfr, _postIdSettingAction); + _deliveredCount.incrementAndGet(); + if(_acceptMode == MessageAcceptMode.NONE && _acquireMode == MessageAcquireMode.PRE_ACQUIRED) + { + forceDequeue(entry, false); + } + } + else + { + forceDequeue(entry, _flowMode == MessageFlowMode.WINDOW); + + } + } + + private void forceDequeue(final QueueEntry entry, final boolean restoreCredit) + { + ServerTransaction txn = new AutoCommitTransaction(getQueue().getVirtualHost().getTransactionLog()); + txn.dequeue(entry.getQueue(),entry.getMessage(), + new ServerTransaction.Action() + { + public void postCommit() + { + if(restoreCredit) + { + restoreCredit(entry); + } + entry.discard(); + } + + public void onRollback() + { + + } + }); + } + + void reject(QueueEntry entry) + { + entry.setRedelivered(); + entry.routeToAlternate(); + + } + + void release(QueueEntry entry) + { + entry.setRedelivered(); + entry.release(); + } + + public void queueDeleted(AMQQueue queue) + { + _deleted.set(true); + } + + public boolean wouldSuspend(QueueEntry msg) + { + return !_creditManager.useCreditForMessage(msg.getMessage()); + } + + public void getSendLock() + { + _stateChangeLock.lock(); + } + + public void releaseSendLock() + { + _stateChangeLock.unlock(); + } + + public void restoreCredit(QueueEntry queueEntry) + { + _creditManager.restoreCredit(1, queueEntry.getSize()); + } + + public void onDequeue(QueueEntry queueEntry) + { + + } + + public void setStateListener(StateListener listener) + { + _stateListener = listener; + } + + public State getState() + { + return _state.get(); + } + + public AMQQueue.Context getQueueContext() + { + return _queueContext; + } + + public void setQueueContext(AMQQueue.Context queueContext) + { + _queueContext = queueContext; + } + + public boolean isActive() + { + return getState() == State.ACTIVE; + } + + public void confirmAutoClose() + { + //No such thing in 0-10 + } + + public void set(String key, Object value) + { + _properties.put(key, value); + } + + public Object get(String key) + { + return _properties.get(key); + } + + + public FlowCreditManager_0_10 getCreditManager() + { + return _creditManager; + } + + + public void stop() + { + if(_state.compareAndSet(State.ACTIVE, State.SUSPENDED)) + { + _stateListener.stateChange(this, State.ACTIVE, State.SUSPENDED); + } + _stopped.set(true); + FlowCreditManager_0_10 creditManager = getCreditManager(); + creditManager.clearCredit(); + } + + public void addCredit(MessageCreditUnit unit, long value) + { + FlowCreditManager_0_10 creditManager = getCreditManager(); + + switch (unit) + { + case MESSAGE: + + creditManager.addCredit(value, 0L); + break; + case BYTE: + creditManager.addCredit(0l, value); + break; + } + + _stopped.set(false); + + if(creditManager.hasCredit()) + { + if(_state.compareAndSet(State.SUSPENDED, State.ACTIVE)) + { + _stateListener.stateChange(this, State.SUSPENDED, State.ACTIVE); + } + } + + } + + public void setFlowMode(MessageFlowMode flowMode) + { + + + _creditManager.removeListener(this); + + switch(flowMode) + { + case CREDIT: + _creditManager = new CreditCreditManager(0l,0l); + break; + case WINDOW: + _creditManager = new WindowCreditManager(0l,0l); + break; + default: + throw new RuntimeException("Unknown message flow mode: " + flowMode); + } + _flowMode = flowMode; + if(_state.compareAndSet(State.ACTIVE, State.SUSPENDED)) + { + _stateListener.stateChange(this, State.ACTIVE, State.SUSPENDED); + } + + _creditManager.addStateListener(this); + + } + + public boolean isStopped() + { + return _stopped.get(); + } + + public boolean acquires() + { + return _acquireMode == MessageAcquireMode.PRE_ACQUIRED; + } + + public void acknowledge(QueueEntry entry) + { + // TODO Fix Store Context / cleanup + if(entry.isAcquiredBy(this)) + { + entry.discard(); + } + } + + public void flush() throws AMQException + { + _queue.flushSubscription(this); + stop(); + } + + public long getSubscriptionID() + { + return _subscriptionID; + } + + public LogActor getLogActor() + { + return _logActor; + } + + public boolean isTransient() + { + return false; + } + + ServerSession getSession() + { + return _session; + } + + + public SessionConfig getSessionConfig() + { + return getSession(); + } + + public boolean isBrowsing() + { + return _acquireMode == MessageAcquireMode.NOT_ACQUIRED; + } + + public boolean isExclusive() + { + return getQueue().hasExclusiveSubscriber(); + } + + public ConfiguredObject getParent() + { + return getSessionConfig(); + } + + public boolean isDurable() + { + return false; + } + + public SubscriptionConfigType getConfigType() + { + return SubscriptionConfigType.getInstance(); + } + + public boolean isExplicitAcknowledge() + { + return _acceptMode == MessageAcceptMode.EXPLICIT; + } + + public String getCreditMode() + { + return _flowMode.toString(); + } + + public UUID getId() + { + return _id; + } + + public String getName() + { + return _destination; + } + + public Map<String, Object> getArguments() + { + return _arguments; + } + + public boolean isSessionTransactional() + { + return _session.isTransactional(); + } + + public long getCreateTime() + { + return _createTime; + } + + public String toLogString() + { + String queueInfo = MessageFormat.format(QUEUE_FORMAT, _queue.getVirtualHost().getName(), + _queue.getNameShortString()); + String result = "[" + MessageFormat.format(SUBSCRIPTION_FORMAT, getSubscriptionID()) + "(" + // queueString is "vh(/{0})/qu({1}) " so need to trim + + queueInfo.substring(0, queueInfo.length() - 1) + ")" + "] "; + return result; + } + + private String getFilterLogString() + { + StringBuilder filterLogString = new StringBuilder(); + String delimiter = ", "; + boolean hasEntries = false; + if (_filters != null && _filters.hasFilters()) + { + filterLogString.append(_filters.toString()); + hasEntries = true; + } + + if (isBrowser()) + { + if (hasEntries) + { + filterLogString.append(delimiter); + } + filterLogString.append("Browser"); + hasEntries = true; + } + + if (isDurable()) + { + if (hasEntries) + { + filterLogString.append(delimiter); + } + filterLogString.append("Durable"); + hasEntries = true; + } + + return filterLogString.toString(); + } + + public LogSubject getLogSubject() + { + return (LogSubject) this; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/QpidAcceptor.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/QpidAcceptor.java new file mode 100644 index 0000000000..3ca22b60c8 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/QpidAcceptor.java @@ -0,0 +1,44 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.transport; + +import org.apache.qpid.transport.NetworkDriver; + +public class QpidAcceptor +{ + NetworkDriver _driver; + String _protocol; + public QpidAcceptor(NetworkDriver driver, String protocol) + { + _driver = driver; + _protocol = protocol; + } + + public NetworkDriver getNetworkDriver() + { + return _driver; + } + + public String toString() + { + return _protocol; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ServerConnection.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ServerConnection.java new file mode 100644 index 0000000000..54cd709af3 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ServerConnection.java @@ -0,0 +1,346 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.transport; + +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.*; + +import java.text.MessageFormat; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.configuration.ConnectionConfig; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.GenericActor; +import org.apache.qpid.server.logging.messages.ConnectionMessages; +import org.apache.qpid.server.protocol.AMQConnectionModel; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.stats.StatisticsCounter; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.transport.Connection; +import org.apache.qpid.transport.ConnectionCloseCode; +import org.apache.qpid.transport.ExecutionErrorCode; +import org.apache.qpid.transport.ExecutionException; +import org.apache.qpid.transport.Method; +import org.apache.qpid.transport.ProtocolEvent; +import org.apache.qpid.transport.Session; + +public class ServerConnection extends Connection implements AMQConnectionModel, LogSubject +{ + private ConnectionConfig _config; + private Runnable _onOpenTask; + private AtomicBoolean _logClosed = new AtomicBoolean(false); + private LogActor _actor = GenericActor.getInstance(this); + + private ApplicationRegistry _registry; + private boolean _statisticsEnabled = false; + private StatisticsCounter _messagesDelivered, _dataDelivered, _messagesReceived, _dataReceived; + + public ServerConnection() + { + + } + + public UUID getId() + { + return _config.getId(); + } + + @Override + protected void invoke(Method method) + { + super.invoke(method); + } + + @Override + protected void setState(State state) + { + super.setState(state); + + if (state == State.OPEN) + { + if (_onOpenTask != null) + { + _onOpenTask.run(); + } + _actor.message(ConnectionMessages.OPEN(getClientId(), "0-10", true, true)); + + getVirtualHost().getConnectionRegistry().registerConnection(this); + } + + if (state == State.CLOSE_RCVD || state == State.CLOSED || state == State.CLOSING) + { + if(_virtualHost != null) + { + _virtualHost.getConnectionRegistry().deregisterConnection(this); + } + } + + if (state == State.CLOSED) + { + logClosed(); + } + } + + protected void logClosed() + { + if(_logClosed.compareAndSet(false, true)) + { + CurrentActor.get().message(this, ConnectionMessages.CLOSE()); + } + } + + @Override + public ServerConnectionDelegate getConnectionDelegate() + { + return (ServerConnectionDelegate) super.getConnectionDelegate(); + } + + public void setConnectionDelegate(ServerConnectionDelegate delegate) + { + super.setConnectionDelegate(delegate); + } + + private VirtualHost _virtualHost; + + + public VirtualHost getVirtualHost() + { + return _virtualHost; + } + + public void setVirtualHost(VirtualHost virtualHost) + { + _virtualHost = virtualHost; + + initialiseStatistics(); + } + + public void setConnectionConfig(final ConnectionConfig config) + { + _config = config; + } + + public ConnectionConfig getConfig() + { + return _config; + } + + public void onOpen(final Runnable task) + { + _onOpenTask = task; + } + + public void closeSession(AMQSessionModel session, AMQConstant cause, String message) throws AMQException + { + ExecutionException ex = new ExecutionException(); + ExecutionErrorCode code = ExecutionErrorCode.INTERNAL_ERROR; + try + { + code = ExecutionErrorCode.get(cause.getCode()); + } + catch (IllegalArgumentException iae) + { + // Ignore, already set to INTERNAL_ERROR + } + ex.setErrorCode(code); + ex.setDescription(message); + ((ServerSession)session).invoke(ex); + + ((ServerSession)session).close(); + } + + public LogSubject getLogSubject() + { + return (LogSubject) this; + } + + @Override + public void received(ProtocolEvent event) + { + if (event.isConnectionControl()) + { + CurrentActor.set(_actor); + } + else + { + ServerSession channel = (ServerSession) getSession(event.getChannel()); + LogActor channelActor = null; + + if (channel != null) + { + channelActor = channel.getLogActor(); + } + + CurrentActor.set(channelActor == null ? _actor : channelActor); + } + + try + { + super.received(event); + } + finally + { + CurrentActor.remove(); + } + } + + public String toLogString() + { + boolean hasVirtualHost = (null != this.getVirtualHost()); + boolean hasPrincipal = (null != getAuthorizationID()); + + if (hasPrincipal && hasVirtualHost) + { + return "[" + + MessageFormat.format(CONNECTION_FORMAT, + getConnectionId(), + getClientId(), + getConfig().getAddress(), + getVirtualHost().getName()) + + "] "; + } + else if (hasPrincipal) + { + return "[" + + MessageFormat.format(USER_FORMAT, + getConnectionId(), + getClientId(), + getConfig().getAddress()) + + "] "; + + } + else + { + return "[" + + MessageFormat.format(SOCKET_FORMAT, + getConnectionId(), + getConfig().getAddress()) + + "] "; + } + } + + public LogActor getLogActor() + { + return _actor; + } + + @Override + public void close(AMQConstant cause, String message) throws AMQException + { + ConnectionCloseCode replyCode = ConnectionCloseCode.NORMAL; + try + { + replyCode = ConnectionCloseCode.get(cause.getCode()); + } + catch (IllegalArgumentException iae) + { + // Ignore + } + close(replyCode, message); + } + + @Override + public List<AMQSessionModel> getSessionModels() + { + List<AMQSessionModel> sessions = new ArrayList<AMQSessionModel>(); + for (Session ssn : getChannels()) + { + sessions.add((AMQSessionModel) ssn); + } + return sessions; + } + + public void registerMessageDelivered(long messageSize) + { + if (isStatisticsEnabled()) + { + _messagesDelivered.registerEvent(1L); + _dataDelivered.registerEvent(messageSize); + } + _virtualHost.registerMessageDelivered(messageSize); + } + + public void registerMessageReceived(long messageSize, long timestamp) + { + if (isStatisticsEnabled()) + { + _messagesReceived.registerEvent(1L, timestamp); + _dataReceived.registerEvent(messageSize, timestamp); + } + _virtualHost.registerMessageReceived(messageSize, timestamp); + } + + public StatisticsCounter getMessageReceiptStatistics() + { + return _messagesReceived; + } + + public StatisticsCounter getDataReceiptStatistics() + { + return _dataReceived; + } + + public StatisticsCounter getMessageDeliveryStatistics() + { + return _messagesDelivered; + } + + public StatisticsCounter getDataDeliveryStatistics() + { + return _dataDelivered; + } + + public void resetStatistics() + { + _messagesDelivered.reset(); + _dataDelivered.reset(); + _messagesReceived.reset(); + _dataReceived.reset(); + } + + public void initialiseStatistics() + { + setStatisticsEnabled(!StatisticsCounter.DISABLE_STATISTICS && + _virtualHost.getApplicationRegistry().getConfiguration().isStatisticsGenerationConnectionsEnabled()); + + _messagesDelivered = new StatisticsCounter("messages-delivered-" + getConnectionId()); + _dataDelivered = new StatisticsCounter("data-delivered-" + getConnectionId()); + _messagesReceived = new StatisticsCounter("messages-received-" + getConnectionId()); + _dataReceived = new StatisticsCounter("data-received-" + getConnectionId()); + } + + public boolean isStatisticsEnabled() + { + return _statisticsEnabled; + } + + public void setStatisticsEnabled(boolean enabled) + { + _statisticsEnabled = enabled; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ServerConnectionDelegate.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ServerConnectionDelegate.java new file mode 100644 index 0000000000..174dcbfa69 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ServerConnectionDelegate.java @@ -0,0 +1,158 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.transport; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.qpid.protocol.ProtocolEngine; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.transport.*; + +public class ServerConnectionDelegate extends ServerDelegate +{ + private String _localFQDN; + private final IApplicationRegistry _appRegistry; + + public ServerConnectionDelegate(IApplicationRegistry appRegistry, String localFQDN) + { + this(new HashMap<String,Object>(Collections.singletonMap("qpid.federation_tag",appRegistry.getBroker().getFederationTag())), Collections.singletonList((Object)"en_US"), appRegistry, localFQDN); + } + + + public ServerConnectionDelegate(Map<String, Object> properties, + List<Object> locales, + IApplicationRegistry appRegistry, + String localFQDN) + { + super(properties, parseToList(appRegistry.getAuthenticationManager().getMechanisms()), locales); + + _appRegistry = appRegistry; + _localFQDN = localFQDN; + } + + private static List<Object> parseToList(String mechanisms) + { + List<Object> list = new ArrayList<Object>(); + StringTokenizer tokenizer = new StringTokenizer(mechanisms, " "); + while(tokenizer.hasMoreTokens()) + { + list.add(tokenizer.nextToken()); + } + return list; + } + + @Override + public ServerSession getSession(Connection conn, SessionAttach atc) + { + SessionDelegate serverSessionDelegate = new ServerSessionDelegate(_appRegistry); + + ServerSession ssn = new ServerSession(conn, serverSessionDelegate, new Binary(atc.getName()), 0); + + return ssn; + } + + @Override + protected SaslServer createSaslServer(String mechanism) throws SaslException + { + return _appRegistry.getAuthenticationManager().createSaslServer(mechanism, _localFQDN); + + } + + @Override + public void connectionClose(Connection conn, ConnectionClose close) + { + try + { + ((ServerConnection) conn).logClosed(); + } + finally + { + super.connectionClose(conn, close); + } + + } + + @Override + public void connectionOpen(Connection conn, ConnectionOpen open) + { + ServerConnection sconn = (ServerConnection) conn; + + VirtualHost vhost; + String vhostName; + if(open.hasVirtualHost()) + { + vhostName = open.getVirtualHost(); + } + else + { + vhostName = ""; + } + vhost = _appRegistry.getVirtualHostRegistry().getVirtualHost(vhostName); + + SecurityManager.setThreadPrincipal(conn.getAuthorizationID()); + + if(vhost != null) + { + sconn.setVirtualHost(vhost); + + if (!vhost.getSecurityManager().accessVirtualhost(vhostName, ((ProtocolEngine) sconn.getConfig()).getRemoteAddress())) + { + sconn.invoke(new ConnectionClose(ConnectionCloseCode.CONNECTION_FORCED, "Permission denied '"+vhostName+"'")); + sconn.setState(Connection.State.CLOSING); + } + else + { + sconn.invoke(new ConnectionOpenOk(Collections.emptyList())); + sconn.setState(Connection.State.OPEN); + } + } + else + { + sconn.invoke(new ConnectionClose(ConnectionCloseCode.INVALID_PATH, "Unknown virtualhost '"+vhostName+"'")); + sconn.setState(Connection.State.CLOSING); + } + + } + + @Override + protected int getHeartbeatMax() + { + //TODO: implement broker support for actually sending heartbeats + return 0; + } + + @Override + protected int getChannelMax() + { + return ApplicationRegistry.getInstance().getConfiguration().getMaxChannelCount(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ServerSession.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ServerSession.java new file mode 100644 index 0000000000..60c94b43c0 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ServerSession.java @@ -0,0 +1,678 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.transport; + +import static org.apache.qpid.server.logging.subjects.LogSubjectFormat.*; +import static org.apache.qpid.util.Serial.*; + +import java.lang.ref.WeakReference; +import java.security.Principal; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.protocol.ProtocolEngine; +import org.apache.qpid.server.configuration.ConfigStore; +import org.apache.qpid.server.configuration.ConfiguredObject; +import org.apache.qpid.server.configuration.ConnectionConfig; +import org.apache.qpid.server.configuration.SessionConfig; +import org.apache.qpid.server.configuration.SessionConfigType; +import org.apache.qpid.server.logging.LogActor; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.GenericActor; +import org.apache.qpid.server.logging.messages.ChannelMessages; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.protocol.AMQConnectionModel; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.security.PrincipalHolder; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.subscription.Subscription_0_10; +import org.apache.qpid.server.txn.AutoCommitTransaction; +import org.apache.qpid.server.txn.LocalTransaction; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.transport.Binary; +import org.apache.qpid.transport.Connection; +import org.apache.qpid.transport.MessageTransfer; +import org.apache.qpid.transport.Method; +import org.apache.qpid.transport.Range; +import org.apache.qpid.transport.RangeSet; +import org.apache.qpid.transport.Session; +import org.apache.qpid.transport.SessionDelegate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.sun.security.auth.UserPrincipal; + +public class ServerSession extends Session implements PrincipalHolder, SessionConfig, AMQSessionModel, LogSubject +{ + private static final Logger _logger = LoggerFactory.getLogger(ServerSession.class); + + private static final String NULL_DESTINTATION = UUID.randomUUID().toString(); + + private final UUID _id; + private ConnectionConfig _connectionConfig; + private long _createTime = System.currentTimeMillis(); + private LogActor _actor = GenericActor.getInstance(this); + + public static interface MessageDispositionChangeListener + { + public void onAccept(); + + public void onRelease(); + + public void onReject(); + + public boolean acquire(); + + + } + + public static interface Task + { + public void doTask(ServerSession session); + } + + + private final SortedMap<Integer, MessageDispositionChangeListener> _messageDispositionListenerMap = + new ConcurrentSkipListMap<Integer, MessageDispositionChangeListener>(); + + private ServerTransaction _transaction; + + private final AtomicLong _txnStarts = new AtomicLong(0); + private final AtomicLong _txnCommits = new AtomicLong(0); + private final AtomicLong _txnRejects = new AtomicLong(0); + private final AtomicLong _txnCount = new AtomicLong(0); + private final AtomicLong _txnUpdateTime = new AtomicLong(0); + + private Principal _principal; + + private Map<String, Subscription_0_10> _subscriptions = new ConcurrentHashMap<String, Subscription_0_10>(); + + private final List<Task> _taskList = new CopyOnWriteArrayList<Task>(); + + private final WeakReference<Session> _reference; + + ServerSession(Connection connection, SessionDelegate delegate, Binary name, long expiry) + { + this(connection, delegate, name, expiry, ((ServerConnection)connection).getConfig()); + } + + protected void setState(State state) + { + super.setState(state); + + if (state == State.OPEN) + { + _actor.message(ChannelMessages.CREATE()); + } + } + + public ServerSession(Connection connection, SessionDelegate delegate, Binary name, long expiry, ConnectionConfig connConfig) + { + super(connection, delegate, name, expiry); + _connectionConfig = connConfig; + _transaction = new AutoCommitTransaction(this.getMessageStore()); + _principal = new UserPrincipal(connection.getAuthorizationID()); + _reference = new WeakReference<Session>(this); + _id = getConfigStore().createId(); + getConfigStore().addConfiguredObject(this); + } + + private ConfigStore getConfigStore() + { + return getConnectionConfig().getConfigStore(); + } + + + @Override + protected boolean isFull(int id) + { + return isCommandsFull(id); + } + + public void enqueue(final ServerMessage message, final ArrayList<? extends BaseQueue> queues) + { + getConnectionModel().registerMessageReceived(message.getSize(), message.getArrivalTime()); + _transaction.enqueue(queues,message, new ServerTransaction.Action() + { + + BaseQueue[] _queues = queues.toArray(new BaseQueue[queues.size()]); + + public void postCommit() + { + for(int i = 0; i < _queues.length; i++) + { + try + { + _queues[i].enqueue(message); + } + catch (AMQException e) + { + // TODO + throw new RuntimeException(e); + } + } + } + + public void onRollback() + { + // NO-OP + } + }); + + incrementOutstandingTxnsIfNecessary(); + updateTransactionalActivity(); + } + + + public void sendMessage(MessageTransfer xfr, + Runnable postIdSettingAction) + { + invoke(xfr, postIdSettingAction); + getConnectionModel().registerMessageDelivered(xfr.getBodySize()); + } + + public void onMessageDispositionChange(MessageTransfer xfr, MessageDispositionChangeListener acceptListener) + { + _messageDispositionListenerMap.put(xfr.getId(), acceptListener); + } + + + private static interface MessageDispositionAction + { + void performAction(MessageDispositionChangeListener listener); + } + + public void accept(RangeSet ranges) + { + dispositionChange(ranges, new MessageDispositionAction() + { + public void performAction(MessageDispositionChangeListener listener) + { + listener.onAccept(); + } + }); + } + + + public void release(RangeSet ranges) + { + dispositionChange(ranges, new MessageDispositionAction() + { + public void performAction(MessageDispositionChangeListener listener) + { + listener.onRelease(); + } + }); + } + + public void reject(RangeSet ranges) + { + dispositionChange(ranges, new MessageDispositionAction() + { + public void performAction(MessageDispositionChangeListener listener) + { + listener.onReject(); + } + }); + } + + public RangeSet acquire(RangeSet transfers) + { + RangeSet acquired = new RangeSet(); + + if(!_messageDispositionListenerMap.isEmpty()) + { + Iterator<Integer> unacceptedMessages = _messageDispositionListenerMap.keySet().iterator(); + Iterator<Range> rangeIter = transfers.iterator(); + + if(rangeIter.hasNext()) + { + Range range = rangeIter.next(); + + while(range != null && unacceptedMessages.hasNext()) + { + int next = unacceptedMessages.next(); + while(gt(next, range.getUpper())) + { + if(rangeIter.hasNext()) + { + range = rangeIter.next(); + } + else + { + range = null; + break; + } + } + if(range != null && range.includes(next)) + { + MessageDispositionChangeListener changeListener = _messageDispositionListenerMap.get(next); + if(changeListener != null && changeListener.acquire()) + { + acquired.add(next); + } + } + + + } + + } + + + } + + return acquired; + } + + public void dispositionChange(RangeSet ranges, MessageDispositionAction action) + { + if(ranges != null && !_messageDispositionListenerMap.isEmpty()) + { + Iterator<Integer> unacceptedMessages = _messageDispositionListenerMap.keySet().iterator(); + Iterator<Range> rangeIter = ranges.iterator(); + + if(rangeIter.hasNext()) + { + Range range = rangeIter.next(); + + while(range != null && unacceptedMessages.hasNext()) + { + int next = unacceptedMessages.next(); + while(gt(next, range.getUpper())) + { + if(rangeIter.hasNext()) + { + range = rangeIter.next(); + } + else + { + range = null; + break; + } + } + if(range != null && range.includes(next)) + { + MessageDispositionChangeListener changeListener = _messageDispositionListenerMap.remove(next); + action.performAction(changeListener); + } + + + } + + } + + } + } + + public void removeDispositionListener(Method method) + { + _messageDispositionListenerMap.remove(method.getId()); + } + + public void onClose() + { + _transaction.rollback(); + for(MessageDispositionChangeListener listener : _messageDispositionListenerMap.values()) + { + listener.onRelease(); + } + _messageDispositionListenerMap.clear(); + + getConfigStore().removeConfiguredObject(this); + + for (Task task : _taskList) + { + task.doTask(this); + } + + CurrentActor.get().message(getLogSubject(), ChannelMessages.CLOSE()); + } + + @Override + protected void awaitClose() + { + // Broker shouldn't block awaiting close - thus do override this method to do nothing + } + + public void acknowledge(final Subscription_0_10 sub, final QueueEntry entry) + { + _transaction.dequeue(entry.getQueue(), entry.getMessage(), + new ServerTransaction.Action() + { + + public void postCommit() + { + sub.acknowledge(entry); + } + + public void onRollback() + { + entry.release(); + } + }); + updateTransactionalActivity(); + } + + public Collection<Subscription_0_10> getSubscriptions() + { + return _subscriptions.values(); + } + + public void register(String destination, Subscription_0_10 sub) + { + _subscriptions.put(destination == null ? NULL_DESTINTATION : destination, sub); + } + + public Subscription_0_10 getSubscription(String destination) + { + return _subscriptions.get(destination == null ? NULL_DESTINTATION : destination); + } + + public void unregister(Subscription_0_10 sub) + { + _subscriptions.remove(sub.getConsumerTag().toString()); + try + { + sub.getSendLock(); + AMQQueue queue = sub.getQueue(); + if(queue != null) + { + queue.unregisterSubscription(sub); + } + + } + catch (AMQException e) + { + // TODO + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + finally + { + sub.releaseSendLock(); + } + } + + public boolean isTransactional() + { + // this does not look great but there should only be one "non-transactional" + // transactional context, while there could be several transactional ones in + // theory + return !(_transaction instanceof AutoCommitTransaction); + } + + public boolean inTransaction() + { + return isTransactional() && _txnUpdateTime.get() > 0 && _transaction.getTransactionStartTime() > 0; + } + + public void selectTx() + { + _transaction = new LocalTransaction(this.getMessageStore()); + _txnStarts.incrementAndGet(); + } + + public void commit() + { + _transaction.commit(); + + _txnCommits.incrementAndGet(); + _txnStarts.incrementAndGet(); + decrementOutstandingTxnsIfNecessary(); + } + + public void rollback() + { + _transaction.rollback(); + + _txnRejects.incrementAndGet(); + _txnStarts.incrementAndGet(); + decrementOutstandingTxnsIfNecessary(); + } + + + private void incrementOutstandingTxnsIfNecessary() + { + if(isTransactional()) + { + //There can currently only be at most one outstanding transaction + //due to only having LocalTransaction support. Set value to 1 if 0. + _txnCount.compareAndSet(0,1); + } + } + + private void decrementOutstandingTxnsIfNecessary() + { + if(isTransactional()) + { + //There can currently only be at most one outstanding transaction + //due to only having LocalTransaction support. Set value to 0 if 1. + _txnCount.compareAndSet(1,0); + } + } + + /** + * Update last transaction activity timestamp + */ + public void updateTransactionalActivity() + { + if (isTransactional()) + { + _txnUpdateTime.set(System.currentTimeMillis()); + } + } + + public Long getTxnStarts() + { + return _txnStarts.get(); + } + + public Long getTxnCommits() + { + return _txnCommits.get(); + } + + public Long getTxnRejects() + { + return _txnRejects.get(); + } + + public Long getTxnCount() + { + return _txnCount.get(); + } + + public Principal getPrincipal() + { + return _principal; + } + + public void addSessionCloseTask(Task task) + { + _taskList.add(task); + } + + public void removeSessionCloseTask(Task task) + { + _taskList.remove(task); + } + + public WeakReference<Session> getReference() + { + return _reference; + } + + public MessageStore getMessageStore() + { + return getVirtualHost().getMessageStore(); + } + + public VirtualHost getVirtualHost() + { + return (VirtualHost) _connectionConfig.getVirtualHost(); + } + + public UUID getId() + { + return _id; + } + + public SessionConfigType getConfigType() + { + return SessionConfigType.getInstance(); + } + + public ConfiguredObject getParent() + { + return getVirtualHost(); + } + + public boolean isDurable() + { + return false; + } + + public boolean isAttached() + { + return true; + } + + public long getDetachedLifespan() + { + return 0; + } + + public Long getExpiryTime() + { + return null; + } + + public Long getMaxClientRate() + { + return null; + } + + public ConnectionConfig getConnectionConfig() + { + return _connectionConfig; + } + + public String getSessionName() + { + return getName().toString(); + } + + public long getCreateTime() + { + return _createTime; + } + + public void mgmtClose() + { + close(); + } + + public Object getID() + { + return getName(); + } + + public AMQConnectionModel getConnectionModel() + { + return (ServerConnection) getConnection(); + } + + public String getClientID() + { + return getConnection().getClientId(); + } + + public LogActor getLogActor() + { + return _actor; + } + + public LogSubject getLogSubject() + { + return (LogSubject) this; + } + + public void checkTransactionStatus(long openWarn, long openClose, long idleWarn, long idleClose) throws AMQException + { + if (inTransaction()) + { + long currentTime = System.currentTimeMillis(); + long openTime = currentTime - _transaction.getTransactionStartTime(); + long idleTime = currentTime - _txnUpdateTime.get(); + + // Log a warning on idle or open transactions + if (idleWarn > 0L && idleTime > idleWarn) + { + CurrentActor.get().message(getLogSubject(), ChannelMessages.IDLE_TXN(openTime)); + _logger.warn("IDLE TRANSACTION ALERT " + getLogSubject().toString() + " " + idleTime + " ms"); + } + else if (openWarn > 0L && openTime > openWarn) + { + CurrentActor.get().message(getLogSubject(), ChannelMessages.OPEN_TXN(openTime)); + _logger.warn("OPEN TRANSACTION ALERT " + getLogSubject().toString() + " " + openTime + " ms"); + } + + // Close connection for idle or open transactions that have timed out + if (idleClose > 0L && idleTime > idleClose) + { + getConnectionModel().closeSession(this, AMQConstant.RESOURCE_ERROR, "Idle transaction timed out"); + } + else if (openClose > 0L && openTime > openClose) + { + getConnectionModel().closeSession(this, AMQConstant.RESOURCE_ERROR, "Open transaction timed out"); + } + } + } + + @Override + public String toLogString() + { + return "[" + + MessageFormat.format(CHANNEL_FORMAT, + getConnection().getConnectionId(), + getClientID(), + ((ProtocolEngine) _connectionConfig).getRemoteAddress().toString(), + getVirtualHost().getName(), + getChannel()) + + "] "; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ServerSessionDelegate.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ServerSessionDelegate.java new file mode 100644 index 0000000000..be659c87ae --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/transport/ServerSessionDelegate.java @@ -0,0 +1,1244 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.transport; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQUnknownExchangeType; +import org.apache.qpid.common.AMQPFilterTypes; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.exchange.*; +import org.apache.qpid.server.filter.FilterManager; +import org.apache.qpid.server.filter.FilterManagerFactory; +import org.apache.qpid.server.flow.FlowCreditManager_0_10; +import org.apache.qpid.server.flow.WindowCreditManager; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.actors.GenericActor; +import org.apache.qpid.server.message.MessageMetaData_0_10; +import org.apache.qpid.server.message.MessageTransferMessage; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.store.DurableConfigurationStore; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.subscription.Subscription_0_10; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.transport.Acquired; +import org.apache.qpid.transport.DeliveryProperties; +import org.apache.qpid.transport.ExchangeBind; +import org.apache.qpid.transport.ExchangeBound; +import org.apache.qpid.transport.ExchangeBoundResult; +import org.apache.qpid.transport.ExchangeDeclare; +import org.apache.qpid.transport.ExchangeDelete; +import org.apache.qpid.transport.ExchangeQuery; +import org.apache.qpid.transport.ExchangeQueryResult; +import org.apache.qpid.transport.ExchangeUnbind; +import org.apache.qpid.transport.ExecutionErrorCode; +import org.apache.qpid.transport.ExecutionException; +import org.apache.qpid.transport.MessageAccept; +import org.apache.qpid.transport.MessageAcceptMode; +import org.apache.qpid.transport.MessageAcquire; +import org.apache.qpid.transport.MessageAcquireMode; +import org.apache.qpid.transport.MessageCancel; +import org.apache.qpid.transport.MessageFlow; +import org.apache.qpid.transport.MessageFlowMode; +import org.apache.qpid.transport.MessageFlush; +import org.apache.qpid.transport.MessageReject; +import org.apache.qpid.transport.MessageRejectCode; +import org.apache.qpid.transport.MessageRelease; +import org.apache.qpid.transport.MessageResume; +import org.apache.qpid.transport.MessageSetFlowMode; +import org.apache.qpid.transport.MessageStop; +import org.apache.qpid.transport.MessageSubscribe; +import org.apache.qpid.transport.MessageTransfer; +import org.apache.qpid.transport.Method; +import org.apache.qpid.transport.QueueDeclare; +import org.apache.qpid.transport.QueueDelete; +import org.apache.qpid.transport.QueuePurge; +import org.apache.qpid.transport.QueueQuery; +import org.apache.qpid.transport.QueueQueryResult; +import org.apache.qpid.transport.RangeSet; +import org.apache.qpid.transport.Session; +import org.apache.qpid.transport.SessionDelegate; +import org.apache.qpid.transport.TxCommit; +import org.apache.qpid.transport.TxRollback; +import org.apache.qpid.transport.TxSelect; + +public class ServerSessionDelegate extends SessionDelegate +{ + private final IApplicationRegistry _appRegistry; + + public ServerSessionDelegate(IApplicationRegistry appRegistry) + { + _appRegistry = appRegistry; + } + + @Override + public void command(Session session, Method method) + { + SecurityManager.setThreadPrincipal(session.getConnection().getAuthorizationID()); + + if(!session.isClosing()) + { + super.command(session, method); + if (method.isSync()) + { + session.flushProcessed(); + } + } + } + + @Override + public void messageAccept(Session session, MessageAccept method) + { + ((ServerSession)session).accept(method.getTransfers()); + } + + + + @Override + public void messageReject(Session session, MessageReject method) + { + ((ServerSession)session).reject(method.getTransfers()); + } + + @Override + public void messageRelease(Session session, MessageRelease method) + { + ((ServerSession)session).release(method.getTransfers()); + } + + @Override + public void messageAcquire(Session session, MessageAcquire method) + { + RangeSet acquiredRanges = ((ServerSession)session).acquire(method.getTransfers()); + + Acquired result = new Acquired(acquiredRanges); + + + session.executionResult((int) method.getId(), result); + + + } + + @Override + public void messageResume(Session session, MessageResume method) + { + super.messageResume(session, method); + } + + @Override + public void messageSubscribe(Session session, MessageSubscribe method) + { + + //TODO - work around broken Python tests + if(!method.hasAcceptMode()) + { + method.setAcceptMode(MessageAcceptMode.EXPLICIT); + } + if(!method.hasAcquireMode()) + { + method.setAcquireMode(MessageAcquireMode.PRE_ACQUIRED); + + } + + /* if(!method.hasAcceptMode()) + { + exception(session,method,ExecutionErrorCode.ILLEGAL_ARGUMENT, "Accept-mode not supplied"); + } + else if(!method.hasAcquireMode()) + { + exception(session,method,ExecutionErrorCode.ILLEGAL_ARGUMENT, "Acquire-mode not supplied"); + } + else */if(!method.hasQueue()) + { + exception(session,method,ExecutionErrorCode.ILLEGAL_ARGUMENT, "queue not supplied"); + } + else + { + String destination = method.getDestination(); + + if(((ServerSession)session).getSubscription(destination)!=null) + { + exception(session, method, ExecutionErrorCode.NOT_ALLOWED, "Subscription already exists with destaination: '"+destination+"'"); + } + else + { + String queueName = method.getQueue(); + QueueRegistry queueRegistry = getQueueRegistry(session); + + + final AMQQueue queue = queueRegistry.getQueue(queueName); + + if(queue == null) + { + exception(session,method,ExecutionErrorCode.NOT_FOUND, "Queue: " + queueName + " not found"); + } + else if(queue.getPrincipalHolder() != null && queue.getPrincipalHolder() != session) + { + exception(session,method,ExecutionErrorCode.RESOURCE_LOCKED, "Exclusive Queue: " + queueName + " owned exclusively by another session"); + } + else + { + if(queue.isExclusive()) + { + ServerSession s = (ServerSession) session; + queue.setExclusiveOwningSession(s); + if(queue.getPrincipalHolder() == null) + { + queue.setPrincipalHolder(s); + queue.setExclusiveOwningSession(s); + ((ServerSession) session).addSessionCloseTask(new ServerSession.Task() + { + public void doTask(ServerSession session) + { + if(queue.getPrincipalHolder() == session) + { + queue.setPrincipalHolder(null); + queue.setExclusiveOwningSession(null); + } + } + }); + } + + } + + FlowCreditManager_0_10 creditManager = new WindowCreditManager(0L,0L); + + FilterManager filterManager = null; + try + { + filterManager = FilterManagerFactory.createManager(method.getArguments()); + } + catch (AMQException amqe) + { + exception(session, method, ExecutionErrorCode.ILLEGAL_ARGUMENT, "Exception Creating FilterManager"); + return; + } + + Subscription_0_10 sub = new Subscription_0_10((ServerSession)session, + destination, + method.getAcceptMode(), + method.getAcquireMode(), + MessageFlowMode.WINDOW, + creditManager, + filterManager, + method.getArguments()); + + ((ServerSession)session).register(destination, sub); + try + { + queue.registerSubscription(sub, method.getExclusive()); + } + catch (AMQQueue.ExistingExclusiveSubscription existing) + { + exception(session, method, ExecutionErrorCode.RESOURCE_LOCKED, "Queue has an exclusive consumer"); + } + catch (AMQQueue.ExistingSubscriptionPreventsExclusive exclusive) + { + exception(session, method, ExecutionErrorCode.RESOURCE_LOCKED, "Queue has an existing consumer - can't subscribe exclusively"); + } + catch (AMQException e) + { + exception(session, method, e, "Cannot subscribe to '" + destination); + } + } + } + } + } + + + @Override + public void messageTransfer(Session ssn, MessageTransfer xfr) + { + ExchangeRegistry exchangeRegistry = getExchangeRegistry(ssn); + Exchange exchange; + if(xfr.hasDestination()) + { + exchange = exchangeRegistry.getExchange(xfr.getDestination()); + if(exchange == null) + { + exchange = exchangeRegistry.getDefaultExchange(); + } + } + else + { + exchange = exchangeRegistry.getDefaultExchange(); + } + + + DeliveryProperties delvProps = null; + if(xfr.getHeader() != null && (delvProps = xfr.getHeader().get(DeliveryProperties.class)) != null && delvProps.hasTtl() && !delvProps.hasExpiration()) + { + delvProps.setExpiration(System.currentTimeMillis() + delvProps.getTtl()); + } + + MessageMetaData_0_10 messageMetaData = new MessageMetaData_0_10(xfr); + + if (!getVirtualHost(ssn).getSecurityManager().authorisePublish(messageMetaData.isImmediate(), messageMetaData.getRoutingKey(), exchange.getName())) + { + ExecutionErrorCode errorCode = ExecutionErrorCode.UNAUTHORIZED_ACCESS; + String description = "Permission denied: exchange-name '" + exchange.getName() + "'"; + exception(ssn, xfr, errorCode, description); + + return; + } + + final MessageStore store = getVirtualHost(ssn).getMessageStore(); + StoredMessage<MessageMetaData_0_10> storeMessage = store.addMessage(messageMetaData); + ByteBuffer body = xfr.getBody(); + if(body != null) + { + storeMessage.addContent(0, body); + } + storeMessage.flushToStore(); + MessageTransferMessage message = new MessageTransferMessage(storeMessage, ((ServerSession)ssn).getReference()); + + ArrayList<? extends BaseQueue> queues = exchange.route(message); + + + + if(queues != null && queues.size() != 0) + { + ((ServerSession) ssn).enqueue(message, queues); + } + else + { + if(delvProps == null || !delvProps.hasDiscardUnroutable() || !delvProps.getDiscardUnroutable()) + { + if(xfr.getAcceptMode() == MessageAcceptMode.EXPLICIT) + { + RangeSet rejects = new RangeSet(); + rejects.add(xfr.getId()); + MessageReject reject = new MessageReject(rejects, MessageRejectCode.UNROUTABLE, "Unroutable"); + ssn.invoke(reject); + } + else + { + Exchange alternate = exchange.getAlternateExchange(); + if(alternate != null) + { + queues = alternate.route(message); + if(queues != null && queues.size() != 0) + { + ((ServerSession) ssn).enqueue(message, queues); + } + else + { + //TODO - log the message discard + } + } + else + { + //TODO - log the message discard + } + + + } + } + + + } + + ssn.processed(xfr); + } + + @Override + public void messageCancel(Session session, MessageCancel method) + { + String destination = method.getDestination(); + + Subscription_0_10 sub = ((ServerSession)session).getSubscription(destination); + + if(sub == null) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, "not-found: destination '"+destination+"'"); + } + else + { + AMQQueue queue = sub.getQueue(); + ((ServerSession)session).unregister(sub); + if(!queue.isDeleted() && queue.isExclusive() && queue.getConsumerCount() == 0) + { + queue.setPrincipalHolder(null); + } + } + } + + @Override + public void messageFlush(Session session, MessageFlush method) + { + String destination = method.getDestination(); + + Subscription_0_10 sub = ((ServerSession)session).getSubscription(destination); + + if(sub == null) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, "not-found: destination '"+destination+"'"); + } + else + { + + try + { + sub.flush(); + } + catch (AMQException e) + { + exception(session, method, e, "Cannot flush subscription '" + destination); + } + } + } + + @Override + public void txSelect(Session session, TxSelect method) + { + // TODO - check current tx mode + ((ServerSession)session).selectTx(); + } + + @Override + public void txCommit(Session session, TxCommit method) + { + // TODO - check current tx mode + ((ServerSession)session).commit(); + } + + @Override + public void txRollback(Session session, TxRollback method) + { + // TODO - check current tx mode + ((ServerSession)session).rollback(); + } + + + @Override + public void exchangeDeclare(Session session, ExchangeDeclare method) + { + String exchangeName = method.getExchange(); + VirtualHost virtualHost = getVirtualHost(session); + Exchange exchange = getExchange(session, exchangeName); + + if(method.getPassive()) + { + if(exchange == null) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, "not-found: exchange-name '"+exchangeName+"'"); + + } + else + { + // TODO - check exchange has same properties + if(!exchange.getTypeShortString().toString().equals(method.getType())) + { + exception(session, method, ExecutionErrorCode.NOT_ALLOWED, "Cannot redeclare with a different exchange type"); + } + } + + } + else + { + if (exchange == null) + { + ExchangeRegistry exchangeRegistry = getExchangeRegistry(session); + ExchangeFactory exchangeFactory = virtualHost.getExchangeFactory(); + + + + try + { + + exchange = exchangeFactory.createExchange(method.getExchange(), + method.getType(), + method.getDurable(), + method.getAutoDelete()); + + String alternateExchangeName = method.getAlternateExchange(); + if(alternateExchangeName != null && alternateExchangeName.length() != 0) + { + Exchange alternate = getExchange(session, alternateExchangeName); + exchange.setAlternateExchange(alternate); + } + + if (exchange.isDurable()) + { + DurableConfigurationStore store = virtualHost.getDurableConfigurationStore(); + store.createExchange(exchange); + } + + exchangeRegistry.registerExchange(exchange); + } + catch(AMQUnknownExchangeType e) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, "Unknown Exchange Type: " + method.getType()); + } + catch (AMQException e) + { + exception(session, method, e, "Cannot declare exchange '" + exchangeName); + } + } + else + { + if(!exchange.getTypeShortString().toString().equals(method.getType())) + { + exception(session, method, ExecutionErrorCode.NOT_ALLOWED, "Cannot redeclare with a different exchange type"); + } + } + + } + } + + // TODO decouple AMQException and AMQConstant error codes + private void exception(Session session, Method method, AMQException exception, String message) + { + ExecutionErrorCode errorCode = ExecutionErrorCode.INTERNAL_ERROR; + if (exception.getErrorCode() != null) + { + try + { + errorCode = ExecutionErrorCode.get(exception.getErrorCode().getCode()); + } + catch (IllegalArgumentException iae) + { + // ignore, already set to INTERNAL_ERROR + } + } + String description = message + "': " + exception.getMessage(); + + exception(session, method, errorCode, description); + } + + private void exception(Session session, Method method, ExecutionErrorCode errorCode, String description) + { + ExecutionException ex = new ExecutionException(); + ex.setErrorCode(errorCode); + ex.setCommandId(method.getId()); + ex.setDescription(description); + + session.invoke(ex); + + session.close(); + } + + private Exchange getExchange(Session session, String exchangeName) + { + ExchangeRegistry exchangeRegistry = getExchangeRegistry(session); + return exchangeRegistry.getExchange(exchangeName); + } + + private ExchangeRegistry getExchangeRegistry(Session session) + { + VirtualHost virtualHost = getVirtualHost(session); + return virtualHost.getExchangeRegistry(); + + } + + private VirtualHost getVirtualHost(Session session) + { + ServerConnection conn = getServerConnection(session); + VirtualHost vhost = conn.getVirtualHost(); + return vhost; + } + + private ServerConnection getServerConnection(Session session) + { + ServerConnection conn = (ServerConnection) session.getConnection(); + return conn; + } + + @Override + public void exchangeDelete(Session session, ExchangeDelete method) + { + VirtualHost virtualHost = getVirtualHost(session); + ExchangeRegistry exchangeRegistry = virtualHost.getExchangeRegistry(); + + try + { + Exchange exchange = getExchange(session, method.getExchange()); + + if(exchange == null) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, "No such exchange '" + method.getExchange() + "'"); + } + else if(exchange.hasReferrers()) + { + exception(session, method, ExecutionErrorCode.NOT_ALLOWED, "Exchange in use as an alternate exchange"); + } + else if(isStandardExchange(exchange, virtualHost.getExchangeFactory().getRegisteredTypes())) + { + exception(session, method, ExecutionErrorCode.NOT_ALLOWED, "Exchange '"+method.getExchange()+"' cannot be deleted"); + } + else + { + exchangeRegistry.unregisterExchange(method.getExchange(), method.getIfUnused()); + + if (exchange.isDurable() && !exchange.isAutoDelete()) + { + DurableConfigurationStore store = virtualHost.getDurableConfigurationStore(); + store.removeExchange(exchange); + } + } + } + catch (ExchangeInUseException e) + { + exception(session, method, ExecutionErrorCode.PRECONDITION_FAILED, "Exchange in use"); + } + catch (AMQException e) + { + exception(session, method, e, "Cannot delete exchange '" + method.getExchange() ); + } + } + + private boolean isStandardExchange(Exchange exchange, Collection<ExchangeType<? extends Exchange>> registeredTypes) + { + for(ExchangeType type : registeredTypes) + { + if(type.getDefaultExchangeName().toString().equals( exchange.getName() )) + { + return true; + } + } + return false; + } + + @Override + public void exchangeQuery(Session session, ExchangeQuery method) + { + + ExchangeQueryResult result = new ExchangeQueryResult(); + + Exchange exchange = getExchange(session, method.getName()); + + if(exchange != null) + { + result.setDurable(exchange.isDurable()); + result.setType(exchange.getTypeShortString().toString()); + result.setNotFound(false); + } + else + { + result.setNotFound(true); + } + + session.executionResult((int) method.getId(), result); + } + + @Override + public void exchangeBind(Session session, ExchangeBind method) + { + + VirtualHost virtualHost = getVirtualHost(session); + ExchangeRegistry exchangeRegistry = virtualHost.getExchangeRegistry(); + QueueRegistry queueRegistry = virtualHost.getQueueRegistry(); + + if (!method.hasQueue()) + { + exception(session, method, ExecutionErrorCode.ILLEGAL_ARGUMENT, "queue not set"); + } + else if (!method.hasExchange()) + { + exception(session, method, ExecutionErrorCode.ILLEGAL_ARGUMENT, "exchange not set"); + } +/* + else if (!method.hasBindingKey()) + { + exception(session, method, ExecutionErrorCode.ILLEGAL_ARGUMENT, "binding-key not set"); + } +*/ + else + { + //TODO - here because of non-compiant python tests + if (!method.hasBindingKey()) + { + method.setBindingKey(method.getQueue()); + } + AMQQueue queue = queueRegistry.getQueue(method.getQueue()); + Exchange exchange = exchangeRegistry.getExchange(method.getExchange()); + if(queue == null) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, "Queue: '" + method.getQueue() + "' not found"); + } + else if(exchange == null) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, "Exchange: '" + method.getExchange() + "' not found"); + } + else if(exchange.getTypeShortString().equals(HeadersExchange.TYPE.getName()) && (!method.hasArguments() || method.getArguments() == null || !method.getArguments().containsKey("x-match"))) + { + exception(session, method, ExecutionErrorCode.INTERNAL_ERROR, "Bindings to an exchange of type " + HeadersExchange.TYPE.getName() + " require an x-match header"); + } + else + { + AMQShortString routingKey = new AMQShortString(method.getBindingKey()); + FieldTable fieldTable = FieldTable.convertToFieldTable(method.getArguments()); + + if (!exchange.isBound(routingKey, fieldTable, queue)) + { + try + { + virtualHost.getBindingFactory().addBinding(method.getBindingKey(), queue, exchange, method.getArguments()); + } + catch (AMQException e) + { + exception(session, method, e, "Cannot add binding '" + method.getBindingKey()); + } + } + else + { + // todo + } + } + + + } + + + + } + + @Override + public void exchangeUnbind(Session session, ExchangeUnbind method) + { + VirtualHost virtualHost = getVirtualHost(session); + ExchangeRegistry exchangeRegistry = virtualHost.getExchangeRegistry(); + QueueRegistry queueRegistry = virtualHost.getQueueRegistry(); + + if (!method.hasQueue()) + { + exception(session, method, ExecutionErrorCode.ILLEGAL_ARGUMENT, "queue not set"); + } + else if (!method.hasExchange()) + { + exception(session, method, ExecutionErrorCode.ILLEGAL_ARGUMENT, "exchange not set"); + } + else if (!method.hasBindingKey()) + { + exception(session, method, ExecutionErrorCode.ILLEGAL_ARGUMENT, "binding-key not set"); + } + else + { + AMQQueue queue = queueRegistry.getQueue(method.getQueue()); + Exchange exchange = exchangeRegistry.getExchange(method.getExchange()); + if(queue == null) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, "Queue: '" + method.getQueue() + "' not found"); + } + else if(exchange == null) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, "Exchange: '" + method.getExchange() + "' not found"); + } + else + { + try + { + virtualHost.getBindingFactory().removeBinding(method.getBindingKey(), queue, exchange, null); + } + catch (AMQException e) + { + exception(session, method, e, "Cannot remove binding '" + method.getBindingKey()); + } + } + } + + + super.exchangeUnbind(session, method); + } + + @Override + public void exchangeBound(Session session, ExchangeBound method) + { + + ExchangeBoundResult result = new ExchangeBoundResult(); + Exchange exchange; + AMQQueue queue; + if(method.hasExchange()) + { + exchange = getExchange(session, method.getExchange()); + + if(exchange == null) + { + result.setExchangeNotFound(true); + } + } + else + { + exchange = getExchangeRegistry(session).getDefaultExchange(); + } + + + if(method.hasQueue()) + { + + queue = getQueue(session, method.getQueue()); + if(queue == null) + { + result.setQueueNotFound(true); + } + + + if(exchange != null && queue != null) + { + + boolean queueMatched = exchange.isBound(queue); + + result.setQueueNotMatched(!queueMatched); + + + if(method.hasBindingKey()) + { + + if(method.hasArguments()) + { + FieldTable args = FieldTable.convertToFieldTable(method.getArguments()); + + result.setArgsNotMatched(!exchange.isBound(new AMQShortString(method.getBindingKey()), args, queue)); + } + if(queueMatched) + { + result.setKeyNotMatched(!exchange.isBound(method.getBindingKey(), queue)); + } + else + { + result.setKeyNotMatched(!exchange.isBound(method.getBindingKey())); + } + } + else if (method.hasArguments()) + { + // TODO + + } + + result.setQueueNotMatched(!exchange.isBound(queue)); + + } + else if(exchange != null && method.hasBindingKey()) + { + if(method.hasArguments()) + { + // TODO + } + result.setKeyNotMatched(!exchange.isBound(method.getBindingKey())); + + } + + } + else if(exchange != null && method.hasBindingKey()) + { + if(method.hasArguments()) + { + // TODO + } + result.setKeyNotMatched(!exchange.isBound(method.getBindingKey())); + + } + + + session.executionResult((int) method.getId(), result); + + + } + + private AMQQueue getQueue(Session session, String queue) + { + QueueRegistry queueRegistry = getQueueRegistry(session); + return queueRegistry.getQueue(queue); + } + + private QueueRegistry getQueueRegistry(Session session) + { + return getVirtualHost(session).getQueueRegistry(); + } + + @Override + public void queueDeclare(Session session, final QueueDeclare method) + { + + VirtualHost virtualHost = getVirtualHost(session); + DurableConfigurationStore store = virtualHost.getDurableConfigurationStore(); + + String queueName = method.getQueue(); + AMQQueue queue; + QueueRegistry queueRegistry = getQueueRegistry(session); + //TODO: do we need to check that the queue already exists with exactly the same "configuration"? + + synchronized (queueRegistry) + { + + if (((queue = queueRegistry.getQueue(queueName)) == null)) + { + + if (method.getPassive()) + { + String description = "Queue: " + queueName + " not found on VirtualHost(" + virtualHost + ")."; + ExecutionErrorCode errorCode = ExecutionErrorCode.NOT_FOUND; + + exception(session, method, errorCode, description); + + return; + } + else + { + try + { + queue = createQueue(queueName, method, virtualHost, (ServerSession)session); + if(method.getExclusive()) + { + queue.setExclusive(true); + } + else if(method.getAutoDelete()) + { + queue.setDeleteOnNoConsumers(true); + } + + final String alternateExchangeName = method.getAlternateExchange(); + if(alternateExchangeName != null && alternateExchangeName.length() != 0) + { + Exchange alternate = getExchange(session, alternateExchangeName); + queue.setAlternateExchange(alternate); + } + + if(method.hasArguments() && method.getArguments() != null) + { + if(method.getArguments().containsKey("no-local")) + { + Object no_local = method.getArguments().get("no-local"); + if(no_local instanceof Boolean && ((Boolean)no_local)) + { + queue.setNoLocal(true); + } + } + } + + + if (queue.isDurable() && !queue.isAutoDelete()) + { + if(method.hasArguments() && method.getArguments() != null) + { + Map<String,Object> args = method.getArguments(); + FieldTable ftArgs = new FieldTable(); + for(Map.Entry<String, Object> entry : args.entrySet()) + { + ftArgs.put(new AMQShortString(entry.getKey()), entry.getValue()); + } + store.createQueue(queue, ftArgs); + } + else + { + store.createQueue(queue); + } + } + queueRegistry.registerQueue(queue); + boolean autoRegister = ApplicationRegistry.getInstance().getConfiguration().getQueueAutoRegister(); + + if (autoRegister) + { + + ExchangeRegistry exchangeRegistry = getExchangeRegistry(session); + + Exchange defaultExchange = exchangeRegistry.getDefaultExchange(); + + virtualHost.getBindingFactory().addBinding(queueName, queue, defaultExchange, null); + + } + + if (method.hasAutoDelete() + && method.getAutoDelete() + && method.hasExclusive() + && method.getExclusive()) + { + final AMQQueue q = queue; + final ServerSession.Task deleteQueueTask = new ServerSession.Task() + { + public void doTask(ServerSession session) + { + try + { + q.delete(); + } + catch (AMQException e) + { + exception(session, method, e, "Cannot delete '" + method.getQueue()); + } + } + }; + final ServerSession s = (ServerSession) session; + s.addSessionCloseTask(deleteQueueTask); + queue.addQueueDeleteTask(new AMQQueue.Task() + { + public void doTask(AMQQueue queue) throws AMQException + { + s.removeSessionCloseTask(deleteQueueTask); + } + }); + } + if (method.hasExclusive() + && method.getExclusive()) + { + final AMQQueue q = queue; + final ServerSession.Task removeExclusive = new ServerSession.Task() + { + public void doTask(ServerSession session) + { + q.setPrincipalHolder(null); + q.setExclusiveOwningSession(null); + } + }; + final ServerSession s = (ServerSession) session; + q.setExclusiveOwningSession(s); + s.addSessionCloseTask(removeExclusive); + queue.addQueueDeleteTask(new AMQQueue.Task() + { + public void doTask(AMQQueue queue) throws AMQException + { + s.removeSessionCloseTask(removeExclusive); + } + }); + } + } + catch (AMQException e) + { + exception(session, method, e, "Cannot declare queue '" + queueName); + } + } + } + else if (method.getExclusive() && (queue.getExclusiveOwningSession() != null && !queue.getExclusiveOwningSession().equals(session))) + { + String description = "Cannot declare queue('" + queueName + "')," + + " as exclusive queue with same name " + + "declared on another session"; + ExecutionErrorCode errorCode = ExecutionErrorCode.RESOURCE_LOCKED; + + exception(session, method, errorCode, description); + + return; + } + } + } + + protected AMQQueue createQueue(final String queueName, + final QueueDeclare body, + VirtualHost virtualHost, + final ServerSession session) + throws AMQException + { + String owner = body.getExclusive() ? session.getClientID() : null; + + final AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(queueName, body.getDurable(), owner, body.getAutoDelete(), + body.getExclusive(), virtualHost, body.getArguments()); + + return queue; + } + + @Override + public void queueDelete(Session session, QueueDelete method) + { + String queueName = method.getQueue(); + if(queueName == null || queueName.length()==0) + { + exception(session, method, ExecutionErrorCode.INVALID_ARGUMENT, "No queue name supplied"); + + } + else + { + AMQQueue queue = getQueue(session, queueName); + + + if (queue == null) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, "No queue " + queueName + " found"); + } + else + { + if(queue.getPrincipalHolder() != null && queue.getPrincipalHolder() != session) + { + exception(session,method,ExecutionErrorCode.RESOURCE_LOCKED, "Exclusive Queue: " + queueName + " owned exclusively by another session"); + } + else if (method.getIfEmpty() && !queue.isEmpty()) + { + exception(session, method, ExecutionErrorCode.PRECONDITION_FAILED, "Queue " + queueName + " not empty"); + } + else if (method.getIfUnused() && !queue.isUnused()) + { + // TODO - Error code + exception(session, method, ExecutionErrorCode.PRECONDITION_FAILED, "Queue " + queueName + " in use"); + + } + else + { + VirtualHost virtualHost = getVirtualHost(session); + + try + { + queue.delete(); + if (queue.isDurable() && !queue.isAutoDelete()) + { + DurableConfigurationStore store = virtualHost.getDurableConfigurationStore(); + store.removeQueue(queue); + } + } + catch (AMQException e) + { + exception(session, method, e, "Cannot delete queue '" + queueName); + } + } + } + } + } + + @Override + public void queuePurge(Session session, QueuePurge method) + { + String queueName = method.getQueue(); + if(queueName == null || queueName.length()==0) + { + exception(session, method, ExecutionErrorCode.ILLEGAL_ARGUMENT, "No queue name supplied"); + } + else + { + AMQQueue queue = getQueue(session, queueName); + + if (queue == null) + { + exception(session, method, ExecutionErrorCode.NOT_FOUND, "No queue " + queueName + " found"); + } + else + { + try + { + queue.clearQueue(); + } + catch (AMQException e) + { + exception(session, method, e, "Cannot purge queue '" + queueName); + } + } + } + } + + @Override + public void queueQuery(Session session, QueueQuery method) + { + QueueQueryResult result = new QueueQueryResult(); + + AMQQueue queue = getQueue(session, method.getQueue()); + + if(queue != null) + { + result.setQueue(queue.getNameShortString().toString()); + result.setDurable(queue.isDurable()); + result.setExclusive(queue.isExclusive()); + result.setAutoDelete(queue.isAutoDelete()); + result.setArguments(queue.getArguments()); + result.setMessageCount(queue.getMessageCount()); + result.setSubscriberCount(queue.getConsumerCount()); + + } + + + session.executionResult((int) method.getId(), result); + + } + + @Override + public void messageSetFlowMode(Session session, MessageSetFlowMode sfm) + { + String destination = sfm.getDestination(); + + Subscription_0_10 sub = ((ServerSession)session).getSubscription(destination); + + if(sub == null) + { + exception(session, sfm, ExecutionErrorCode.NOT_FOUND, "not-found: destination '"+destination+"'"); + } + else if(sub.isStopped()) + { + sub.setFlowMode(sfm.getFlowMode()); + } + } + + @Override + public void messageStop(Session session, MessageStop stop) + { + String destination = stop.getDestination(); + + Subscription_0_10 sub = ((ServerSession)session).getSubscription(destination); + + if(sub == null) + { + exception(session, stop, ExecutionErrorCode.NOT_FOUND, "not-found: destination '"+destination+"'"); + } + else + { + sub.stop(); + } + + } + + @Override + public void messageFlow(Session session, MessageFlow flow) + { + String destination = flow.getDestination(); + + Subscription_0_10 sub = ((ServerSession)session).getSubscription(destination); + + if(sub == null) + { + exception(session, flow, ExecutionErrorCode.NOT_FOUND, "not-found: destination '"+destination+"'"); + } + else + { + sub.addCredit(flow.getUnit(), flow.getValue()); + } + + } + + @Override + public void closed(Session session) + { + for(Subscription_0_10 sub : getSubscriptions(session)) + { + ((ServerSession)session).unregister(sub); + } + ((ServerSession)session).onClose(); + } + + @Override + public void detached(Session session) + { + closed(session); + } + + public Collection<Subscription_0_10> getSubscriptions(Session session) + { + return ((ServerSession)session).getSubscriptions(); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/AutoCommitTransaction.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/AutoCommitTransaction.java new file mode 100755 index 0000000000..36e9d78440 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/AutoCommitTransaction.java @@ -0,0 +1,258 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.txn; + +import java.util.Collection; +import java.util.List; + +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.server.message.EnqueableMessage; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.store.TransactionLog; + +/** + * An implementation of ServerTransaction where each enqueue/dequeue + * operation takes place within it own transaction. + * + * Since there is no long-lived transaction, the commit and rollback methods of + * this implementation are empty. + */ +public class AutoCommitTransaction implements ServerTransaction +{ + protected static final Logger _logger = Logger.getLogger(AutoCommitTransaction.class); + + private final TransactionLog _transactionLog; + + public AutoCommitTransaction(TransactionLog transactionLog) + { + _transactionLog = transactionLog; + } + + public long getTransactionStartTime() + { + return 0L; + } + + /** + * Since AutoCommitTransaction have no concept of a long lived transaction, any Actions registered + * by the caller are executed immediately. + */ + public void addPostTransactionAction(Action immediateAction) + { + immediateAction.postCommit(); + } + + public void dequeue(BaseQueue queue, EnqueableMessage message, Action postTransactionAction) + { + TransactionLog.Transaction txn = null; + try + { + if(message.isPersistent() && queue.isDurable()) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Dequeue of message number " + message.getMessageNumber() + " from transaction log. Queue : " + queue.getNameShortString()); + } + + txn = _transactionLog.newTransaction(); + txn.dequeueMessage(queue, message.getMessageNumber()); + txn.commitTran(); + txn = null; + } + postTransactionAction.postCommit(); + postTransactionAction = null; + } + catch (AMQException e) + { + _logger.error("Error during message dequeue", e); + throw new RuntimeException("Error during message dequeue", e); + } + finally + { + rollbackIfNecessary(postTransactionAction, txn); + } + + } + + public void dequeue(Collection<QueueEntry> queueEntries, Action postTransactionAction) + { + TransactionLog.Transaction txn = null; + try + { + for(QueueEntry entry : queueEntries) + { + ServerMessage message = entry.getMessage(); + BaseQueue queue = entry.getQueue(); + + if(message.isPersistent() && queue.isDurable()) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Dequeue of message number " + message.getMessageNumber() + " from transaction log. Queue : " + queue.getNameShortString()); + } + + if(txn == null) + { + txn = _transactionLog.newTransaction(); + } + + txn.dequeueMessage(queue, message.getMessageNumber()); + } + + } + if(txn != null) + { + txn.commitTran(); + txn = null; + } + postTransactionAction.postCommit(); + postTransactionAction = null; + } + catch (AMQException e) + { + _logger.error("Error during message dequeues", e); + throw new RuntimeException("Error during message dequeues", e); + } + finally + { + rollbackIfNecessary(postTransactionAction, txn); + } + + } + + + public void enqueue(BaseQueue queue, EnqueableMessage message, Action postTransactionAction) + { + TransactionLog.Transaction txn = null; + try + { + if(message.isPersistent() && queue.isDurable()) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Enqueue of message number " + message.getMessageNumber() + " to transaction log. Queue : " + queue.getNameShortString()); + } + + txn = _transactionLog.newTransaction(); + txn.enqueueMessage(queue, message.getMessageNumber()); + txn.commitTran(); + txn = null; + } + postTransactionAction.postCommit(); + postTransactionAction = null; + } + catch (AMQException e) + { + _logger.error("Error during message enqueue", e); + throw new RuntimeException("Error during message enqueue", e); + } + finally + { + rollbackIfNecessary(postTransactionAction, txn); + } + + + } + + public void enqueue(List<? extends BaseQueue> queues, EnqueableMessage message, Action postTransactionAction) + { + TransactionLog.Transaction txn = null; + try + { + + if(message.isPersistent()) + { + Long id = message.getMessageNumber(); + for(BaseQueue queue : queues) + { + if (queue.isDurable()) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Enqueue of message number " + message.getMessageNumber() + " to transaction log. Queue : " + queue.getNameShortString()); + } + if (txn == null) + { + txn = _transactionLog.newTransaction(); + } + + txn.enqueueMessage(queue, id); + } + } + + if (txn != null) + { + txn.commitTran(); + txn = null; + + } + } + postTransactionAction.postCommit(); + postTransactionAction = null; + } + catch (AMQException e) + { + _logger.error("Error during message enqueues", e); + throw new RuntimeException("Error during message enqueues", e); + } + finally + { + rollbackIfNecessary(postTransactionAction, txn); + } + + } + + + public void commit() + { + } + + public void rollback() + { + } + + private void rollbackIfNecessary(Action postTransactionAction, TransactionLog.Transaction txn) + { + if (txn != null) + { + try + { + txn.abortTran(); + } + catch (AMQStoreException e) + { + _logger.error("Abort transaction failed", e); + // Deliberate decision not to re-throw this exception. Rationale: we can only reach here if a previous + // TransactionLog method has ended in Exception. If we were to re-throw here, we would prevent + // our caller from receiving the original exception (which is likely to be more revealing of the underlying error). + } + } + if (postTransactionAction != null) + { + postTransactionAction.onRollback(); + } + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/LocalTransaction.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/LocalTransaction.java new file mode 100755 index 0000000000..946dbd7c28 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/LocalTransaction.java @@ -0,0 +1,309 @@ +package org.apache.qpid.server.txn; +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + * + */ + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.qpid.AMQException; +import org.apache.qpid.server.message.EnqueableMessage; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.store.TransactionLog; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.store.TransactionLog; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A concrete implementation of ServerTransaction where enqueue/dequeue + * operations share a single long-lived transaction. + * + * The caller is responsible for invoking commit() (or rollback()) as necessary. + */ +public class LocalTransaction implements ServerTransaction +{ + protected static final Logger _logger = LoggerFactory.getLogger(LocalTransaction.class); + + private final List<Action> _postTransactionActions = new ArrayList<Action>(); + + private volatile TransactionLog.Transaction _transaction; + private TransactionLog _transactionLog; + private long _txnStartTime = 0L; + + public LocalTransaction(TransactionLog transactionLog) + { + _transactionLog = transactionLog; + } + + public boolean inTransaction() + { + return _transaction != null; + } + + public long getTransactionStartTime() + { + return _txnStartTime; + } + + public void addPostTransactionAction(Action postTransactionAction) + { + _postTransactionActions.add(postTransactionAction); + } + + public void dequeue(BaseQueue queue, EnqueableMessage message, Action postTransactionAction) + { + _postTransactionActions.add(postTransactionAction); + + if(message.isPersistent() && queue.isDurable()) + { + try + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Dequeue of message number " + message.getMessageNumber() + " from transaction log. Queue : " + queue.getNameShortString()); + } + + beginTranIfNecessary(); + _transaction.dequeueMessage(queue, message.getMessageNumber()); + + } + catch(AMQException e) + { + _logger.error("Error during message dequeues", e); + tidyUpOnError(e); + } + } + } + + public void dequeue(Collection<QueueEntry> queueEntries, Action postTransactionAction) + { + _postTransactionActions.add(postTransactionAction); + + try + { + for(QueueEntry entry : queueEntries) + { + ServerMessage message = entry.getMessage(); + BaseQueue queue = entry.getQueue(); + + if(message.isPersistent() && queue.isDurable()) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Dequeue of message number " + message.getMessageNumber() + " from transaction log. Queue : " + queue.getNameShortString()); + } + + beginTranIfNecessary(); + _transaction.dequeueMessage(queue, message.getMessageNumber()); + } + + } + } + catch(AMQException e) + { + _logger.error("Error during message dequeues", e); + tidyUpOnError(e); + } + } + + private void tidyUpOnError(Exception e) + { + try + { + for(Action action : _postTransactionActions) + { + action.onRollback(); + } + } + finally + { + try + { + if (_transaction != null) + { + _transaction.abortTran(); + } + } + catch (Exception abortException) + { + _logger.error("Abort transaction failed while trying to handle previous error", abortException); + } + finally + { + resetDetails(); + } + } + + throw new RuntimeException(e); + } + + private void beginTranIfNecessary() + { + + if(_transaction == null) + { + try + { + _transaction = _transactionLog.newTransaction(); + } + catch (Exception e) + { + tidyUpOnError(e); + } + } + } + + public void enqueue(BaseQueue queue, EnqueableMessage message, Action postTransactionAction) + { + _postTransactionActions.add(postTransactionAction); + + if(message.isPersistent() && queue.isDurable()) + { + try + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Enqueue of message number " + message.getMessageNumber() + " to transaction log. Queue : " + queue.getNameShortString()); + } + + beginTranIfNecessary(); + _transaction.enqueueMessage(queue, message.getMessageNumber()); + } + catch (Exception e) + { + _logger.error("Error during message enqueue", e); + + tidyUpOnError(e); + } + } + } + + public void enqueue(List<? extends BaseQueue> queues, EnqueableMessage message, Action postTransactionAction) + { + _postTransactionActions.add(postTransactionAction); + + if (_txnStartTime == 0L) + { + _txnStartTime = System.currentTimeMillis(); + } + + if(message.isPersistent()) + { + try + { + for(BaseQueue queue : queues) + { + if(queue.isDurable()) + { + if (_logger.isDebugEnabled()) + { + _logger.debug("Enqueue of message number " + message.getMessageNumber() + " to transaction log. Queue : " + queue.getNameShortString() ); + } + + + beginTranIfNecessary(); + _transaction.enqueueMessage(queue, message.getMessageNumber()); + } + } + + } + catch (Exception e) + { + _logger.error("Error during message enqueue", e); + + tidyUpOnError(e); + } + } + } + + public void commit() + { + try + { + if(_transaction != null) + { + _transaction.commitTran(); + } + + for(Action action : _postTransactionActions) + { + action.postCommit(); + } + } + catch (Exception e) + { + _logger.error("Failed to commit transaction", e); + + for(Action action : _postTransactionActions) + { + action.onRollback(); + } + throw new RuntimeException("Failed to commit transaction", e); + } + finally + { + resetDetails(); + } + } + + public void rollback() + { + try + { + if(_transaction != null) + { + _transaction.abortTran(); + } + } + catch (AMQException e) + { + _logger.error("Failed to rollback transaction", e); + throw new RuntimeException("Failed to rollback transaction", e); + } + finally + { + try + { + for(Action action : _postTransactionActions) + { + action.onRollback(); + } + } + finally + { + resetDetails(); + } + } + } + + private void resetDetails() + { + _transaction = null; + _postTransactionActions.clear(); + _txnStartTime = 0L; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/ServerTransaction.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/ServerTransaction.java new file mode 100755 index 0000000000..b3c6e1ac3a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/txn/ServerTransaction.java @@ -0,0 +1,110 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.txn; + +import org.apache.qpid.server.message.EnqueableMessage; +import org.apache.qpid.server.queue.BaseQueue; +import org.apache.qpid.server.queue.QueueEntry; + +import java.util.Collection; +import java.util.List; + + +/** + * The ServerTransaction interface allows a set enqueue/dequeue operations to be + * performed against the transaction belonging the underlying TransactionLog object. + * + * Typically all ServerTransaction implementations decide if a message should be enlisted + * into a store transaction by examining the durable property of the queue, and the persistence + * property of the message. + * + * A caller may register a list of post transaction Actions to be + * performed on commit() (or rollback()). + * + */ +public interface ServerTransaction +{ + /** + * Represents an action to be performed on transaction commit or rollback + */ + public static interface Action + { + public void postCommit(); + + public void onRollback(); + } + + /** + * Return the time the current transaction started. + * + * @return the time this transaction started or 0 if not in a transaction + */ + long getTransactionStartTime(); + + /** + * Register an Action for execution after transaction commit or rollback. Actions + * will be executed in the order in which they are registered. + */ + void addPostTransactionAction(Action postTransactionAction); + + /** + * Dequeue a message from a queue registering a post transaction action. + * + * A store operation will result only for a persistent message on a durable queue. + */ + void dequeue(BaseQueue queue, EnqueableMessage message, Action postTransactionAction); + + /** + * Dequeue a message(s) from queue(s) registering a post transaction action. + * + * Store operations will result only for a persistent messages on durable queues. + */ + void dequeue(Collection<QueueEntry> messages, Action postTransactionAction); + + /** + * Enqueue a message to a queue registering a post transaction action. + * + * A store operation will result only for a persistent message on a durable queue. + */ + void enqueue(BaseQueue queue, EnqueableMessage message, Action postTransactionAction); + + /** + * Enqueue a message(s) to queue(s) registering a post transaction action. + * + * Store operations will result only for a persistent messages on durable queues. + */ + void enqueue(List<? extends BaseQueue> queues, EnqueableMessage message, Action postTransactionAction); + + /** + * Commit the transaction represented by this object. + * + * If the caller has registered one or more Actions, the postCommit() method on each will + * be executed immediately after the underlying transaction has committed. + */ + void commit(); + + /** Rollback the transaction represented by this object. + * + * If the caller has registered one or more Actions, the onRollback() method on each will + * be executed immediately after the underlying transaction has rolled-back. + */ + void rollback(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/util/CircularBuffer.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/CircularBuffer.java new file mode 100644 index 0000000000..e730e2f3c3 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/CircularBuffer.java @@ -0,0 +1,131 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.util; + +import java.util.Iterator; + +import org.apache.log4j.Logger; + +public class CircularBuffer implements Iterable +{ + + private static final Logger _logger = Logger.getLogger(CircularBuffer.class); + + private final Object[] _log; + private int _size; + private int _index; + + public CircularBuffer(int size) + { + _log = new Object[size]; + } + + public void add(Object o) + { + _log[_index++] = o; + _size = Math.min(_size+1, _log.length); + if(_index >= _log.length) + { + _index = 0; + } + } + + public Object get(int i) + { + if(i >= _log.length) + { + throw new ArrayIndexOutOfBoundsException(i); + } + return _log[index(i)]; + } + + public int size() { + return _size; + } + + public Iterator iterator() + { + return new Iterator() + { + private int i = 0; + + public boolean hasNext() + { + return i < _size; + } + + public Object next() + { + return get(i++); + } + + public void remove() + { + throw new UnsupportedOperationException(); + } + }; + } + + public String toString() + { + StringBuilder s = new StringBuilder(); + boolean first = true; + for(Object o : this) + { + if(!first) + { + s.append(", "); + } + else + { + first = false; + } + s.append(o); + } + return s.toString(); + } + + public void dump() + { + for(Object o : this) + { + _logger.info(o); + } + } + + int index(int i) + { + return _size == _log.length ? (_index + i) % _log.length : i; + } + + public static void main(String[] artgv) + { + String[] items = new String[]{ + "A","B","C","D","E","F","G","H","I","J","K" + }; + CircularBuffer buffer = new CircularBuffer(5); + for(String s : items) + { + buffer.add(s); + _logger.info(buffer); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/util/LoggingProxy.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/LoggingProxy.java new file mode 100644 index 0000000000..eda97e0ed2 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/util/LoggingProxy.java @@ -0,0 +1,105 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.util; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; + +/** + * Dynamic proxy that records invocations in a fixed size circular buffer, + * dumping details on hitting an exception. + * <p> + * Useful in debugging. + * <p> + */ +public class LoggingProxy implements InvocationHandler +{ + private final Object _target; + private final CircularBuffer _log; + + public LoggingProxy(Object target, int size) + { + _target = target; + _log = new CircularBuffer(size); + } + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable + { + try + { + entered(method, args); + Object result = method.invoke(_target, args); + returned(method, result); + return result; + } + catch(InvocationTargetException e) + { + dump(); + throw e.getTargetException(); + } + } + + void dump() + { + _log.dump(); + } + + CircularBuffer getBuffer() + { + return _log; + } + + private synchronized void entered(Method method, Object[] args) + { + if (args == null) + { + _log.add(Thread.currentThread() + ": " + method.getName() + "() entered"); + } + else + { + _log.add(Thread.currentThread() + ": " + method.getName() + "(" + Arrays.toString(args) + ") entered"); + } + } + + private synchronized void returned(Method method, Object result) + { + if (method.getReturnType() == Void.TYPE) + { + _log.add(Thread.currentThread() + ": " + method.getName() + "() returned"); + } + else + { + _log.add(Thread.currentThread() + ": " + method.getName() + "() returned " + result); + } + } + + public Object getProxy(Class... c) + { + return Proxy.newProxyInstance(_target.getClass().getClassLoader(), c, this); + } + + public int getBufferSize() { + return _log.size(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/HouseKeepingTask.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/HouseKeepingTask.java new file mode 100644 index 0000000000..2db1944cd1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/HouseKeepingTask.java @@ -0,0 +1,76 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.virtualhost; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.logging.RootMessageLogger; +import org.apache.qpid.server.logging.actors.AbstractActor; +import org.apache.qpid.server.logging.actors.CurrentActor; + +public abstract class HouseKeepingTask implements Runnable +{ + Logger _logger = Logger.getLogger(this.getClass()); + + private VirtualHost _virtualHost; + + private String _name; + + private RootMessageLogger _rootLogger; + public HouseKeepingTask(VirtualHost vhost) + { + _virtualHost = vhost; + _name = _virtualHost.getName() + ":" + this.getClass().getSimpleName(); + _rootLogger = CurrentActor.get().getRootMessageLogger(); + } + + final public void run() + { + // Don't need to undo this as this is a thread pool thread so will + // always go through here before we do any real work. + Thread.currentThread().setName(_name); + CurrentActor.set(new AbstractActor(_rootLogger) + { + @Override + public String getLogMessage() + { + return _name; + } + }); + + try + { + execute(); + } + catch (Throwable e) + { + _logger.warn(this.getClass().getSimpleName() + " throw exception: " + e, e); + } + } + + public VirtualHost getVirtualHost() + { + return _virtualHost; + } + + /** Execute the plugin. */ + public abstract void execute(); + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/ManagedVirtualHost.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/ManagedVirtualHost.java new file mode 100644 index 0000000000..767474d5ae --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/ManagedVirtualHost.java @@ -0,0 +1,44 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.qpid.server.virtualhost;
+
+import java.io.IOException;
+
+import org.apache.qpid.management.common.mbeans.annotations.MBeanAttribute;
+
+/**
+ * The management interface exposed to allow management of a virtualHost
+ */
+public interface ManagedVirtualHost
+{
+ static final String TYPE = "VirtualHost";
+ static final int VERSION = 1;
+
+ /**
+ * Returns the name of the managed virtualHost.
+ * @return the name of the exchange.
+ * @throws java.io.IOException
+ */
+ @MBeanAttribute(name="Name", description= TYPE + " Name")
+ String getName() throws IOException;
+
+
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java new file mode 100755 index 0000000000..04f19b79bb --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHost.java @@ -0,0 +1,100 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.virtualhost; + +import java.util.UUID; + +import org.apache.qpid.common.Closeable; +import org.apache.qpid.server.binding.BindingFactory; +import org.apache.qpid.server.configuration.ConfigStore; +import org.apache.qpid.server.configuration.VirtualHostConfig; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.connection.IConnectionRegistry; +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.federation.BrokerLink; +import org.apache.qpid.server.management.ManagedObject; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.stats.StatisticsGatherer; +import org.apache.qpid.server.store.DurableConfigurationStore; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.TransactionLog; + +public interface VirtualHost extends DurableConfigurationStore.Source, VirtualHostConfig, Closeable, StatisticsGatherer +{ + IConnectionRegistry getConnectionRegistry(); + + VirtualHostConfiguration getConfiguration(); + + String getName(); + + QueueRegistry getQueueRegistry(); + + ExchangeRegistry getExchangeRegistry(); + + ExchangeFactory getExchangeFactory(); + + MessageStore getMessageStore(); + + TransactionLog getTransactionLog(); + + DurableConfigurationStore getDurableConfigurationStore(); + + AuthenticationManager getAuthenticationManager(); + + SecurityManager getSecurityManager(); + + void close(); + + ManagedObject getManagedObject(); + + UUID getBrokerId(); + + void scheduleHouseKeepingTask(long period, HouseKeepingTask task); + + long getHouseKeepingTaskCount(); + + public long getHouseKeepingCompletedTaskCount(); + + int getHouseKeepingPoolSize(); + + void setHouseKeepingPoolSize(int newSize); + + int getHouseKeepingActiveCount(); + + IApplicationRegistry getApplicationRegistry(); + + BindingFactory getBindingFactory(); + + void createBrokerConnection(String transport, + String host, + int port, + String vhost, + boolean durable, + String authMechanism, String username, String password); + + ConfigStore getConfigStore(); + + void removeBrokerConnection(BrokerLink brokerLink); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostConfigRecoveryHandler.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostConfigRecoveryHandler.java new file mode 100755 index 0000000000..96a9ac729e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostConfigRecoveryHandler.java @@ -0,0 +1,360 @@ +/* +* +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you 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.apache.qpid.server.virtualhost; + +import org.apache.qpid.server.store.ConfigurationRecoveryHandler; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.MessageStoreRecoveryHandler; +import org.apache.qpid.server.store.StoredMessage; +import org.apache.qpid.server.store.TransactionLogRecoveryHandler; +import org.apache.qpid.server.store.TransactionLog; +import org.apache.qpid.server.store.TransactionLogResource; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.logging.subjects.MessageStoreLogSubject; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.TransactionLogMessages; +import org.apache.qpid.server.message.AMQMessage; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.server.message.MessageTransferMessage; +import org.apache.qpid.server.binding.BindingFactory; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.AMQException; + +import org.apache.log4j.Logger; + +import java.nio.ByteBuffer; + +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import java.util.HashMap; +import java.util.TreeMap; + +public class VirtualHostConfigRecoveryHandler implements ConfigurationRecoveryHandler, + ConfigurationRecoveryHandler.QueueRecoveryHandler, + ConfigurationRecoveryHandler.ExchangeRecoveryHandler, + ConfigurationRecoveryHandler.BindingRecoveryHandler, + MessageStoreRecoveryHandler, + MessageStoreRecoveryHandler.StoredMessageRecoveryHandler, + TransactionLogRecoveryHandler, + TransactionLogRecoveryHandler.QueueEntryRecoveryHandler +{ + private static final Logger _logger = Logger.getLogger(VirtualHostConfigRecoveryHandler.class); + + + private final VirtualHost _virtualHost; + + private MessageStoreLogSubject _logSubject; + private List<ProcessAction> _actions; + + private MessageStore _store; + private TransactionLog _transactionLog; + + private final Map<String, Integer> _queueRecoveries = new TreeMap<String, Integer>(); + private Map<Long, ServerMessage> _recoveredMessages = new HashMap<Long, ServerMessage>(); + private Map<Long, StoredMessage> _unusedMessages = new HashMap<Long, StoredMessage>(); + + + + public VirtualHostConfigRecoveryHandler(VirtualHost virtualHost) + { + _virtualHost = virtualHost; + } + + public QueueRecoveryHandler begin(MessageStore store) + { + _logSubject = new MessageStoreLogSubject(_virtualHost,store); + _store = store; + CurrentActor.get().message(_logSubject, TransactionLogMessages.RECOVERY_START(null, false)); + + return this; + } + + public void queue(String queueName, String owner, boolean exclusive, FieldTable arguments) + { + try + { + AMQShortString queueNameShortString = new AMQShortString(queueName); + + AMQQueue q = _virtualHost.getQueueRegistry().getQueue(queueNameShortString); + + if (q == null) + { + q = AMQQueueFactory.createAMQQueueImpl(queueNameShortString, true, owner == null ? null : new AMQShortString(owner), false, exclusive, _virtualHost, + arguments); + _virtualHost.getQueueRegistry().registerQueue(q); + } + + CurrentActor.get().message(_logSubject, TransactionLogMessages.RECOVERY_START(queueName, true)); + + //Record that we have a queue for recovery + _queueRecoveries.put(queueName, 0); + } + catch (AMQException e) + { + // TODO + throw new RuntimeException(e); + } + } + + public ExchangeRecoveryHandler completeQueueRecovery() + { + return this; + } + + public void exchange(String exchangeName, String type, boolean autoDelete) + { + try + { + Exchange exchange; + AMQShortString exchangeNameSS = new AMQShortString(exchangeName); + exchange = _virtualHost.getExchangeRegistry().getExchange(exchangeNameSS); + if (exchange == null) + { + exchange = _virtualHost.getExchangeFactory().createExchange(exchangeNameSS, new AMQShortString(type), true, autoDelete, 0); + _virtualHost.getExchangeRegistry().registerExchange(exchange); + } + } + catch (AMQException e) + { + // TODO + throw new RuntimeException(e); + } + } + + public BindingRecoveryHandler completeExchangeRecovery() + { + return this; + } + + public StoredMessageRecoveryHandler begin() + { + // TODO - log begin + return this; + } + + public void message(StoredMessage message) + { + ServerMessage serverMessage; + switch(message.getMetaData().getType()) + { + case META_DATA_0_8: + serverMessage = new AMQMessage(message); + break; + case META_DATA_0_10: + serverMessage = new MessageTransferMessage(message, null); + break; + default: + throw new RuntimeException("Unknown message type retrieved from store " + message.getMetaData().getClass()); + } + + //_logger.debug("Recovered message with id " + serverMessage); + + + _recoveredMessages.put(message.getMessageNumber(), serverMessage); + _unusedMessages.put(message.getMessageNumber(), message); + } + + public void completeMessageRecovery() + { + //TODO - log end + //To change body of implemented methods use File | Settings | File Templates. + } + + public TransactionLogRecoveryHandler.QueueEntryRecoveryHandler begin(TransactionLog log) + { + _transactionLog = log; + return this; + } + + private static final class ProcessAction + { + private final AMQQueue _queue; + private final AMQMessage _message; + + public ProcessAction(AMQQueue queue, AMQMessage message) + { + _queue = queue; + _message = message; + } + + public void process() + { + try + { + _queue.enqueue(_message); + } + catch(AMQException e) + { + throw new RuntimeException(e); + } + } + + } + + public void binding(String exchangeName, String queueName, String bindingKey, ByteBuffer buf) + { + _actions = new ArrayList<ProcessAction>(); + try + { + Exchange exchange = _virtualHost.getExchangeRegistry().getExchange(exchangeName); + if (exchange == null) + { + _logger.error("Unknown exchange: " + exchangeName + ", cannot bind queue : " + queueName); + return; + } + + AMQQueue queue = _virtualHost.getQueueRegistry().getQueue(new AMQShortString(queueName)); + if (queue == null) + { + _logger.error("Unknown queue: " + queueName + ", cannot be bound to exchange: " + exchangeName); + } + else + { + FieldTable argumentsFT = null; + if(buf != null) + { + argumentsFT = new FieldTable(org.apache.mina.common.ByteBuffer.wrap(buf),buf.limit()); + } + + BindingFactory bf = _virtualHost.getBindingFactory(); + + Map<String, Object> argumentMap = FieldTable.convertToMap(argumentsFT); + + if(bf.getBinding(bindingKey, queue, exchange, argumentMap) == null) + { + + _logger.info("Restoring binding: (Exchange: " + exchange.getNameShortString() + ", Queue: " + queueName + + ", Routing Key: " + bindingKey + ", Arguments: " + argumentsFT + ")"); + + bf.restoreBinding(bindingKey, queue, exchange, argumentMap); + } + } + } + catch (AMQException e) + { + throw new RuntimeException(e); + } + + } + + public void completeBindingRecovery() + { + //return this; + } + + public void complete() + { + + + } + + public void queueEntry(final String queueName, long messageId) + { + AMQShortString queueNameShortString = new AMQShortString(queueName); + + AMQQueue queue = _virtualHost.getQueueRegistry().getQueue(queueNameShortString); + + try + { + if(queue != null) + { + ServerMessage message = _recoveredMessages.get(messageId); + _unusedMessages.remove(messageId); + + if(message != null) + { + + + if (_logger.isDebugEnabled()) + { + _logger.debug("On recovery, delivering " + message.getMessageNumber() + " to " + queue.getNameShortString()); + } + + Integer count = _queueRecoveries.get(queueName); + if (count == null) + { + count = 0; + } + + queue.enqueue(message); + + _queueRecoveries.put(queueName, ++count); + } + else + { + _logger.warn("Message id " + messageId + " referenced in log as enqueued in queue " + queue.getNameShortString() + " is unknown, entry will be discarded"); + TransactionLog.Transaction txn = _transactionLog.newTransaction(); + txn.dequeueMessage(queue, messageId); + txn.commitTranAsync(); + } + } + else + { + _logger.warn("Message id " + messageId + " in log references queue " + queueName + " which is not in the configuration, entry will be discarded"); + TransactionLog.Transaction txn = _transactionLog.newTransaction(); + TransactionLogResource mockQueue = + new TransactionLogResource() + { + + public String getResourceName() + { + return queueName; + } + }; + txn.dequeueMessage(mockQueue, messageId); + txn.commitTranAsync(); + } + + } + catch(AMQException e) + { + throw new RuntimeException(e); + } + + + + } + + public void completeQueueEntryRecovery() + { + + for(StoredMessage m : _unusedMessages.values()) + { + _logger.warn("Message id " + m.getMessageNumber() + " in store, but not in any queue - removing...."); + m.remove(); + } + + for(Map.Entry<String,Integer> entry : _queueRecoveries.entrySet()) + { + CurrentActor.get().message(_logSubject, TransactionLogMessages.RECOVERED(entry.getValue(), entry.getKey())); + + CurrentActor.get().message(_logSubject, TransactionLogMessages.RECOVERY_COMPLETE(entry.getKey(), true)); + } + + CurrentActor.get().message(_logSubject, TransactionLogMessages.RECOVERY_COMPLETE(null, false)); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostImpl.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostImpl.java new file mode 100644 index 0000000000..33c713c62a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostImpl.java @@ -0,0 +1,876 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.virtualhost; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import javax.management.NotCompliantMBeanException; +import javax.management.ObjectName; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.AMQStoreException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.server.AMQBrokerManagerMBean; +import org.apache.qpid.server.binding.BindingFactory; +import org.apache.qpid.server.configuration.BrokerConfig; +import org.apache.qpid.server.configuration.ConfigStore; +import org.apache.qpid.server.configuration.ConfiguredObject; +import org.apache.qpid.server.configuration.ExchangeConfiguration; +import org.apache.qpid.server.configuration.QueueConfiguration; +import org.apache.qpid.server.configuration.VirtualHostConfigType; +import org.apache.qpid.server.configuration.VirtualHostConfiguration; +import org.apache.qpid.server.connection.ConnectionRegistry; +import org.apache.qpid.server.connection.IConnectionRegistry; +import org.apache.qpid.server.exchange.DefaultExchangeFactory; +import org.apache.qpid.server.exchange.DefaultExchangeRegistry; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.ExchangeFactory; +import org.apache.qpid.server.exchange.ExchangeRegistry; +import org.apache.qpid.server.federation.BrokerLink; +import org.apache.qpid.server.logging.LogSubject; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.logging.messages.VirtualHostMessages; +import org.apache.qpid.server.logging.subjects.MessageStoreLogSubject; +import org.apache.qpid.server.management.AMQManagedObject; +import org.apache.qpid.server.management.ManagedObject; +import org.apache.qpid.server.protocol.AMQConnectionModel; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.AMQQueueFactory; +import org.apache.qpid.server.queue.DefaultQueueRegistry; +import org.apache.qpid.server.queue.QueueRegistry; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.IApplicationRegistry; +import org.apache.qpid.server.security.SecurityManager; +import org.apache.qpid.server.security.auth.manager.AuthenticationManager; +import org.apache.qpid.server.security.auth.manager.PrincipalDatabaseAuthenticationManager; +import org.apache.qpid.server.stats.StatisticsCounter; +import org.apache.qpid.server.store.ConfigurationRecoveryHandler; +import org.apache.qpid.server.store.DurableConfigurationStore; +import org.apache.qpid.server.store.MessageStore; +import org.apache.qpid.server.store.TransactionLog; +import org.apache.qpid.server.virtualhost.plugins.VirtualHostPlugin; +import org.apache.qpid.server.virtualhost.plugins.VirtualHostPluginFactory; + +public class VirtualHostImpl implements VirtualHost +{ + private static final Logger _logger = Logger.getLogger(VirtualHostImpl.class); + + private final String _name; + + private ConnectionRegistry _connectionRegistry; + + private QueueRegistry _queueRegistry; + + private ExchangeRegistry _exchangeRegistry; + + private ExchangeFactory _exchangeFactory; + + private MessageStore _messageStore; + + protected VirtualHostMBean _virtualHostMBean; + + private AMQBrokerManagerMBean _brokerMBean; + + private final AuthenticationManager _authenticationManager; + + private SecurityManager _securityManager; + + private final ScheduledThreadPoolExecutor _houseKeepingTasks; + private final IApplicationRegistry _appRegistry; + private VirtualHostConfiguration _configuration; + private DurableConfigurationStore _durableConfigurationStore; + private BindingFactory _bindingFactory; + private BrokerConfig _broker; + private UUID _id; + + private boolean _statisticsEnabled = false; + private StatisticsCounter _messagesDelivered, _dataDelivered, _messagesReceived, _dataReceived; + + private final long _createTime = System.currentTimeMillis(); + private final ConcurrentHashMap<BrokerLink,BrokerLink> _links = new ConcurrentHashMap<BrokerLink, BrokerLink>(); + private static final int HOUSEKEEPING_SHUTDOWN_TIMEOUT = 5; + + public IConnectionRegistry getConnectionRegistry() + { + return _connectionRegistry; + } + + public VirtualHostConfiguration getConfiguration() + { + return _configuration; + } + + public UUID getId() + { + return _id; + } + + public VirtualHostConfigType getConfigType() + { + return VirtualHostConfigType.getInstance(); + } + + public ConfiguredObject getParent() + { + return getBroker(); + } + + public boolean isDurable() + { + return false; + } + + /** + * Virtual host JMX MBean class. + * + * This has some of the methods implemented from management intrerface for exchanges. Any + * implementaion of an Exchange MBean should extend this class. + */ + public class VirtualHostMBean extends AMQManagedObject implements ManagedVirtualHost + { + public VirtualHostMBean() throws NotCompliantMBeanException + { + super(ManagedVirtualHost.class, ManagedVirtualHost.TYPE); + } + + public String getObjectInstanceName() + { + return ObjectName.quote(_name); + } + + public String getName() + { + return _name; + } + + public VirtualHostImpl getVirtualHost() + { + return VirtualHostImpl.this; + } + } + + public VirtualHostImpl(IApplicationRegistry appRegistry, VirtualHostConfiguration hostConfig) throws Exception + { + this(appRegistry, hostConfig, null); + } + + + public VirtualHostImpl(VirtualHostConfiguration hostConfig, MessageStore store) throws Exception + { + this(ApplicationRegistry.getInstance(),hostConfig,store); + } + + private VirtualHostImpl(IApplicationRegistry appRegistry, VirtualHostConfiguration hostConfig, MessageStore store) throws Exception + { + if (hostConfig == null) + { + throw new IllegalAccessException("HostConfig and MessageStore cannot be null"); + } + + _appRegistry = appRegistry; + _broker = _appRegistry.getBroker(); + _configuration = hostConfig; + _name = _configuration.getName(); + + _id = _appRegistry.getConfigStore().createId(); + + CurrentActor.get().message(VirtualHostMessages.CREATED(_name)); + + if (_name == null || _name.length() == 0) + { + throw new IllegalArgumentException("Illegal name (" + _name + ") for virtualhost."); + } + + _securityManager = new SecurityManager(_appRegistry.getSecurityManager()); + _securityManager.configureHostPlugins(_configuration); + + _virtualHostMBean = new VirtualHostMBean(); + + _connectionRegistry = new ConnectionRegistry(); + + _houseKeepingTasks = new ScheduledThreadPoolExecutor(_configuration.getHouseKeepingThreadCount()); + + _queueRegistry = new DefaultQueueRegistry(this); + + _exchangeFactory = new DefaultExchangeFactory(this); + _exchangeFactory.initialise(_configuration); + + _exchangeRegistry = new DefaultExchangeRegistry(this); + + StartupRoutingTable configFileRT = new StartupRoutingTable(); + + _durableConfigurationStore = configFileRT; + + // This needs to be after the RT has been defined as it creates the default durable exchanges. + _exchangeRegistry.initialise(); + + _bindingFactory = new BindingFactory(this); + + initialiseModel(_configuration); + + if (store != null) + { + _messageStore = store; + _durableConfigurationStore = store; + } + else + { + initialiseMessageStore(hostConfig); + } + + _authenticationManager = ApplicationRegistry.getInstance().getAuthenticationManager(); + + _brokerMBean = new AMQBrokerManagerMBean(_virtualHostMBean); + _brokerMBean.register(); + initialiseHouseKeeping(hostConfig.getHousekeepingExpiredMessageCheckPeriod()); + + initialiseStatistics(); + } + + private void initialiseHouseKeeping(long period) + { + /* add a timer task to iterate over queues, cleaning expired messages from queues with no consumers */ + if (period != 0L) + { + class ExpiredMessagesTask extends HouseKeepingTask + { + public ExpiredMessagesTask(VirtualHost vhost) + { + super(vhost); + } + + public void execute() + { + for (AMQQueue q : _queueRegistry.getQueues()) + { + _logger.debug("Checking message status for queue: " + + q.getName()); + try + { + q.checkMessageStatus(); + } + catch (Exception e) + { + _logger.error("Exception in housekeeping for queue: " + + q.getNameShortString().toString(), e); + //Don't throw exceptions as this will stop the + // house keeping task from running. + } + } + for (AMQConnectionModel connection : getConnectionRegistry().getConnections()) + { + _logger.debug("Checking for long running open transactions on connection " + connection); + for (AMQSessionModel session : connection.getSessionModels()) + { + _logger.debug("Checking for long running open transactions on session " + session); + try + { + session.checkTransactionStatus(_configuration.getTransactionTimeoutOpenWarn(), + _configuration.getTransactionTimeoutOpenClose(), + _configuration.getTransactionTimeoutIdleWarn(), + _configuration.getTransactionTimeoutIdleClose()); + } + catch (Exception e) + { + _logger.error("Exception in housekeeping for connection: " + connection.toString(), e); + } + } + } + } + } + + scheduleHouseKeepingTask(period, new ExpiredMessagesTask(this)); + + Map<String, VirtualHostPluginFactory> plugins = + ApplicationRegistry.getInstance().getPluginManager().getVirtualHostPlugins(); + + if (plugins != null) + { + for (Map.Entry<String, VirtualHostPluginFactory> entry : plugins.entrySet()) + { + String pluginName = entry.getKey(); + VirtualHostPluginFactory factory = entry.getValue(); + try + { + VirtualHostPlugin plugin = factory.newInstance(this); + + // If we had configuration for the plugin the schedule it. + if (plugin != null) + { + _houseKeepingTasks.scheduleAtFixedRate(plugin, plugin.getDelay() / 2, + plugin.getDelay(), plugin.getTimeUnit()); + + _logger.info("Loaded VirtualHostPlugin:" + plugin); + } + } + catch (RuntimeException e) + { + _logger.error("Unable to load VirtualHostPlugin:" + pluginName + " due to:" + e.getMessage(), e); + } + } + } + } + } + + /** + * Allow other broker components to register a HouseKeepingTask + * + * @param period How often this task should run, in ms. + * @param task The task to run. + */ + public void scheduleHouseKeepingTask(long period, HouseKeepingTask task) + { + _houseKeepingTasks.scheduleAtFixedRate(task, period / 2, period, + TimeUnit.MILLISECONDS); + } + + public long getHouseKeepingTaskCount() + { + return _houseKeepingTasks.getTaskCount(); + } + + public long getHouseKeepingCompletedTaskCount() + { + return _houseKeepingTasks.getCompletedTaskCount(); + } + + public int getHouseKeepingPoolSize() + { + return _houseKeepingTasks.getCorePoolSize(); + } + + public void setHouseKeepingPoolSize(int newSize) + { + _houseKeepingTasks.setCorePoolSize(newSize); + } + + + public int getHouseKeepingActiveCount() + { + return _houseKeepingTasks.getActiveCount(); + } + + + private void initialiseMessageStore(VirtualHostConfiguration hostConfig) throws Exception + { + String messageStoreClass = hostConfig.getMessageStoreClass(); + + Class clazz = Class.forName(messageStoreClass); + Object o = clazz.newInstance(); + + if (!(o instanceof MessageStore)) + { + throw new ClassCastException("Message store class must implement " + MessageStore.class + ". Class " + clazz + + " does not."); + } + MessageStore messageStore = (MessageStore) o; + VirtualHostConfigRecoveryHandler recoveryHandler = new VirtualHostConfigRecoveryHandler(this); + + MessageStoreLogSubject storeLogSubject = new MessageStoreLogSubject(this, messageStore); + + messageStore.configureConfigStore(this.getName(), + recoveryHandler, + hostConfig.getStoreConfiguration(), + storeLogSubject); + + messageStore.configureMessageStore(this.getName(), + recoveryHandler, + hostConfig.getStoreConfiguration(), + storeLogSubject); + messageStore.configureTransactionLog(this.getName(), + recoveryHandler, + hostConfig.getStoreConfiguration(), + storeLogSubject); + + _messageStore = messageStore; + _durableConfigurationStore = messageStore; + } + + private void initialiseModel(VirtualHostConfiguration config) throws ConfigurationException, AMQException + { + _logger.debug("Loading configuration for virtualhost: " + config.getName()); + + List exchangeNames = config.getExchanges(); + + for (Object exchangeNameObj : exchangeNames) + { + String exchangeName = String.valueOf(exchangeNameObj); + configureExchange(config.getExchangeConfiguration(exchangeName)); + } + + String[] queueNames = config.getQueueNames(); + + for (Object queueNameObj : queueNames) + { + String queueName = String.valueOf(queueNameObj); + configureQueue(config.getQueueConfiguration(queueName)); + } + } + + private void configureExchange(ExchangeConfiguration exchangeConfiguration) throws AMQException + { + AMQShortString exchangeName = new AMQShortString(exchangeConfiguration.getName()); + + Exchange exchange; + exchange = _exchangeRegistry.getExchange(exchangeName); + if (exchange == null) + { + + AMQShortString type = new AMQShortString(exchangeConfiguration.getType()); + boolean durable = exchangeConfiguration.getDurable(); + boolean autodelete = exchangeConfiguration.getAutoDelete(); + + Exchange newExchange = _exchangeFactory.createExchange(exchangeName, type, durable, autodelete, 0); + _exchangeRegistry.registerExchange(newExchange); + + if (newExchange.isDurable()) + { + _durableConfigurationStore.createExchange(newExchange); + } + } + } + + private void configureQueue(QueueConfiguration queueConfiguration) throws AMQException, ConfigurationException + { + AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(queueConfiguration, this); + + if (queue.isDurable()) + { + getDurableConfigurationStore().createQueue(queue); + } + + String exchangeName = queueConfiguration.getExchange(); + + Exchange exchange = _exchangeRegistry.getExchange(exchangeName == null ? null : new AMQShortString(exchangeName)); + + if (exchange == null) + { + exchange = _exchangeRegistry.getDefaultExchange(); + } + + if (exchange == null) + { + throw new ConfigurationException("Attempt to bind queue to unknown exchange:" + exchangeName); + } + + List routingKeys = queueConfiguration.getRoutingKeys(); + if (routingKeys == null || routingKeys.isEmpty()) + { + routingKeys = Collections.singletonList(queue.getNameShortString()); + } + + for (Object routingKeyNameObj : routingKeys) + { + AMQShortString routingKey = new AMQShortString(String.valueOf(routingKeyNameObj)); + if (_logger.isInfoEnabled()) + { + _logger.info("Binding queue:" + queue + " with routing key '" + routingKey + "' to exchange:" + this); + } + _bindingFactory.addBinding(routingKey.toString(), queue, exchange, null); + } + + if (exchange != _exchangeRegistry.getDefaultExchange()) + { + _bindingFactory.addBinding(queue.getNameShortString().toString(), queue, exchange, null); + } + } + + public String getName() + { + return _name; + } + + public BrokerConfig getBroker() + { + return _broker; + } + + public String getFederationTag() + { + return _broker.getFederationTag(); + } + + public void setBroker(final BrokerConfig broker) + { + _broker = broker; + } + + public long getCreateTime() + { + return _createTime; + } + + public QueueRegistry getQueueRegistry() + { + return _queueRegistry; + } + + public ExchangeRegistry getExchangeRegistry() + { + return _exchangeRegistry; + } + + public ExchangeFactory getExchangeFactory() + { + return _exchangeFactory; + } + + public MessageStore getMessageStore() + { + return _messageStore; + } + + public TransactionLog getTransactionLog() + { + return _messageStore; + } + + public DurableConfigurationStore getDurableConfigurationStore() + { + return _durableConfigurationStore; + } + + public AuthenticationManager getAuthenticationManager() + { + return _authenticationManager; + } + + public SecurityManager getSecurityManager() + { + return _securityManager; + } + + public void close() + { + //Stop Connections + _connectionRegistry.close(); + + //Stop the Queues processing + if (_queueRegistry != null) + { + for (AMQQueue queue : _queueRegistry.getQueues()) + { + queue.stop(); + } + } + + //Stop Housekeeping + if (_houseKeepingTasks != null) + { + _houseKeepingTasks.shutdown(); + + try + { + if (!_houseKeepingTasks.awaitTermination(HOUSEKEEPING_SHUTDOWN_TIMEOUT, TimeUnit.SECONDS)) + { + _houseKeepingTasks.shutdownNow(); + } + } + catch (InterruptedException e) + { + _logger.warn("Interrupted during Housekeeping shutdown:" + e.getMessage()); + // Swallowing InterruptedException ok as we are shutting down. + } + } + + //Close MessageStore + if (_messageStore != null) + { + //Remove MessageStore Interface should not throw Exception + try + { + _messageStore.close(); + } + catch (Exception e) + { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + } + } + + CurrentActor.get().message(VirtualHostMessages.CLOSED()); + } + + public ManagedObject getBrokerMBean() + { + return _brokerMBean; + } + + public ManagedObject getManagedObject() + { + return _virtualHostMBean; + } + + public UUID getBrokerId() + { + return _appRegistry.getBrokerId(); + } + + public IApplicationRegistry getApplicationRegistry() + { + return _appRegistry; + } + + public BindingFactory getBindingFactory() + { + return _bindingFactory; + } + + public void registerMessageDelivered(long messageSize) + { + if (isStatisticsEnabled()) + { + _messagesDelivered.registerEvent(1L); + _dataDelivered.registerEvent(messageSize); + } + _appRegistry.registerMessageDelivered(messageSize); + } + + public void registerMessageReceived(long messageSize, long timestamp) + { + if (isStatisticsEnabled()) + { + _messagesReceived.registerEvent(1L, timestamp); + _dataReceived.registerEvent(messageSize, timestamp); + } + _appRegistry.registerMessageReceived(messageSize, timestamp); + } + + public StatisticsCounter getMessageReceiptStatistics() + { + return _messagesReceived; + } + + public StatisticsCounter getDataReceiptStatistics() + { + return _dataReceived; + } + + public StatisticsCounter getMessageDeliveryStatistics() + { + return _messagesDelivered; + } + + public StatisticsCounter getDataDeliveryStatistics() + { + return _dataDelivered; + } + + public void resetStatistics() + { + _messagesDelivered.reset(); + _dataDelivered.reset(); + _messagesReceived.reset(); + _dataReceived.reset(); + + for (AMQConnectionModel connection : _connectionRegistry.getConnections()) + { + connection.resetStatistics(); + } + } + + public void initialiseStatistics() + { + setStatisticsEnabled(!StatisticsCounter.DISABLE_STATISTICS && + _appRegistry.getConfiguration().isStatisticsGenerationVirtualhostsEnabled()); + + _messagesDelivered = new StatisticsCounter("messages-delivered-" + getName()); + _dataDelivered = new StatisticsCounter("bytes-delivered-" + getName()); + _messagesReceived = new StatisticsCounter("messages-received-" + getName()); + _dataReceived = new StatisticsCounter("bytes-received-" + getName()); + } + + public boolean isStatisticsEnabled() + { + return _statisticsEnabled; + } + + public void setStatisticsEnabled(boolean enabled) + { + _statisticsEnabled = enabled; + } + + public void createBrokerConnection(final String transport, + final String host, + final int port, + final String vhost, + final boolean durable, + final String authMechanism, + final String username, + final String password) + { + BrokerLink blink = new BrokerLink(this, transport, host, port, vhost, durable, authMechanism, username, password); + if(_links.putIfAbsent(blink,blink) != null) + { + getConfigStore().addConfiguredObject(blink); + } + } + + public void removeBrokerConnection(final String transport, + final String host, + final int port, + final String vhost) + { + removeBrokerConnection(new BrokerLink(this, transport, host, port, vhost, false, null,null,null)); + + } + + public void removeBrokerConnection(BrokerLink blink) + { + blink = _links.get(blink); + if(blink != null) + { + blink.close(); + getConfigStore().removeConfiguredObject(blink); + } + } + + public ConfigStore getConfigStore() + { + return getApplicationRegistry().getConfigStore(); + } + + /** + * Temporary Startup RT class to record the creation of persistent queues / exchanges. + * + * + * This is so we can replay the creation of queues/exchanges in to the real _RT after it has been loaded. + * This should be removed after the _RT has been fully split from the the TL + */ + private static class StartupRoutingTable implements DurableConfigurationStore + { + public List<Exchange> exchange = new LinkedList<Exchange>(); + public List<CreateQueueTuple> queue = new LinkedList<CreateQueueTuple>(); + public List<CreateBindingTuple> bindings = new LinkedList<CreateBindingTuple>(); + + public void configure(VirtualHost virtualHost, String base, VirtualHostConfiguration config) throws Exception + { + } + + public void close() throws Exception + { + } + + public void removeMessage(Long messageId) throws AMQException + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void configureConfigStore(String name, + ConfigurationRecoveryHandler recoveryHandler, + Configuration config, + LogSubject logSubject) throws Exception + { + //To change body of implemented methods use File | Settings | File Templates. + } + + public void createExchange(Exchange exchange) throws AMQStoreException + { + if (exchange.isDurable()) + { + this.exchange.add(exchange); + } + } + + public void removeExchange(Exchange exchange) throws AMQStoreException + { + } + + public void bindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQStoreException + { + if (exchange.isDurable() && queue.isDurable()) + { + bindings.add(new CreateBindingTuple(exchange, routingKey, queue, args)); + } + } + + public void unbindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQStoreException + { + } + + public void createQueue(AMQQueue queue) throws AMQStoreException + { + createQueue(queue, null); + } + + public void createQueue(AMQQueue queue, FieldTable arguments) throws AMQStoreException + { + if (queue.isDurable()) + { + this.queue.add(new CreateQueueTuple(queue, arguments)); + } + } + + public void removeQueue(AMQQueue queue) throws AMQStoreException + { + } + + + private static class CreateQueueTuple + { + public AMQQueue queue; + public FieldTable arguments; + + public CreateQueueTuple(AMQQueue queue, FieldTable arguments) + { + this.queue = queue; + this.arguments = arguments; + } + } + + private static class CreateBindingTuple + { + public AMQQueue queue; + public FieldTable arguments; + public Exchange exchange; + public AMQShortString routingKey; + + public CreateBindingTuple(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) + { + this.exchange = exchange; + this.routingKey = routingKey; + this.queue = queue; + arguments = args; + } + } + + public void updateQueue(AMQQueue queue) throws AMQStoreException + { + } + } + + @Override + public String toString() + { + return _name; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostRegistry.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostRegistry.java new file mode 100644 index 0000000000..32d0c4c4d1 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/VirtualHostRegistry.java @@ -0,0 +1,109 @@ +/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.qpid.server.virtualhost;
+
+import org.apache.qpid.common.Closeable;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.configuration.ConfigStore;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+public class VirtualHostRegistry implements Closeable
+{
+ private final Map<String, VirtualHost> _registry = new ConcurrentHashMap<String, VirtualHost>();
+
+
+ private String _defaultVirtualHostName;
+ private ApplicationRegistry _applicationRegistry;
+
+ public VirtualHostRegistry(ApplicationRegistry applicationRegistry)
+ {
+ _applicationRegistry = applicationRegistry;
+ }
+
+ public synchronized void registerVirtualHost(VirtualHost host) throws Exception
+ {
+ if(_registry.containsKey(host.getName()))
+ {
+ throw new Exception("Virtual Host with name " + host.getName() + " already registered.");
+ }
+ _registry.put(host.getName(),host);
+ }
+
+ public synchronized void unregisterVirtualHost(VirtualHost host)
+ {
+ _registry.remove(host.getName());
+ }
+
+ public VirtualHost getVirtualHost(String name)
+ {
+ if(name == null || name.trim().length() == 0 || "/".equals(name.trim()))
+ {
+ name = getDefaultVirtualHostName();
+ }
+
+ return _registry.get(name);
+ }
+
+ public VirtualHost getDefaultVirtualHost()
+ {
+ return getVirtualHost(getDefaultVirtualHostName());
+ }
+
+ private String getDefaultVirtualHostName()
+ {
+ return _defaultVirtualHostName;
+ }
+
+ public void setDefaultVirtualHostName(String defaultVirtualHostName)
+ {
+ _defaultVirtualHostName = defaultVirtualHostName;
+ }
+
+
+ public Collection<VirtualHost> getVirtualHosts()
+ {
+ return new ArrayList<VirtualHost>(_registry.values());
+ }
+
+ public ApplicationRegistry getApplicationRegistry()
+ {
+ return _applicationRegistry;
+ }
+
+ public ConfigStore getConfigStore()
+ {
+ return _applicationRegistry.getConfigStore();
+ }
+
+ public void close()
+ {
+ for (VirtualHost virtualHost : getVirtualHosts())
+ {
+ virtualHost.close();
+ }
+
+ }
+}
diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/ConfiguredQueueBindingListener.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/ConfiguredQueueBindingListener.java new file mode 100644 index 0000000000..12206013eb --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/ConfiguredQueueBindingListener.java @@ -0,0 +1,106 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.virtualhost.plugins; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.configuration.plugins.SlowConsumerDetectionQueueConfiguration; +import org.apache.qpid.server.exchange.AbstractExchange; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.exchange.Exchange.BindingListener; +import org.apache.qpid.server.queue.AMQQueue; + +/** + * This is a listener that caches queues that are configured for slow consumer disconnection. + * + * There should be one listener per virtual host, which can be added to all exchanges on + * that host. + * + * TODO In future, it will be possible to configure the policy at runtime, so only the queue + * itself is cached, and the configuration looked up by the housekeeping thread. This means + * that there may be occasions where the copy of the cache contents retrieved by the thread + * does not contain queues that are configured, or that configured queues are not present. + * + * @see BindingListener + */ +public class ConfiguredQueueBindingListener implements BindingListener +{ + private static final Logger _log = Logger.getLogger(ConfiguredQueueBindingListener.class); + + private String _vhostName; + private Set<AMQQueue> _cache = Collections.synchronizedSet(new HashSet<AMQQueue>()); + + public ConfiguredQueueBindingListener(String vhostName) + { + _vhostName = vhostName; + } + + /** + * @see BindingListener#bindingAdded(Exchange, Binding) + */ + public void bindingAdded(Exchange exchange, Binding binding) + { + processBinding(binding); + } + + /** + * @see BindingListener#bindingRemoved(Exchange, Binding) + */ + public void bindingRemoved(Exchange exchange, Binding binding) + { + processBinding(binding); + } + + private void processBinding(Binding binding) + { + AMQQueue queue = binding.getQueue(); + + SlowConsumerDetectionQueueConfiguration config = + queue.getConfiguration().getConfiguration(SlowConsumerDetectionQueueConfiguration.class.getName()); + if (config != null) + { + _cache.add(queue); + } + else + { + _cache.remove(queue); + } + } + + /** + * Lookup and return the cache of configured {@link AMQQueue}s. + * + * Note that when accessing the cached queues, the {@link Iterator} is not thread safe + * (see the {@link Collections#synchronizedSet(Set)} documentation) so a copy of the + * cache is returned. + * + * @return a copy of the cached {@link java.util.Set} of queues + */ + public Set<AMQQueue> getQueueCache() + { + return new HashSet<AMQQueue>(_cache); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/SlowConsumerDetection.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/SlowConsumerDetection.java new file mode 100644 index 0000000000..5c4fe0aab8 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/SlowConsumerDetection.java @@ -0,0 +1,158 @@ +/* + * + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.virtualhost.plugins; + +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.configuration.plugins.SlowConsumerDetectionConfiguration; +import org.apache.qpid.server.configuration.plugins.SlowConsumerDetectionQueueConfiguration; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.plugins.Plugin; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.virtualhost.plugins.logging.SlowConsumerDetectionMessages; + +public class SlowConsumerDetection extends VirtualHostHouseKeepingPlugin +{ + private SlowConsumerDetectionConfiguration _config; + private ConfiguredQueueBindingListener _listener; + + public static class SlowConsumerFactory implements VirtualHostPluginFactory + { + public SlowConsumerDetection newInstance(VirtualHost vhost) + { + SlowConsumerDetectionConfiguration config = vhost.getConfiguration().getConfiguration(SlowConsumerDetectionConfiguration.class.getName()); + + if (config == null) + { + return null; + } + + SlowConsumerDetection plugin = new SlowConsumerDetection(vhost); + plugin.configure(config); + return plugin; + } + } + + /** + * Configures the slow consumer disconnect plugin by adding a listener to each exchange on this + * cirtual host to record all the configured queues in a cache for processing by the housekeeping + * thread. + * + * @see Plugin#configure(ConfigurationPlugin) + */ + public void configure(ConfigurationPlugin config) + { + _config = (SlowConsumerDetectionConfiguration) config; + _listener = new ConfiguredQueueBindingListener(getVirtualHost().getName()); + for (AMQShortString exchangeName : getVirtualHost().getExchangeRegistry().getExchangeNames()) + { + getVirtualHost().getExchangeRegistry().getExchange(exchangeName).addBindingListener(_listener); + } + } + + public SlowConsumerDetection(VirtualHost vhost) + { + super(vhost); + } + + public void execute() + { + CurrentActor.get().message(SlowConsumerDetectionMessages.RUNNING()); + + Set<AMQQueue> cache = _listener.getQueueCache(); + for (AMQQueue q : cache) + { + CurrentActor.get().message(SlowConsumerDetectionMessages.CHECKING_QUEUE(q.getName())); + + try + { + SlowConsumerDetectionQueueConfiguration config = + q.getConfiguration().getConfiguration(SlowConsumerDetectionQueueConfiguration.class.getName()); + if (checkQueueStatus(q, config)) + { + config.getPolicy().performPolicy(q); + } + } + catch (Exception e) + { + // Don't throw exceptions as this will stop the house keeping task from running. + _logger.error("Exception in SlowConsumersDetection for queue: " + q.getName(), e); + } + } + + CurrentActor.get().message(SlowConsumerDetectionMessages.COMPLETE()); + } + + public long getDelay() + { + return _config.getDelay(); + } + + public TimeUnit getTimeUnit() + { + return _config.getTimeUnit(); + } + + /** + * Check the depth,messageSize,messageAge,messageCount values for this q + * + * @param q the queue to check + * @param config the queue configuration to compare against the queue state + * + * @return true if the queue has reached a threshold. + */ + private boolean checkQueueStatus(AMQQueue q, SlowConsumerDetectionQueueConfiguration config) + { + if (config != null) + { + _logger.info("Retrieved Queue(" + q.getName() + ") Config:" + config); + + int count = q.getMessageCount(); + + // First Check message counts + if ((config.getMessageCount() != 0 && count >= config.getMessageCount()) || + // The check queue depth + (config.getDepth() != 0 && q.getQueueDepth() >= config.getDepth()) || + // finally if we have messages on the queue check Arrival time. + // We must check count as OldestArrival time is Long.MAX_LONG when + // there are no messages. + (config.getMessageAge() != 0 && + ((count > 0) && q.getOldestMessageArrivalTime() >= config.getMessageAge()))) + { + + if (_logger.isDebugEnabled()) + { + _logger.debug("Detected Slow Consumer on Queue(" + q.getName() + ")"); + _logger.debug("Queue Count:" + q.getMessageCount() + ":" + config.getMessageCount()); + _logger.debug("Queue Depth:" + q.getQueueDepth() + ":" + config.getDepth()); + _logger.debug("Queue Arrival:" + q.getOldestMessageArrivalTime() + ":" + config.getMessageAge()); + } + + return true; + } + } + return false; + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/VirtualHostHouseKeepingPlugin.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/VirtualHostHouseKeepingPlugin.java new file mode 100644 index 0000000000..3798f47f0b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/VirtualHostHouseKeepingPlugin.java @@ -0,0 +1,54 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.virtualhost.plugins; + +import org.apache.log4j.Logger; +import org.apache.qpid.server.virtualhost.HouseKeepingTask; +import org.apache.qpid.server.virtualhost.VirtualHost; + +import java.util.concurrent.TimeUnit; + +public abstract class VirtualHostHouseKeepingPlugin extends HouseKeepingTask implements VirtualHostPlugin +{ + protected final Logger _logger = Logger.getLogger(getClass()); + + public VirtualHostHouseKeepingPlugin(VirtualHost vhost) + { + super(vhost); + } + + + /** + * Long value representing the delay between repeats + * + * @return + */ + public abstract long getDelay(); + + /** + * Option to specify what the delay value represents + * + * @return + * + * @see java.util.concurrent.TimeUnit for valid value. + */ + public abstract TimeUnit getTimeUnit(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/VirtualHostPlugin.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/VirtualHostPlugin.java new file mode 100644 index 0000000000..1886c2d01d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/VirtualHostPlugin.java @@ -0,0 +1,43 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.virtualhost.plugins; + +import java.util.concurrent.TimeUnit; + +import org.apache.qpid.server.plugins.Plugin; +import org.apache.qpid.server.virtualhost.VirtualHost; + +public interface VirtualHostPlugin extends Runnable, Plugin +{ + /** + * Long value representing the delay between repeats + * + * @return + */ + public long getDelay(); + + /** + * Option to specify what the delay value represents + * @see java.util.concurrent.TimeUnit for valid value. + * @return + */ + public TimeUnit getTimeUnit(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/VirtualHostPluginFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/VirtualHostPluginFactory.java new file mode 100644 index 0000000000..c8bea18444 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/VirtualHostPluginFactory.java @@ -0,0 +1,28 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.virtualhost.plugins; + +import org.apache.qpid.server.virtualhost.VirtualHost; + +public interface VirtualHostPluginFactory +{ + public VirtualHostHouseKeepingPlugin newInstance(VirtualHost vhost); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/logging/SlowConsumerDetection_logmessages.properties b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/logging/SlowConsumerDetection_logmessages.properties new file mode 100644 index 0000000000..03c56910c2 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/logging/SlowConsumerDetection_logmessages.properties @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# +# Default File used for all non-defined locales. + +RUNNING = SCD-1001 : Running +COMPLETE = SCD-1002 : Complete +CHECKING_QUEUE = SCD-1003 : Checking Status of Queue {0}
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/logging/TopicDeletePolicy_logmessages.properties b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/logging/TopicDeletePolicy_logmessages.properties new file mode 100644 index 0000000000..ed4fb1d45a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/logging/TopicDeletePolicy_logmessages.properties @@ -0,0 +1,22 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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. +# +# Default File used for all non-defined locales. + +DELETING_QUEUE = TDP-1001 : Deleting Queue +DISCONNECTING = TDP-1002 : Disconnecting Session
\ No newline at end of file diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/policies/TopicDeletePolicy.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/policies/TopicDeletePolicy.java new file mode 100644 index 0000000000..6028f63fdb --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/policies/TopicDeletePolicy.java @@ -0,0 +1,141 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.virtualhost.plugins.policies; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.log4j.Logger; +import org.apache.qpid.AMQException; +import org.apache.qpid.protocol.AMQConstant; +import org.apache.qpid.server.binding.Binding; +import org.apache.qpid.server.configuration.plugins.SlowConsumerDetectionPolicyConfiguration; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.exchange.TopicExchange; +import org.apache.qpid.server.logging.actors.CurrentActor; +import org.apache.qpid.server.protocol.AMQSessionModel; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.virtualhost.plugins.logging.TopicDeletePolicyMessages; +import org.apache.qpid.slowconsumerdetection.policies.SlowConsumerPolicyPlugin; +import org.apache.qpid.slowconsumerdetection.policies.SlowConsumerPolicyPluginFactory; + +public class TopicDeletePolicy implements SlowConsumerPolicyPlugin +{ + Logger _logger = Logger.getLogger(TopicDeletePolicy.class); + private TopicDeletePolicyConfiguration _configuration; + + public static class TopicDeletePolicyFactory implements SlowConsumerPolicyPluginFactory + { + public TopicDeletePolicy newInstance(ConfigurationPlugin configuration) throws ConfigurationException + { + TopicDeletePolicyConfiguration config = + configuration.getConfiguration(TopicDeletePolicyConfiguration.class.getName()); + + TopicDeletePolicy policy = new TopicDeletePolicy(); + policy.configure(config); + return policy; + } + + public String getPluginName() + { + return "topicdelete"; + } + + public Class<TopicDeletePolicy> getPluginClass() + { + return TopicDeletePolicy.class; + } + } + + public void performPolicy(AMQQueue q) + { + if (q == null) + { + return; + } + + AMQSessionModel owner = q.getExclusiveOwningSession(); + + // Only process exclusive queues + if (owner == null) + { + return; + } + + //Only process Topics + if (!validateQueueIsATopic(q)) + { + return; + } + + try + { + CurrentActor.get().message(owner.getLogSubject(),TopicDeletePolicyMessages.DISCONNECTING()); + // Close the consumer . this will cause autoDelete Queues to be purged + owner.getConnectionModel(). + closeSession(owner, AMQConstant.RESOURCE_ERROR, + "Consuming to slow."); + + // Actively delete non autoDelete queues if deletePersistent is set + if (!q.isAutoDelete() && (_configuration != null && _configuration.deletePersistent())) + { + CurrentActor.get().message(q.getLogSubject(), TopicDeletePolicyMessages.DELETING_QUEUE()); + q.delete(); + } + + } + catch (AMQException e) + { + _logger.warn("Unable to close consumer:" + owner + ", on queue:" + q.getName()); + } + + } + + /** + * Check the queue bindings to validate the queue is bound to the + * topic exchange. + * + * @param q the Queue + * + * @return true iff Q is bound to a TopicExchange + */ + private boolean validateQueueIsATopic(AMQQueue q) + { + for (Binding binding : q.getBindings()) + { + if (binding.getExchange() instanceof TopicExchange) + { + return true; + } + } + + return false; + } + + public void configure(ConfigurationPlugin config) + { + _configuration = (TopicDeletePolicyConfiguration) config; + } + + @Override + public String toString() + { + return "TopicDelete" + (_configuration == null ? "" : "[" + _configuration + "]"); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/policies/TopicDeletePolicyConfiguration.java b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/policies/TopicDeletePolicyConfiguration.java new file mode 100644 index 0000000000..7dfd22c733 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/server/virtualhost/plugins/policies/TopicDeletePolicyConfiguration.java @@ -0,0 +1,81 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.server.virtualhost.plugins.policies; + +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.server.configuration.plugins.ConfigurationPlugin; +import org.apache.qpid.server.configuration.plugins.ConfigurationPluginFactory; + +public class TopicDeletePolicyConfiguration extends ConfigurationPlugin +{ + + public static class TopicDeletePolicyConfigurationFactory + implements ConfigurationPluginFactory + { + public ConfigurationPlugin newInstance(String path, + Configuration config) + throws ConfigurationException + { + TopicDeletePolicyConfiguration slowConsumerConfig = + new TopicDeletePolicyConfiguration(); + slowConsumerConfig.setConfiguration(path, config); + return slowConsumerConfig; + } + + public List<String> getParentPaths() + { + return Arrays.asList( + "virtualhosts.virtualhost.queues.slow-consumer-detection.policy.topicDelete", + "virtualhosts.virtualhost.queues.queue.slow-consumer-detection.policy.topicDelete", + "virtualhosts.virtualhost.topics.slow-consumer-detection.policy.topicDelete", + "virtualhosts.virtualhost.topics.topic.slow-consumer-detection.policy.topicDelete"); + } + } + + public String[] getElementsProcessed() + { + return new String[]{"delete-persistent"}; + } + + @Override + public void validateConfiguration() throws ConfigurationException + { + // No validation required. + } + + public boolean deletePersistent() + { + // If we don't have configuration then we don't deletePersistent Queues + return (hasConfiguration() && contains("delete-persistent")); + } + + @Override + public String formatToString() + { + return (deletePersistent()?"delete-durable":""); + } + + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/slowconsumerdetection/policies/SlowConsumerPolicyPlugin.java b/qpid/java/broker/src/main/java/org/apache/qpid/slowconsumerdetection/policies/SlowConsumerPolicyPlugin.java new file mode 100644 index 0000000000..7f600abdc9 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/slowconsumerdetection/policies/SlowConsumerPolicyPlugin.java @@ -0,0 +1,29 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.slowconsumerdetection.policies; + +import org.apache.qpid.server.plugins.Plugin; +import org.apache.qpid.server.queue.AMQQueue; + +public interface SlowConsumerPolicyPlugin extends Plugin +{ + public void performPolicy(AMQQueue Queue); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/slowconsumerdetection/policies/SlowConsumerPolicyPluginFactory.java b/qpid/java/broker/src/main/java/org/apache/qpid/slowconsumerdetection/policies/SlowConsumerPolicyPluginFactory.java new file mode 100644 index 0000000000..b2fe6766a6 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/slowconsumerdetection/policies/SlowConsumerPolicyPluginFactory.java @@ -0,0 +1,27 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.slowconsumerdetection.policies; + +import org.apache.qpid.server.plugins.PluginFactory; + +public interface SlowConsumerPolicyPluginFactory<P extends SlowConsumerPolicyPlugin> extends PluginFactory<P> +{ +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/MessageStoreTool.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/MessageStoreTool.java new file mode 100644 index 0000000000..dca165fa7e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/MessageStoreTool.java @@ -0,0 +1,652 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.tools.messagestore; + +import org.apache.commons.cli.Option; +import org.apache.commons.cli.OptionBuilder; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.qpid.configuration.Configuration; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.registry.ConfigurationFileApplicationRegistry; +import org.apache.qpid.server.store.MemoryMessageStore; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.tools.messagestore.commands.Clear; +import org.apache.qpid.tools.messagestore.commands.Command; +import org.apache.qpid.tools.messagestore.commands.Copy; +import org.apache.qpid.tools.messagestore.commands.Dump; +import org.apache.qpid.tools.messagestore.commands.Help; +import org.apache.qpid.tools.messagestore.commands.List; +import org.apache.qpid.tools.messagestore.commands.Load; +import org.apache.qpid.tools.messagestore.commands.Quit; +import org.apache.qpid.tools.messagestore.commands.Select; +import org.apache.qpid.tools.messagestore.commands.Show; +import org.apache.qpid.tools.messagestore.commands.Move; +import org.apache.qpid.tools.messagestore.commands.Purge; +import org.apache.qpid.tools.utils.CommandParser; +import org.apache.qpid.tools.utils.Console; +import org.apache.qpid.tools.utils.SimpleCommandParser; +import org.apache.qpid.tools.utils.SimpleConsole; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.StringTokenizer; + +/** + * MessageStoreTool. + */ +public class MessageStoreTool +{ + /** Text outputted at the start of each console.*/ + private static final String BOILER_PLATE = "MessageStoreTool - for examining Persistent Qpid Broker MessageStore instances"; + + /** I/O Wrapper. */ + protected Console _console; + + /** Batch mode flag. */ + protected boolean _batchMode; + + /** Internal State object. */ + private State _state = new State(); + + private HashMap<String, Command> _commands = new HashMap<String, Command>(); + + /** SLF4J Logger. */ + private static Logger _devlog = LoggerFactory.getLogger(MessageStoreTool.class); + + /** Loaded configuration file. */ + private Configuration _config; + + /** Control used for main run loop. */ + private boolean _running = true; + private boolean _initialised = false; + + //---------------------------------------------------------------------------------------------------/ + + public static void main(String[] args) throws Configuration.InitException + { + + MessageStoreTool tool = new MessageStoreTool(args); + + tool.start(); + } + + + public MessageStoreTool(String[] args) throws Configuration.InitException + { + this(args, System.in, System.out); + } + + public MessageStoreTool(String[] args, InputStream in, OutputStream out) throws Configuration.InitException + { + BufferedReader consoleReader = new BufferedReader(new InputStreamReader(in)); + BufferedWriter consoleWriter = new BufferedWriter(new OutputStreamWriter(out)); + + Runtime.getRuntime().addShutdownHook(new Thread(new ShutdownHook(this))); + _batchMode = false; + + _console = new SimpleConsole(consoleWriter, consoleReader); + + _config = new Configuration(); + + setOptions(); + _config.processCommandline(args); + } + + + private void setOptions() + { + Option help = new Option("h", "help", false, "print this message"); + Option version = new Option("v", "version", false, "print the version information and exit"); + Option configFile = + OptionBuilder.withArgName("file").hasArg() + .withDescription("use given configuration file By " + + "default looks for a file named " + + Configuration.DEFAULT_CONFIG_FILE + " in " + Configuration.QPID_HOME) + .withLongOpt("config") + .create("c"); + + _config.setOption(help); + _config.setOption(version); + _config.setOption(configFile); + } + + public State getState() + { + return _state; + } + + public Map<String, Command> getCommands() + { + return _commands; + } + + public void setConfigurationFile(String configfile) throws Configuration.InitException + { + _config.loadConfig(new File(configfile)); + setup(); + } + + public Console getConsole() + { + return _console; + } + + public void setConsole(Console console) + { + _console = console; + } + + /** + * Simple ShutdownHook to cleanly shutdown the databases + */ + static class ShutdownHook implements Runnable + { + MessageStoreTool _tool; + + ShutdownHook(MessageStoreTool messageStoreTool) + { + _tool = messageStoreTool; + } + + public void run() + { + _tool.quit(); + } + } + + public void quit() + { + _running = false; + + if (_initialised) + { + ApplicationRegistry.remove(1); + } + + _console.println("...exiting"); + + _console.close(); + } + + public void setBatchMode(boolean batchmode) + { + _batchMode = batchmode; + } + + /** + * Main loop + */ + protected void start() + { + setup(); + + if (!_initialised) + { + System.exit(1); + } + + _console.println(""); + + _console.println(BOILER_PLATE); + + runCLI(); + } + + private void setup() + { + loadDefaultVirtualHosts(); + + loadCommands(); + + _state.clearAll(); + } + + private void loadCommands() + { + _commands.clear(); + //todo Dynamically load the classes that exis in com.redhat.etp.qpid.commands + _commands.put("close", new Clear(this)); + _commands.put("copy", new Copy(this)); + _commands.put("dump", new Dump(this)); + _commands.put("help", new Help(this)); + _commands.put("list", new List(this)); + _commands.put("load", new Load(this)); + _commands.put("move", new Move(this)); + _commands.put("purge", new Purge(this)); + _commands.put("quit", new Quit(this)); + _commands.put("select", new Select(this)); + _commands.put("show", new Show(this)); + } + + private void loadDefaultVirtualHosts() + { + final File configFile = _config.getConfigFile(); + + loadVirtualHosts(configFile); + } + + private void loadVirtualHosts(File configFile) + { + + if (!configFile.exists()) + { + _devlog.error("Config file not found:" + configFile.getAbsolutePath()); + return; + } + else + { + _devlog.debug("using config file :" + configFile.getAbsolutePath()); + } + + try + { + ConfigurationFileApplicationRegistry registry = new ConfigurationFileApplicationRegistry(configFile); + + ApplicationRegistry.remove(1); + + ApplicationRegistry.initialise(registry); + + checkMessageStores(); + _initialised = true; + } + catch (ConfigurationException e) + { + _console.println("Unable to load configuration due to configuration error: " + e.getMessage()); + e.printStackTrace(); + } + catch (Exception e) + { + _console.println("Unable to load configuration due to: " + e.getMessage()); + e.printStackTrace(); + } + + + } + + private void checkMessageStores() + { + Collection<VirtualHost> vhosts = ApplicationRegistry.getInstance().getVirtualHostRegistry().getVirtualHosts(); + + boolean warning = false; + for (VirtualHost vhost : vhosts) + { + if (vhost.getMessageStore() instanceof MemoryMessageStore) + { + _console.println("WARNING: Virtualhost '" + vhost.getName() + "' is using a MemoryMessageStore. " + + "Changes will not persist."); + warning = true; + } + } + + if (warning) + { + _console.println(""); + _console.println("Please ensure you are using the correct config file currently using '" + + _config.getConfigFile().getAbsolutePath() + "'"); + _console.println("New config file can be specifed by 'load <config file>' or -c on the commandline."); + _console.println(""); + } + } + + private void runCLI() + { + while (_running) + { + if (!_batchMode) + { + printPrompt(); + } + + String[] args = _console.readCommand(); + + while (args != null) + { + exec(args); + + if (_running) + { + if (!_batchMode) + { + printPrompt(); + } + + args = _console.readCommand(); + } + } + } + } + + private void printPrompt() + { + _console.print(prompt()); + } + + + /** + * Execute a script (batch mode). + * + * @param script The file script + */ + protected void runScripts(String script) + { + //Store Current State + boolean oldBatch = _batchMode; + CommandParser oldParser = _console.getCommandParser(); + setBatchMode(true); + + try + { + _devlog.debug("Running script '" + script + "'"); + + _console.setCommandParser(new SimpleCommandParser(new BufferedReader(new FileReader(script)))); + + start(); + } + catch (java.io.FileNotFoundException e) + { + _devlog.error("Script not found: '" + script + "' due to:" + e.getMessage()); + } + + //Restore previous state + _console.setCommandParser(oldParser); + setBatchMode(oldBatch); + } + + public String prompt() + { + String state = _state.toString(); + if (state != null && state.length() != 0) + { + return state + ":bdb$ "; + } + else + { + return "bdb$ "; + } + } + + /** + * Execute the command. + * + * @param args [command, arg0, arg1...]. + */ + protected void exec(String[] args) + { + // Comment lines start with a # + if (args.length == 0 || args[0].startsWith("#")) + { + return; + } + + final String command = args[0]; + + Command cmd = _commands.get(command); + + if (cmd == null) + { + _console.println("Command not understood: " + command); + } + else + { + cmd.execute(args); + } + } + + + /** + * Displays usage info. + */ + protected static void help() + { + System.out.println(BOILER_PLATE); + System.out.println("Usage: java " + MessageStoreTool.class + " [Options]"); + System.out.println(" [-c <broker config file>] : Defaults to \"$QPID_HOME/etc/config.xml\""); + } + + + /** + * This class is used to store the current state of the tool. + * + * This is then interrogated by the various commands to augment their behaviour. + * + * + */ + public static class State + { + private VirtualHost _vhost = null; + private AMQQueue _queue = null; + private Exchange _exchange = null; + private java.util.List<Long> _msgids = null; + + public State() + { + } + + public void setQueue(AMQQueue queue) + { + _queue = queue; + } + + public AMQQueue getQueue() + { + return _queue; + } + + public void setVhost(VirtualHost vhost) + { + _vhost = vhost; + } + + public VirtualHost getVhost() + { + return _vhost; + } + + public Exchange getExchange() + { + return _exchange; + } + + public void setExchange(Exchange exchange) + { + _exchange = exchange; + } + + public String toString() + { + StringBuilder status = new StringBuilder(); + + if (_vhost != null) + { + status.append(_vhost.getName()); + + if (_exchange != null) + { + status.append("["); + status.append(_exchange.getNameShortString()); + status.append("]"); + + if (_queue != null) + { + status.append("->'"); + status.append(_queue.getNameShortString()); + status.append("'"); + + if (_msgids != null) + { + status.append(printMessages()); + } + } + } + } + + return status.toString(); + } + + + public String printMessages() + { + StringBuilder sb = new StringBuilder(); + + Long previous = null; + + Long start = null; + for (Long id : _msgids) + { + if (previous != null) + { + if (id == previous + 1) + { + if (start == null) + { + start = previous; + } + } + else + { + if (start != null) + { + sb.append(","); + sb.append(start); + sb.append("-"); + sb.append(id); + start = null; + } + else + { + sb.append(","); + sb.append(previous); + } + } + } + + previous = id; + } + + if (start != null) + { + sb.append(","); + sb.append(start); + sb.append("-"); + sb.append(_msgids.get(_msgids.size() - 1)); + } + else + { + sb.append(","); + sb.append(previous); + } + + // surround list in () + sb.replace(0, 1, "("); + sb.append(")"); + return sb.toString(); + } + + public void clearAll() + { + _vhost = null; + clearExchange(); + } + + public void clearExchange() + { + _exchange = null; + clearQueue(); + } + + public void clearQueue() + { + _queue = null; + clearMessages(); + } + + public void clearMessages() + { + _msgids = null; + } + + /** + * A common location to provide parsing of the message id string + * utilised by a number of the commands. + * The String is comma separated list of ids that can be individual ids + * or a range (4-10) + * + * @param msgString string of msg ids to parse 1,2,4-10 + */ + public void setMessages(String msgString) + { + StringTokenizer tok = new StringTokenizer(msgString, ","); + + if (tok.hasMoreTokens()) + { + _msgids = new LinkedList<Long>(); + } + + while (tok.hasMoreTokens()) + { + String next = tok.nextToken(); + if (next.contains("-")) + { + Long start = Long.parseLong(next.substring(0, next.indexOf("-"))); + Long end = Long.parseLong(next.substring(next.indexOf("-") + 1)); + + if (end >= start) + { + for (long l = start; l <= end; l++) + { + _msgids.add(l); + } + } + } + else + { + _msgids.add(Long.parseLong(next)); + } + } + + } + + public void setMessages(java.util.List<Long> msgids) + { + _msgids = msgids; + } + + public java.util.List<Long> getMessages() + { + return _msgids; + } + }//Class State + +}//Class MessageStoreTool diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/AbstractCommand.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/AbstractCommand.java new file mode 100644 index 0000000000..5444197cb4 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/AbstractCommand.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.tools.messagestore.commands; + +import org.apache.qpid.tools.messagestore.MessageStoreTool; +import org.apache.qpid.tools.utils.Console; + +public abstract class AbstractCommand implements Command +{ + protected Console _console; + protected MessageStoreTool _tool; + + public AbstractCommand(MessageStoreTool tool) + { + _console = tool.getConsole(); + _tool = tool; + } + + public void setOutput(Console out) + { + _console = out; + } + + protected void commandError(String message, String[] args) + { + _console.print(getCommand() + " : " + message); + + if (args != null) + { + for (int i = 1; i < args.length; i++) + { + _console.print(args[i]); + } + } + _console.println(""); + _console.println(help()); + } + + + public abstract String help(); + + public abstract String usage(); + + public abstract String getCommand(); + + + public abstract void execute(String... args); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Clear.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Clear.java new file mode 100644 index 0000000000..b0006b3fe6 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Clear.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.tools.messagestore.commands; + +import org.apache.qpid.tools.messagestore.MessageStoreTool; + +public class Clear extends AbstractCommand +{ + public Clear(MessageStoreTool tool) + { + super(tool); + } + + public String help() + { + return "Clears any selection."; + } + + public String usage() + { + return "clear [ all | virtualhost | exchange | queue | msgs ]"; + } + + public String getCommand() + { + return "clear"; + } + + public void execute(String... args) + { + assert args.length > 0; + assert args[0].equals(getCommand()); + + if (args.length < 1) + { + doClose("all"); + } + else + { + doClose(args[1]); + } + } + + private void doClose(String type) + { + if (type.equals("virtualhost") + || type.equals("all")) + { + _tool.getState().clearAll(); + } + + if (type.equals("exchange")) + { + _tool.getState().clearExchange(); + } + + if (type.equals("queue")) + { + _tool.getState().clearQueue(); + } + + if (type.equals("msgs")) + { + _tool.getState().clearMessages(); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Command.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Command.java new file mode 100644 index 0000000000..bfa775a34a --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Command.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.tools.messagestore.commands; + +import org.apache.qpid.tools.utils.Console; + +public interface Command +{ + public void setOutput(Console out); + + public String help(); + + public abstract String usage(); + + String getCommand(); + + public void execute(String... args); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Copy.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Copy.java new file mode 100644 index 0000000000..348c95572d --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Copy.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.tools.messagestore.commands; + +import org.apache.qpid.tools.messagestore.MessageStoreTool; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.txn.LocalTransaction; + +public class Copy extends Move +{ + public Copy(MessageStoreTool tool) + { + super(tool); + } + + public String help() + { + return "Copy messages between queues.";/*\n" + + "The currently selected message set will be copied to the specifed queue.\n" + + "Alternatively the values can be provided on the command line."; */ + } + + public String usage() + { + return "copy to=<queue> [from=<queue>] [msgids=<msgids eg, 1,2,4-10>]"; + } + + public String getCommand() + { + return "copy"; + } + + protected void doCommand(AMQQueue fromQueue, long start, long end, AMQQueue toQueue) + { + ServerTransaction txn = new LocalTransaction(fromQueue.getVirtualHost().getTransactionLog()); + fromQueue.copyMessagesToAnotherQueue(start, end, toQueue.getNameShortString().toString(), txn); + txn.commit(); + } + +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Dump.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Dump.java new file mode 100644 index 0000000000..8bb5d02b01 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Dump.java @@ -0,0 +1,305 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.tools.messagestore.commands; + +import org.apache.commons.codec.binary.Hex; +import org.apache.qpid.server.queue.QueueEntryImpl; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.tools.messagestore.MessageStoreTool; +import org.apache.qpid.tools.utils.Console; + +import java.io.UnsupportedEncodingException; +import java.util.LinkedList; +import java.util.List; + +public class Dump extends Show +{ + private static final int LINE_SIZE = 8; + private static final String DEFAULT_ENCODING = "utf-8"; + private static final boolean SPACE_BYTES = true; + private static final String BYTE_SPACER = " "; + private static final String NON_PRINTING_ASCII_CHAR = "?"; + + protected boolean _content = true; + + public Dump(MessageStoreTool tool) + { + super(tool); + } + + public String help() + { + return "Dump selected message content. Default: show=content"; + } + + public String usage() + { + return getCommand() + " [show=[all],[msgheaders],[_amqHeaders],[routing],[content]] [id=<msgid e.g. 1,2,4-10>]"; + } + + public String getCommand() + { + return "dump"; + } + + public void execute(String... args) + { + assert args.length > 0; + assert args[0].equals(getCommand()); + + + if (args.length >= 2) + { + for (String arg : args) + { + if (arg.startsWith("show=")) + { + _content = arg.contains("content") || arg.contains("all"); + } + } + + parseArgs(args); + } + + performShow(); + } + + + protected List<List> createMessageData(java.util.List<Long> msgids, List<QueueEntry> messages, boolean showHeaders, boolean showRouting, + boolean showMessageHeaders) + { + + List<List> display = new LinkedList<List>(); + + List<String> hex = new LinkedList<String>(); + List<String> ascii = new LinkedList<String>(); + display.add(hex); + display.add(ascii); + + for (QueueEntry entry : messages) + { + ServerMessage msg = entry.getMessage(); + if (!includeMsg(msg, msgids)) + { + continue; + } + + //Add divider between messages + hex.add(Console.ROW_DIVIDER); + ascii.add(Console.ROW_DIVIDER); + + // Show general message information + hex.add(Show.Columns.ID.name()); + ascii.add(msg.getMessageNumber().toString()); + + hex.add(Console.ROW_DIVIDER); + ascii.add(Console.ROW_DIVIDER); + + if (showRouting) + { + addShowInformation(hex, ascii, msg, "Routing Details", true, false, false); + } + if (showHeaders) + { + addShowInformation(hex, ascii, msg, "Headers", false, true, false); + } + if (showMessageHeaders) + { + addShowInformation(hex, ascii, msg, null, false, false, true); + } + + // Add Content Body section + hex.add("Content Body"); + ascii.add(""); + hex.add(Console.ROW_DIVIDER); + ascii.add(Console.ROW_DIVIDER); + + + final int messageSize = (int) msg.getSize(); + if (messageSize != 0) + { + hex.add("Hex"); + hex.add(Console.ROW_DIVIDER); + + + ascii.add("ASCII"); + ascii.add(Console.ROW_DIVIDER); + + java.nio.ByteBuffer buf = java.nio.ByteBuffer.allocate(64 * 1024); + + int position = 0; + + while(position < messageSize) + { + + position += msg.getContent(buf, position); + buf.flip(); + //Duplicate so we don't destroy original data :) + java.nio.ByteBuffer hexBuffer = buf; + + java.nio.ByteBuffer charBuffer = hexBuffer.duplicate(); + + Hex hexencoder = new Hex(); + + while (hexBuffer.hasRemaining()) + { + byte[] line = new byte[LINE_SIZE]; + + int bufsize = hexBuffer.remaining(); + if (bufsize < LINE_SIZE) + { + hexBuffer.get(line, 0, bufsize); + } + else + { + bufsize = line.length; + hexBuffer.get(line); + } + + byte[] encoded = hexencoder.encode(line); + + try + { + String encStr = new String(encoded, 0, bufsize * 2, DEFAULT_ENCODING); + String hexLine = ""; + + int strLength = encStr.length(); + for (int c = 0; c < strLength; c++) + { + hexLine += encStr.charAt(c); + + if ((c & 1) == 1 && SPACE_BYTES) + { + hexLine += BYTE_SPACER; + } + } + + hex.add(hexLine); + } + catch (UnsupportedEncodingException e) + { + _console.println(e.getMessage()); + return null; + } + } + + while (charBuffer.hasRemaining()) + { + String asciiLine = ""; + + for (int pos = 0; pos < LINE_SIZE; pos++) + { + if (charBuffer.hasRemaining()) + { + byte ch = charBuffer.get(); + + if (isPrintable(ch)) + { + asciiLine += (char) ch; + } + else + { + asciiLine += NON_PRINTING_ASCII_CHAR; + } + + if (SPACE_BYTES) + { + asciiLine += BYTE_SPACER; + } + } + else + { + break; + } + } + + ascii.add(asciiLine); + } + buf.clear(); + } + } + else + { + List<String> result = new LinkedList<String>(); + + display.add(result); + result.add("No ContentBodies"); + } + } + + // if hex is empty then we have no data to display + if (hex.size() == 0) + { + return null; + } + + return display; + } + + private void addShowInformation(List<String> column1, List<String> column2, ServerMessage msg, + String title, boolean routing, boolean headers, boolean messageHeaders) + { + List<QueueEntry> single = new LinkedList<QueueEntry>(); + single.add(new QueueEntryImpl(null,msg, Long.MIN_VALUE)); + + List<List> routingData = super.createMessageData(null, single, headers, routing, messageHeaders); + + //Reformat data + if (title != null) + { + column1.add(title); + column2.add(""); + column1.add(Console.ROW_DIVIDER); + column2.add(Console.ROW_DIVIDER); + } + + // look at all columns in the routing Data + for (List item : routingData) + { + // the item should be: + // Title + // *divider + // value + // otherwise we can't reason about the correct value + if (item.size() == 3) + { + //Filter out the columns we are not interested in. + + String columnName = item.get(0).toString(); + + if (!(columnName.equals(Show.Columns.ID.name()) + || columnName.equals(Show.Columns.Size.name()))) + { + column1.add(columnName); + column2.add(item.get(2).toString()); + } + } + } + column1.add(Console.ROW_DIVIDER); + column2.add(Console.ROW_DIVIDER); + } + + private boolean isPrintable(byte c) + { + return c > 31 && c < 127; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Help.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Help.java new file mode 100644 index 0000000000..0f9546541b --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Help.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.tools.messagestore.commands; + +import org.apache.qpid.tools.messagestore.MessageStoreTool; +import org.apache.qpid.tools.utils.Console; + +import java.util.LinkedList; +import java.util.Map; + +public class Help extends AbstractCommand +{ + public Help(MessageStoreTool tool) + { + super(tool); + } + + public String help() + { + return "Provides detailed help on commands."; + } + + public String getCommand() + { + return "help"; + } + + public String usage() + { + return "help [<command>]"; + } + + public void execute(String... args) + { + assert args.length > 0; + assert args[0].equals(getCommand()); + + if (args.length > 1) + { + Command command = _tool.getCommands().get(args[1]); + if (command != null) + { + _console.println(command.help()); + _console.println("Usage:" + command.usage()); + } + else + { + commandError("Command not found: ", args); + } + } + else + { + java.util.List<java.util.List> data = new LinkedList<java.util.List>(); + + java.util.List<String> commandName = new LinkedList<String>(); + java.util.List<String> commandDescription = new LinkedList<String>(); + + data.add(commandName); + data.add(commandDescription); + + //Set up Headers + commandName.add("Command"); + commandDescription.add("Description"); + + commandName.add(Console.ROW_DIVIDER); + commandDescription.add(Console.ROW_DIVIDER); + + //Add current Commands with descriptions + Map<String, Command> commands = _tool.getCommands(); + + for (Command command : commands.values()) + { + commandName.add(command.getCommand()); + commandDescription.add(command.help()); + } + + _console.printMap("Available Commands", data); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/List.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/List.java new file mode 100644 index 0000000000..3c4a0c8fac --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/List.java @@ -0,0 +1,314 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.tools.messagestore.commands; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.tools.messagestore.MessageStoreTool; +import org.apache.qpid.tools.utils.Console; + +import java.util.Collection; +import java.util.LinkedList; + +public class List extends AbstractCommand +{ + + public List(MessageStoreTool tool) + { + super(tool); + } + + public void setOutput(Console out) + { + _console = out; + } + + public String help() + { + return "list available items."; + } + + public String usage() + { + return "list queues [<exchange>] | exchanges | bindings [<exchange>] | all"; + } + + public String getCommand() + { + return "list"; + } + + public void execute(String... args) + { + assert args.length > 0; + assert args[0].equals(getCommand()); + + if (args.length > 1) + { + if ((args[1].equals("exchanges")) + || (args[1].equals("queues")) + || (args[1].equals("bindings")) + || (args[1].equals("all"))) + { + if (args.length == 2) + { + doList(args[1]); + } + else if (args.length == 3) + { + doList(args[1], args[2]); + } + } + else + { + commandError("Unknown options. ", args); + } + } + else if (args.length < 2) + { + doList("all"); + } + else + { + doList(args[1]); + } + } + + private void doList(String... listItem) + { + if (_tool.getState().getVhost() == null) + { + _console.println("No Virtualhost open. Open a Virtualhost first."); + listVirtualHosts(); + return; + } + + VirtualHost vhost = _tool.getState().getVhost(); + + java.util.List<String> data = null; + + if (listItem[0].equals("queues")) + { + if (listItem.length > 1) + { + data = listQueues(vhost, new AMQShortString(listItem[1])); + } + else + { + Exchange exchange = _tool.getState().getExchange(); + data = listQueues(vhost, exchange); + } + } + + if (listItem[0].equals("exchanges")) + { + data = listExchanges(vhost); + } + + if (listItem[0].equals("bindings")) + { + + if (listItem.length > 1) + { + data = listBindings(vhost, new AMQShortString(listItem[1])); + } + else + { + Exchange exchange = _tool.getState().getExchange(); + + data = listBindings(vhost, exchange); + } + } + + if (data != null) + { + if (data.size() == 1) + { + _console.println("No '" + listItem[0] + "' to display,"); + } + else + { + _console.displayList(true, data.toArray(new String[0])); + } + } + + + if (listItem[0].equals("all")) + { + + boolean displayed = false; + Exchange exchange = _tool.getState().getExchange(); + + //Do the display here for each one so that they are pretty printed + data = listQueues(vhost, exchange); + if (data != null) + { + displayed = true; + _console.displayList(true, data.toArray(new String[0])); + } + + if (exchange == null) + { + data = listExchanges(vhost); + if (data != null) + { + displayed = true; + _console.displayList(true, data.toArray(new String[0])); + } + } + + data = listBindings(vhost, exchange); + if (data != null) + { + displayed = true; + _console.displayList(true, data.toArray(new String[0])); + } + + if (!displayed) + { + _console.println("Nothing to list"); + } + } + } + + private void listVirtualHosts() + { + Collection<VirtualHost> vhosts = ApplicationRegistry.getInstance() + .getVirtualHostRegistry().getVirtualHosts(); + + String[] data = new String[vhosts.size() + 1]; + + data[0] = "Available VirtualHosts"; + + int index = 1; + for (VirtualHost vhost : vhosts) + { + data[index] = vhost.getName(); + index++; + } + + _console.displayList(true, data); + } + + private java.util.List<String> listBindings(VirtualHost vhost, AMQShortString exchangeName) + { + return listBindings(vhost, vhost.getExchangeRegistry().getExchange(exchangeName)); + } + + private java.util.List<String> listBindings(VirtualHost vhost, Exchange exchange) + { + Collection<AMQShortString> queues = vhost.getQueueRegistry().getQueueNames(); + + if (queues == null || queues.size() == 0) + { + return null; + } + + java.util.List<String> data = new LinkedList<String>(); + + data.add("Current Bindings"); + + for (AMQShortString queue : queues) + { + if (exchange != null) + { + if (exchange.isBound(queue)) + { + data.add(queue.toString()); + } + } + else + { + data.add(queue.toString()); + } + } + + return data; + } + + private java.util.List<String> listExchanges(VirtualHost vhost) + { + Collection<AMQShortString> queues = vhost.getExchangeRegistry().getExchangeNames(); + + if (queues == null || queues.size() == 0) + { + return null; + } + + java.util.List<String> data = new LinkedList<String>(); + + data.add("Available Exchanges"); + + for (AMQShortString queue : queues) + { + data.add(queue.toString()); + } + + return data; + } + + private java.util.List<String> listQueues(VirtualHost vhost, AMQShortString exchangeName) + { + return listQueues(vhost, vhost.getExchangeRegistry().getExchange(exchangeName)); + } + + private java.util.List<String> listQueues(VirtualHost vhost, Exchange exchange) + { + Collection<AMQQueue> queues = vhost.getQueueRegistry().getQueues(); + + if (queues == null || queues.size() == 0) + { + return null; + } + + java.util.List<String> data = new LinkedList<String>(); + + data.add("Available Queues"); + + for (AMQQueue queue : queues) + { + if (exchange != null) + { + if (exchange.isBound(queue)) + { + data.add(queue.getNameShortString().toString()); + } + } + else + { + data.add(queue.getNameShortString().toString()); + } + } + + if (exchange != null) + { + if (queues.size() == 1) + { + return null; + } + } + + return data; + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Load.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Load.java new file mode 100644 index 0000000000..244a311c30 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Load.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.tools.messagestore.commands; + +import org.apache.qpid.configuration.Configuration; +import org.apache.qpid.tools.messagestore.MessageStoreTool; + +public class Load extends AbstractCommand +{ + public Load(MessageStoreTool tool) + { + super(tool); + } + + public String help() + { + return "Loads specified broker configuration file."; + } + + public String usage() + { + return "load <configuration file>"; + } + + public String getCommand() + { + return "load"; + } + + public void execute(String... args) + { + assert args.length > 0; + assert args[0].equals(getCommand()); + + if (args.length > 2) + { + _console.print("load " + args[1] + ": additional options not understood:"); + for (int i = 2; i < args.length; i++) + { + _console.print(args[i] + " "); + } + _console.println(""); + } + else if (args.length < 2) + { + _console.println("Enter Configuration file."); + String input = _console.readln(); + if (input != null) + { + doLoad(input); + } + else + { + _console.println("Did not recognise config file."); + } + } + else + { + doLoad(args[1]); + } + } + + private void doLoad(String configfile) + { + _console.println("Loading Configuration:" + configfile); + + try + { + _tool.setConfigurationFile(configfile); + } + catch (Configuration.InitException e) + { + _console.println("Unable to open config file due to: '" + e.getMessage() + "'"); + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Move.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Move.java new file mode 100644 index 0000000000..615f6ec1c2 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Move.java @@ -0,0 +1,202 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.tools.messagestore.commands; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.txn.ServerTransaction; +import org.apache.qpid.server.txn.LocalTransaction; +import org.apache.qpid.tools.messagestore.MessageStoreTool; + +import java.util.LinkedList; +import java.util.List; + +public class Move extends AbstractCommand +{ + + public Move(MessageStoreTool tool) + { + super(tool); + } + + public String help() + { + return "Move messages between queues.";/*\n" + + "The currently selected message set will be moved to the specifed queue.\n" + + "Alternatively the values can be provided on the command line.";*/ + } + + public String usage() + { + return "move to=<queue> [from=<queue>] [msgids=<msgids eg, 1,2,4-10>]"; + } + + public String getCommand() + { + return "move"; + } + + public void execute(String... args) + { + AMQQueue toQueue = null; + AMQQueue fromQueue = _tool.getState().getQueue(); + java.util.List<Long> msgids = _tool.getState().getMessages(); + + if (args.length >= 2) + { + for (String arg : args) + { + if (arg.startsWith("to=")) + { + String queueName = arg.substring(arg.indexOf("=") + 1); + toQueue = _tool.getState().getVhost().getQueueRegistry().getQueue(new AMQShortString(queueName)); + } + + if (arg.startsWith("from=")) + { + String queueName = arg.substring(arg.indexOf("=") + 1); + fromQueue = _tool.getState().getVhost().getQueueRegistry().getQueue(new AMQShortString(queueName)); + } + + if (arg.startsWith("msgids=")) + { + String msgidStr = arg.substring(arg.indexOf("=") + 1); + + // Record the current message selection + java.util.List<Long> currentIDs = _tool.getState().getMessages(); + + // Use the ToolState class to perform the messasge parsing + _tool.getState().setMessages(msgidStr); + msgids = _tool.getState().getMessages(); + + // Reset the original selection of messages + _tool.getState().setMessages(currentIDs); + } + } + } + + if (!checkRequirements(fromQueue, toQueue, msgids)) + { + return; + } + + processIDs(fromQueue, toQueue, msgids); + } + + private void processIDs(AMQQueue fromQueue, AMQQueue toQueue, java.util.List<Long> msgids) + { + Long previous = null; + Long start = null; + + if (msgids == null) + { + msgids = allMessageIDs(fromQueue); + } + + if (msgids == null || msgids.size() == 0) + { + _console.println("No Messages to move."); + return; + } + + for (long id : msgids) + { + if (previous != null) + { + if (id == previous + 1) + { + if (start == null) + { + start = previous; + } + } + else + { + if (start != null) + { + //move a range of ids + doCommand(fromQueue, start, id, toQueue); + start = null; + } + else + { + //move a single id + doCommand(fromQueue, id, id, toQueue); + } + } + } + + previous = id; + } + + if (start != null) + { + //move a range of ids + doCommand(fromQueue, start, previous, toQueue); + } + } + + private List<Long> allMessageIDs(AMQQueue fromQueue) + { + List<Long> ids = new LinkedList<Long>(); + + if (fromQueue != null) + { + List<QueueEntry> messages = fromQueue.getMessagesOnTheQueue(); + if (messages != null) + { + for (QueueEntry msg : messages) + { + ids.add(msg.getMessage().getMessageNumber()); + } + } + } + + return ids; + } + + protected boolean checkRequirements(AMQQueue fromQueue, AMQQueue toQueue, List<Long> msgids) + { + if (toQueue == null) + { + _console.println("Destination queue not specifed."); + _console.println(usage()); + return false; + } + + if (fromQueue == null) + { + _console.println("Source queue not specifed."); + _console.println(usage()); + return false; + } + + return true; + } + + protected void doCommand(AMQQueue fromQueue, long start, long id, AMQQueue toQueue) + { + ServerTransaction txn = new LocalTransaction(fromQueue.getVirtualHost().getTransactionLog()); + fromQueue.moveMessagesToAnotherQueue(start, id, toQueue.getNameShortString().toString(), txn); + txn.commit(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Purge.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Purge.java new file mode 100644 index 0000000000..8df4afa2db --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Purge.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.tools.messagestore.commands; + +import org.apache.qpid.tools.messagestore.MessageStoreTool; +import org.apache.qpid.server.queue.AMQQueue; + +public class Purge extends Move +{ + public Purge(MessageStoreTool tool) + { + super(tool); + } + + public String help() + { + return "Purge messages from a queue.\n" + + "The currently selected message set will be purged from the specifed queue.\n" + + "Alternatively the values can be provided on the command line."; + } + + public String usage() + { + return "purge from=<queue> [msgids=<msgids eg, 1,2,4-10>]"; + } + + public String getCommand() + { + return "purge"; + } + + + protected boolean checkRequirements(AMQQueue fromQueue, AMQQueue toQueue, java.util.List<Long> msgids) + { + if (fromQueue == null) + { + _console.println("Source queue not specifed."); + _console.println(usage()); + return false; + } + + return true; + } + + protected void doCommand(AMQQueue fromQueue, long start, long end, AMQQueue toQueue) + { + fromQueue.removeMessagesFromQueue(start, end); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Quit.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Quit.java new file mode 100644 index 0000000000..a81bc07c38 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Quit.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.tools.messagestore.commands; + +import org.apache.qpid.tools.messagestore.MessageStoreTool; + +public class Quit extends AbstractCommand +{ + public Quit(MessageStoreTool tool) + { + super(tool); + } + + public String help() + { + return "Quit the tool."; + } + + public String usage() + { + return "quit"; + } + + public String getCommand() + { + return "quit"; + } + + public void execute(String... args) + { + assert args.length > 0; + assert args[0].equals("quit"); + + _tool.quit(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Select.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Select.java new file mode 100644 index 0000000000..ff59568374 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Select.java @@ -0,0 +1,233 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.tools.messagestore.commands; + +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.server.exchange.Exchange; +import org.apache.qpid.server.registry.ApplicationRegistry; +import org.apache.qpid.server.virtualhost.VirtualHost; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.tools.messagestore.MessageStoreTool; + +import java.util.LinkedList; +import java.util.StringTokenizer; + +public class Select extends AbstractCommand +{ + + public Select(MessageStoreTool tool) + { + super(tool); + } + + public String help() + { + return "Perform a selection."; + } + + public String usage() + { + return "select virtualhost <name> |exchange <name> |queue <name> | msg id=<msgids eg. 1,2,4-10>"; + } + + public String getCommand() + { + return "select"; + } + + public void execute(String... args) + { + assert args.length > 2; + assert args[0].equals("select"); + + if (args.length < 3) + { + if (args[1].equals("show")) + { + doSelect(args[1], null); + } + else + { + _console.print("select : unknown command:"); + _console.println(help()); + } + } + else + { + if (args[1].equals("virtualhost") + || args[1].equals("vhost") + || args[1].equals("exchange") + || args[1].equals("queue") + || args[1].equals("msg") + ) + { + doSelect(args[1], args[2]); + } + else + { + _console.println(help()); + } + } + } + + private void doSelect(String type, String item) + { + if (type.equals("virtualhost")) + { + + VirtualHost vhost = ApplicationRegistry.getInstance() + .getVirtualHostRegistry().getVirtualHost(item); + + if (vhost == null) + { + _console.println("Virtualhost '" + item + "' not found."); + } + else + { + _tool.getState().setVhost(vhost); + } + } + + if (type.equals("exchange")) + { + + VirtualHost vhost = _tool.getState().getVhost(); + + if (vhost == null) + { + _console.println("No Virtualhost open. Open a Virtualhost first."); + return; + } + + + Exchange exchange = vhost.getExchangeRegistry().getExchange(new AMQShortString(item)); + + if (exchange == null) + { + _console.println("Exchange '" + item + "' not found."); + } + else + { + _tool.getState().setExchange(exchange); + } + + if (_tool.getState().getQueue() != null) + { + if (!exchange.isBound(_tool.getState().getQueue())) + { + _tool.getState().setQueue(null); + } + } + } + + if (type.equals("queue")) + { + VirtualHost vhost = _tool.getState().getVhost(); + + if (vhost == null) + { + _console.println("No Virtualhost open. Open a Virtualhost first."); + return; + } + + AMQQueue queue = vhost.getQueueRegistry().getQueue(new AMQShortString(item)); + + if (queue == null) + { + _console.println("Queue '" + item + "' not found."); + } + else + { + _tool.getState().setQueue(queue); + + if (_tool.getState().getExchange() == null) + { + for (AMQShortString exchangeName : vhost.getExchangeRegistry().getExchangeNames()) + { + Exchange exchange = vhost.getExchangeRegistry().getExchange(exchangeName); + if (exchange.isBound(queue)) + { + _tool.getState().setExchange(exchange); + break; + } + } + } + + //remove the message selection + _tool.getState().setMessages((java.util.List<Long>) null); + } + } + + if (type.equals("msg")) + { + if (item.startsWith("id=")) + { + StringTokenizer tok = new StringTokenizer(item.substring(item.indexOf("=") + 1), ","); + + java.util.List<Long> msgids = null; + + if (tok.hasMoreTokens()) + { + msgids = new LinkedList<Long>(); + } + + while (tok.hasMoreTokens()) + { + String next = tok.nextToken(); + if (next.contains("-")) + { + Long start = Long.parseLong(next.substring(0, next.indexOf("-"))); + Long end = Long.parseLong(next.substring(next.indexOf("-") + 1)); + + if (end >= start) + { + for (long l = start; l <= end; l++) + { + msgids.add(l); + } + } + } + else + { + msgids.add(Long.parseLong(next)); + } + } + + _tool.getState().setMessages(msgids); + } + + } + + if (type.equals("show")) + { + _console.println(_tool.getState().toString()); + if (_tool.getState().getMessages() != null) + { + _console.print("Msgs:"); + for (Long l : _tool.getState().getMessages()) + { + _console.print(" " + l); + } + _console.println(""); + } + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Show.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Show.java new file mode 100644 index 0000000000..806e161bbc --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/messagestore/commands/Show.java @@ -0,0 +1,516 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.tools.messagestore.commands; + +import org.apache.qpid.AMQException; +import org.apache.qpid.framing.AMQShortString; +import org.apache.qpid.framing.BasicContentHeaderProperties; +import org.apache.qpid.framing.FieldTable; +import org.apache.qpid.framing.abstraction.MessagePublishInfo; +import org.apache.qpid.server.message.AMQMessage; +import org.apache.qpid.server.queue.AMQQueue; +import org.apache.qpid.server.queue.QueueEntry; +import org.apache.qpid.server.message.ServerMessage; +import org.apache.qpid.tools.messagestore.MessageStoreTool; +import org.apache.qpid.tools.utils.Console; + +import java.util.LinkedList; +import java.util.List; + +public class Show extends AbstractCommand +{ + protected boolean _amqHeaders = false; + protected boolean _routing = false; + protected boolean _msgHeaders = false; + + public Show(MessageStoreTool tool) + { + super(tool); + } + + public String help() + { + return "Shows the messages headers."; + } + + public String usage() + { + return getCommand() + " [show=[all],[msgheaders],[amqheaders],[routing]] [id=<msgid e.g. 1,2,4-10>]"; + } + + public String getCommand() + { + return "show"; + } + + public void execute(String... args) + { + assert args.length > 0; + assert args[0].equals(getCommand()); + + if (args.length < 2) + { + parseArgs("all"); + } + else + { + parseArgs(args); + } + + performShow(); + } + + protected void parseArgs(String... args) + { + List<Long> msgids = null; + + if (args.length >= 2) + { + for (String arg : args) + { + if (arg.startsWith("show=")) + { + _msgHeaders = arg.contains("msgheaders") || arg.contains("all"); + _amqHeaders = arg.contains("amqheaders") || arg.contains("all"); + _routing = arg.contains("routing") || arg.contains("all"); + } + + if (arg.startsWith("id=")) + { + _tool.getState().setMessages(msgids); + } + }//for args + }// if args > 2 + } + + protected void performShow() + { + if (_tool.getState().getVhost() == null) + { + _console.println("No Virtualhost selected. 'DuSelect' a Virtualhost first."); + return; + } + + AMQQueue _queue = _tool.getState().getQueue(); + + List<Long> msgids = _tool.getState().getMessages(); + + if (_queue != null) + { + List<QueueEntry> messages = _queue.getMessagesOnTheQueue(); + if (messages == null || messages.size() == 0) + { + _console.println("No messages on queue"); + return; + } + + List<List> data = createMessageData(msgids, messages, _amqHeaders, _routing, _msgHeaders); + if (data != null) + { + _console.printMap(null, data); + } + else + { + String message = "No data to display."; + if (msgids != null) + { + message += " Is message selection correct? " + _tool.getState().printMessages(); + } + _console.println(message); + } + + } + else + { + _console.println("No Queue specified to show."); + } + } + + /** + * Create the list data for display from the messages. + * + * @param msgids The list of message ids to display + * @param messages A list of messages to format and display. + * @param showHeaders should the header info be shown + * @param showRouting show the routing info be shown + * @param showMessageHeaders show the msg headers be shown + * @return the formated data lists for printing + */ + protected List<List> createMessageData(List<Long> msgids, List<QueueEntry> messages, boolean showHeaders, boolean showRouting, + boolean showMessageHeaders) + { + + // Currenly exposed message properties +// //Printing the content Body +// msg.getContentBodyIterator(); +// //Print the Headers +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getAppId(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getAppIdAsString(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getClusterId(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getContentType(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getCorrelationId(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getDeliveryMode(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getEncoding(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getExpiration(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getHeaders(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getMessageNumber(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getPriority(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getPropertyFlags(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getReplyTo(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getTimestamp(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getType(); +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getUserId(); +// +// //Print out all the property names +// ((BasicContentHeaderProperties)msg.getContentHeaderBody().properties).getHeaders().getPropertyNames(); +// +// msg.getMessageNumber(); +// msg.getSize(); +// msg.getArrivalTime(); + +// msg.getDeliveredSubscription(); +// msg.getDeliveredToConsumer(); +// msg.getMessageHandle(); +// msg.getMessageNumber(); +// msg.getMessagePublishInfo(); +// msg.getPublisher(); + +// msg.getStoreContext(); +// msg.isAllContentReceived(); +// msg.isPersistent(); +// msg.isRedelivered(); +// msg.isRejectedBy(); +// msg.isTaken(); + + //Header setup + + List<List> data = new LinkedList<List>(); + + List<String> id = new LinkedList<String>(); + data.add(id); + id.add(Columns.ID.name()); + id.add(Console.ROW_DIVIDER); + + List<String> exchange = new LinkedList<String>(); + List<String> routingkey = new LinkedList<String>(); + List<String> immediate = new LinkedList<String>(); + List<String> mandatory = new LinkedList<String>(); + if (showRouting) + { + data.add(exchange); + exchange.add(Columns.Exchange.name()); + exchange.add(Console.ROW_DIVIDER); + + data.add(routingkey); + routingkey.add(Columns.RoutingKey.name()); + routingkey.add(Console.ROW_DIVIDER); + + data.add(immediate); + immediate.add(Columns.isImmediate.name()); + immediate.add(Console.ROW_DIVIDER); + + data.add(mandatory); + mandatory.add(Columns.isMandatory.name()); + mandatory.add(Console.ROW_DIVIDER); + } + + List<String> size = new LinkedList<String>(); + List<String> appid = new LinkedList<String>(); + List<String> clusterid = new LinkedList<String>(); + List<String> contenttype = new LinkedList<String>(); + List<String> correlationid = new LinkedList<String>(); + List<String> deliverymode = new LinkedList<String>(); + List<String> encoding = new LinkedList<String>(); + List<String> arrival = new LinkedList<String>(); + List<String> expiration = new LinkedList<String>(); + List<String> priority = new LinkedList<String>(); + List<String> propertyflag = new LinkedList<String>(); + List<String> replyto = new LinkedList<String>(); + List<String> timestamp = new LinkedList<String>(); + List<String> type = new LinkedList<String>(); + List<String> userid = new LinkedList<String>(); + List<String> ispersitent = new LinkedList<String>(); + List<String> isredelivered = new LinkedList<String>(); + List<String> isdelivered = new LinkedList<String>(); + + data.add(size); + size.add(Columns.Size.name()); + size.add(Console.ROW_DIVIDER); + + if (showHeaders) + { + data.add(ispersitent); + ispersitent.add(Columns.isPersistent.name()); + ispersitent.add(Console.ROW_DIVIDER); + + data.add(isredelivered); + isredelivered.add(Columns.isRedelivered.name()); + isredelivered.add(Console.ROW_DIVIDER); + + data.add(isdelivered); + isdelivered.add(Columns.isDelivered.name()); + isdelivered.add(Console.ROW_DIVIDER); + + data.add(appid); + appid.add(Columns.App_ID.name()); + appid.add(Console.ROW_DIVIDER); + + data.add(clusterid); + clusterid.add(Columns.Cluster_ID.name()); + clusterid.add(Console.ROW_DIVIDER); + + data.add(contenttype); + contenttype.add(Columns.Content_Type.name()); + contenttype.add(Console.ROW_DIVIDER); + + data.add(correlationid); + correlationid.add(Columns.Correlation_ID.name()); + correlationid.add(Console.ROW_DIVIDER); + + data.add(deliverymode); + deliverymode.add(Columns.Delivery_Mode.name()); + deliverymode.add(Console.ROW_DIVIDER); + + data.add(encoding); + encoding.add(Columns.Encoding.name()); + encoding.add(Console.ROW_DIVIDER); + + data.add(arrival); + expiration.add(Columns.Arrival.name()); + expiration.add(Console.ROW_DIVIDER); + + data.add(expiration); + expiration.add(Columns.Expiration.name()); + expiration.add(Console.ROW_DIVIDER); + + data.add(priority); + priority.add(Columns.Priority.name()); + priority.add(Console.ROW_DIVIDER); + + data.add(propertyflag); + propertyflag.add(Columns.Property_Flag.name()); + propertyflag.add(Console.ROW_DIVIDER); + + data.add(replyto); + replyto.add(Columns.ReplyTo.name()); + replyto.add(Console.ROW_DIVIDER); + + data.add(timestamp); + timestamp.add(Columns.Timestamp.name()); + timestamp.add(Console.ROW_DIVIDER); + + data.add(type); + type.add(Columns.Type.name()); + type.add(Console.ROW_DIVIDER); + + data.add(userid); + userid.add(Columns.UserID.name()); + userid.add(Console.ROW_DIVIDER); + } + + List<String> msgHeaders = new LinkedList<String>(); + if (showMessageHeaders) + { + data.add(msgHeaders); + msgHeaders.add(Columns.MsgHeaders.name()); + msgHeaders.add(Console.ROW_DIVIDER); + } + + //Add create the table of data + for (QueueEntry entry : messages) + { + ServerMessage msg = entry.getMessage(); + if (!includeMsg(msg, msgids)) + { + continue; + } + + id.add(msg.getMessageNumber().toString()); + + size.add("" + msg.getSize()); + + arrival.add("" + msg.getArrivalTime()); + + ispersitent.add(msg.isPersistent() ? "true" : "false"); + + + isredelivered.add(entry.isRedelivered() ? "true" : "false"); + + isdelivered.add(entry.getDeliveredToConsumer() ? "true" : "false"); + +// msg.getMessageHandle(); + + BasicContentHeaderProperties headers = null; + + try + { + if(msg instanceof AMQMessage) + { + headers = ((BasicContentHeaderProperties) ((AMQMessage)msg).getContentHeaderBody().getProperties()); + } + } + catch (AMQException e) + { + //ignore +// commandError("Unable to read properties for message: " + e.getMessage(), null); + } + + if (headers != null) + { + String appidS = headers.getAppIdAsString(); + appid.add(appidS == null ? "null" : appidS); + + String clusterS = headers.getClusterIdAsString(); + clusterid.add(clusterS == null ? "null" : clusterS); + + String contentS = headers.getContentTypeAsString(); + contenttype.add(contentS == null ? "null" : contentS); + + String correlationS = headers.getCorrelationIdAsString(); + correlationid.add(correlationS == null ? "null" : correlationS); + + deliverymode.add("" + headers.getDeliveryMode()); + + AMQShortString encodeSS = headers.getEncoding(); + encoding.add(encodeSS == null ? "null" : encodeSS.toString()); + + expiration.add("" + headers.getExpiration()); + + FieldTable headerFT = headers.getHeaders(); + msgHeaders.add(headerFT == null ? "none" : "" + headerFT.toString()); + + priority.add("" + headers.getPriority()); + propertyflag.add("" + headers.getPropertyFlags()); + + AMQShortString replytoSS = headers.getReplyTo(); + replyto.add(replytoSS == null ? "null" : replytoSS.toString()); + + timestamp.add("" + headers.getTimestamp()); + + AMQShortString typeSS = headers.getType(); + type.add(typeSS == null ? "null" : typeSS.toString()); + + AMQShortString useridSS = headers.getUserId(); + userid.add(useridSS == null ? "null" : useridSS.toString()); + + MessagePublishInfo info = null; + try + { + if(msg instanceof AMQMessage) + { + info = ((AMQMessage)msg).getMessagePublishInfo(); + } + + } + catch (AMQException e) + { + //ignore + } + + if (info != null) + { + AMQShortString exchangeSS = info.getExchange(); + exchange.add(exchangeSS == null ? "null" : exchangeSS.toString()); + + AMQShortString routingkeySS = info.getRoutingKey(); + routingkey.add(routingkeySS == null ? "null" : routingkeySS.toString()); + + immediate.add(info.isImmediate() ? "true" : "false"); + mandatory.add(info.isMandatory() ? "true" : "false"); + } + +// msg.getPublisher(); -- only used in clustering +// msg.getStoreContext(); +// msg.isAllContentReceived(); + + }// if headers!=null + +// need to access internal map and do lookups. +// msg.isTaken(); +// msg.getDeliveredSubscription(); +// msg.isRejectedBy(); + + } + + // if id only had the header and the divider in it then we have no data to display + if (id.size() == 2) + { + return null; + } + return data; + } + + protected boolean includeMsg(ServerMessage msg, List<Long> msgids) + { + if (msgids == null) + { + return true; + } + + Long msgid = msg.getMessageNumber(); + + boolean found = false; + + if (msgids != null) + { + //check msgid is in msgids + for (Long l : msgids) + { + if (l.equals(msgid)) + { + found = true; + break; + } + } + } + return found; + } + + public enum Columns + { + ID, + Size, + Exchange, + RoutingKey, + isImmediate, + isMandatory, + isPersistent, + isRedelivered, + isDelivered, + App_ID, + Cluster_ID, + Content_Type, + Correlation_ID, + Delivery_Mode, + Encoding, + Arrival, + Expiration, + Priority, + Property_Flag, + ReplyTo, + Timestamp, + Type, + UserID, + MsgHeaders + } +} + + diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/security/Passwd.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/security/Passwd.java new file mode 100644 index 0000000000..c27c52eb8e --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/security/Passwd.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.tools.security; + +import org.apache.commons.codec.binary.Base64; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.DigestException; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintStream; + +public class Passwd +{ + public static void main(String args[]) throws NoSuchAlgorithmException, DigestException, IOException + { + if (args.length != 2) + { + System.out.println("Passwd <username> <password>"); + System.exit(0); + } + + byte[] data = args[1].getBytes("utf-8"); + + MessageDigest md = MessageDigest.getInstance("MD5"); + + for (byte b : data) + { + md.update(b); + } + + byte[] digest = md.digest(); + + Base64 b64 = new Base64(); + + byte[] encoded = b64.encode(digest); + + output(args[0], encoded); + } + + private static void output(String user, byte[] encoded) throws IOException + { + +// File passwdFile = new File("qpid.passwd"); + + PrintStream ps = new PrintStream(System.out); + + user += ":"; + ps.write(user.getBytes("utf-8")); + + for (byte b : encoded) + { + ps.write(b); + } + + ps.println(); + + ps.flush(); + ps.close(); + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/CommandParser.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/CommandParser.java new file mode 100644 index 0000000000..986fea32cc --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/CommandParser.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.tools.utils; + +public interface CommandParser +{ + /** + * If there is more than one command received on the last parse request. + * + * Subsequent calls to parse will utilise this input rather than reading new data from the input source + * @return boolean + */ + boolean more(); + + /** + * True if the currently parsed command has been requested as a background operation + * + * @return boolean + */ + boolean isBackground(); + + /** + * Parses user commands, and groups tokens in the + * String[] format that all Java main's love. + * + * If more than one command is provided in one input line then the more() method will return true. + * A subsequent call to parse() will continue to parse that input line before reading new input. + * + * @return <code>input</code> split in args[] format; null if eof. + * @throws java.io.IOException if there is a problem reading from the input stream + */ + String[] parse() throws java.io.IOException; +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/Console.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/Console.java new file mode 100644 index 0000000000..cf457d1ea5 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/Console.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.tools.utils; + +import java.util.List; + +public interface Console +{ + public enum CellFormat + { + CENTRED, LEFT, RIGHT + } + + public static String ROW_DIVIDER = "*divider"; + + public void print(String... message); + + public void println(String... message); + + public String readln(); + + /** + * Reads and parses the command line. + * + * + * @return The next command or null + */ + public String[] readCommand(); + + public CommandParser getCommandParser(); + + public void setCommandParser(CommandParser parser); + + /** + * + * Prints the list of String nicely. + * + * +-------------+ + * | Heading | + * +-------------+ + * | Item 1 | + * | Item 2 | + * | Item 3 | + * +-------------+ + * + * @param hasTitle should list[0] be used as a heading + * @param list The list of Strings to display + */ + public void displayList(boolean hasTitle, String... list); + + /** + * + * Prints the list of String nicely. + * + * +----------------------------+ + * | Heading | + * +----------------------------+ + * | title | title | .. + * +----------------------------+ + * | Item 2 | value 2 | .. + * +----------------------------+ (*divider) + * | Item 3 | value 2 | .. + * +----------------------------+ + * + * @param title The title to display if any + * @param entries the entries to display in a map. + */ + void printMap(String title, List<List> entries); + + + public void close(); +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/SimpleCommandParser.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/SimpleCommandParser.java new file mode 100644 index 0000000000..09444ccdd7 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/SimpleCommandParser.java @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.tools.utils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.StringTokenizer; + +public class SimpleCommandParser implements CommandParser +{ + private static final String COMMAND_SEPERATOR = ";"; + + /** Input source of commands */ + protected BufferedReader _reader; + + /** The next list of commands from the command line */ + private StringBuilder _nextCommand = null; + + public SimpleCommandParser(BufferedReader reader) + { + _reader = reader; + } + + public boolean more() + { + return _nextCommand != null; + } + + public boolean isBackground() + { + return false; + } + + public String[] parse() throws IOException + { + String[] commands = null; + + String input = null; + + if (_nextCommand == null) + { + input = _reader.readLine(); + } + else + { + input = _nextCommand.toString(); + _nextCommand = null; + } + + if (input == null) + { + return null; + } + + StringTokenizer tok = new StringTokenizer(input, " "); + + int tokenCount = tok.countTokens(); + int index = 0; + + if (tokenCount > 0) + { + commands = new String[tokenCount]; + boolean commandComplete = false; + + while (tok.hasMoreTokens()) + { + String next = tok.nextToken(); + + if (next.equals(COMMAND_SEPERATOR)) + { + commandComplete = true; + _nextCommand = new StringBuilder(); + continue; + } + + if (commandComplete) + { + _nextCommand.append(next); + _nextCommand.append(" "); + } + else + { + commands[index] = next; + index++; + } + } + + } + + //Reduce the String[] if not all the tokens were used in this command. + // i.e. there is more than one command on the line. + if (index != tokenCount) + { + String[] shortCommands = new String[index]; + System.arraycopy(commands, 0, shortCommands, 0, index); + return shortCommands; + } + else + { + return commands; + } + } +} diff --git a/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/SimpleConsole.java b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/SimpleConsole.java new file mode 100644 index 0000000000..2791a39f92 --- /dev/null +++ b/qpid/java/broker/src/main/java/org/apache/qpid/tools/utils/SimpleConsole.java @@ -0,0 +1,364 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.tools.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +public class SimpleConsole implements Console +{ + /** SLF4J Logger. */ + private static Logger _devlog = LoggerFactory.getLogger(SimpleConsole.class); + + /** Console Writer. */ + protected BufferedWriter _consoleWriter; + + /** Console Reader. */ + protected BufferedReader _consoleReader; + + /** Parser for command-line input. */ + protected CommandParser _parser; + + public SimpleConsole(BufferedWriter writer, BufferedReader reader) + { + _consoleWriter = writer; + _consoleReader = reader; + _parser = new SimpleCommandParser(_consoleReader); + } + + public void print(String... message) + { + try + { + for (String s : message) + { + _consoleWriter.write(s); + } + _consoleWriter.flush(); + } + catch (IOException e) + { + _devlog.error(e.getMessage() + ": Occurred whilst trying to write:" + Arrays.asList(message)); + } + + } + + public void println(String... message) + { + print(message); + print(System.getProperty("line.separator")); + } + + + public String readln() + { + try + { + return _consoleReader.readLine(); + } + catch (IOException e) + { + _devlog.debug("Unable to read input due to:" + e.getMessage()); + return null; + } + } + + public String[] readCommand() + { + try + { + return _parser.parse(); + } + catch (IOException e) + { + _devlog.error("Error reading command:" + e.getMessage()); + return new String[0]; + } + } + + public CommandParser getCommandParser() + { + return _parser; + } + + public void setCommandParser(CommandParser parser) + { + _parser = parser; + } + + public void displayList(boolean hasTitle, String... list) + { + java.util.List<java.util.List> data = new LinkedList<List>(); + + java.util.List<String> values = new LinkedList<String>(); + + data.add(values); + + for (String value : list) + { + values.add(value); + } + + if (hasTitle) + { + values.add(1, "*divider"); + } + + printMap(null, data); + } + + /** + * + * Prints the list of String nicely. + * + * +----------------------------+ + * | Heading | + * +----------------------------+ + * | title | title | .. + * +----------------------------+ + * | Item 2 | value 2 | .. + * | Item 3 | value 2 | .. + * +----------------------------+ + * + * @param title The title to display if any + * @param entries the entries to display in a map. + */ + public void printMap(String title, java.util.List<java.util.List> entries) + { + try + { + int columns = entries.size(); + + int[] columnWidth = new int[columns]; + + // calculate row count + int rowMax = 0; + + //the longest item + int itemMax = 0; + + for (int i = 0; i < columns; i++) + { + int columnIRowMax = entries.get(i).size(); + + if (columnIRowMax > rowMax) + { + rowMax = columnIRowMax; + } + for (Object values : entries.get(i)) + { + if (values.toString().equals(Console.ROW_DIVIDER)) + { + continue; + } + + int itemLength = values.toString().length(); + + //note for single width + if (itemLength > itemMax) + { + itemMax = itemLength; + } + + //note for mulit width + if (itemLength > columnWidth[i]) + { + columnWidth[i] = itemLength; + } + + } + } + + int tableWidth = 0; + + + for (int i = 0; i < columns; i++) + { + // plus 2 for the space padding + columnWidth[i] += 2; + } + for (int size : columnWidth) + { + tableWidth += size; + } + tableWidth += (columns - 1); + + if (title != null) + { + if (title.length() > tableWidth) + { + tableWidth = title.length(); + } + + printCellRow("+", "-", tableWidth); + + printCell(CellFormat.CENTRED, "|", tableWidth, " " + title + " ", 0); + _consoleWriter.newLine(); + + } + + //put top line | or bottom of title + printCellRow("+", "-", tableWidth); + + //print the table data + int row = 0; + + for (; row < rowMax; row++) + { + for (int i = 0; i < columns; i++) + { + java.util.List columnData = entries.get(i); + + String value; + // does this column have a value for this row + if (columnData.size() > row) + { + value = " " + columnData.get(row).toString() + " "; + } + else + { + value = " "; + } + + if (i == 0 && value.equals(" " + Console.ROW_DIVIDER + " ")) + { + printCellRow("+", "-", tableWidth); + //move on to the next row + break; + } + else + { + printCell(CellFormat.LEFT, "|", columnWidth[i], value, i); + } + + // if it is the last row then do a new line. + if (i == columns - 1) + { + _consoleWriter.newLine(); + } + } + } + + printCellRow("+", "-", tableWidth); + + } + catch (IOException e) + { + _devlog.error(e.getMessage() + ": Occured whilst trying to write."); + } + } + + public void close() + { + + try + { + _consoleReader.close(); + } + catch (IOException e) + { + _devlog.error(e.getMessage() + ": Occured whilst trying to close reader."); + } + + try + { + + _consoleWriter.close(); + } + catch (IOException e) + { + _devlog.error(e.getMessage() + ": Occured whilst trying to close writer."); + } + + } + + private void printCell(CellFormat format, String edge, int cellWidth, String cell, int column) throws IOException + { + int pad = cellWidth - cell.length(); + + if (column == 0) + { + _consoleWriter.write(edge); + } + + switch (format) + { + case CENTRED: + printPad(" ", pad / 2); + break; + case RIGHT: + printPad(" ", pad); + break; + } + + _consoleWriter.write(cell); + + + switch (format) + { + case CENTRED: + // if pad isn't even put the extra one on the right + if (pad % 2 == 0) + { + printPad(" ", pad / 2); + } + else + { + printPad(" ", (pad / 2) + 1); + } + break; + case LEFT: + printPad(" ", pad); + break; + } + + + _consoleWriter.write(edge); + + } + + private void printCellRow(String edge, String mid, int cellWidth) throws IOException + { + _consoleWriter.write(edge); + + printPad(mid, cellWidth); + + _consoleWriter.write(edge); + _consoleWriter.newLine(); + } + + private void printPad(String padChar, int count) throws IOException + { + for (int i = 0; i < count; i++) + { + _consoleWriter.write(padChar); + } + } + + +} |